@gotza02/sequential-thinking 2026.2.7 → 2026.2.10

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
@@ -1,17 +1,18 @@
1
1
  # Sequential Thinking MCP Server (Extended Edition) 🧠✨
2
2
 
3
- **MCP Server ที่ยกระดับ AI ให้เป็นวิศวกรซอฟต์แวร์อัจฉริยะ ด้วยระบบ Deepest Thinking และการวิเคราะห์ Codebase เชิงลึก**
3
+ **MCP Server ที่ยกระดับ AI ให้เป็นวิศวกรซอฟต์แวร์อัจฉริยะ ด้วยระบบ Deepest Thinking, การวิเคราะห์ Codebase เชิงลึก และฐานข้อมูลความรู้ (Code Database)**
4
4
 
5
- โปรเจกต์นี้คือส่วนขยายขั้นสูงของ Sequential Thinking ที่รวมเอาความสามารถในการวางแผนที่เป็นระบบ, การหาข้อมูลทั่วโลก (Web Search), การสร้างแผนผังความสัมพันธ์ของโค้ด (Dependency Graph) และการจัดการหน่วยความจำระยะยาวเข้าด้วยกัน เพื่อให้ AI สามารถทำงานที่ซับซ้อนได้อย่างอิสระและแม่นยำ
5
+ โปรเจกต์นี้คือส่วนขยายขั้นสูงของ Sequential Thinking ที่รวมเอาความสามารถในการวางแผนที่เป็นระบบ, การหาข้อมูลทั่วโลก (Web Search), การสร้างแผนผังความสัมพันธ์ของโค้ด (Dependency Graph), การจัดการหน่วยความจำระยะยาว และ **ฐานข้อมูลความรู้โค้ด (Code Database)** เข้าด้วยกัน เพื่อให้ AI สามารถทำงานที่ซับซ้อนได้อย่างอิสระและแม่นยำ
6
6
 
7
7
  ---
8
8
 
9
9
  ## 🌟 จุดเด่นของเวอร์ชัน Extended
10
10
 
11
11
  1. **Deepest Thinking Protocol**: บังคับให้ AI วิเคราะห์ปัญหาแบบเป็นลำดับขั้นตอน (Step-by-step) พร้อมการแตกกิ่งความคิด (Branching) และการทบทวนตัวเอง (Reflexion) เพื่อหาทางเลือกที่ดีที่สุด
12
- 2. **Codebase Intelligence**: ระบบ `ProjectKnowledgeGraph` ที่ใช้ TypeScript Compiler API จริงๆ ในการสแกนความสัมพันธ์ระหว่างไฟล์และ Exported Symbols
13
- 3. **Deep Coding Workflow**: เครื่องมือใหม่สำหรับการแก้ไขโค้ดที่ต้องผ่านการวิเคราะห์บริบท (Context Document) และการวางแผนที่ผ่านการตรวจสอบเหตุผลแล้วเท่านั้น
14
- 4. **Optimized Web Extraction**: ระบบอ่านเว็บที่ตัดโฆษณาและส่วนที่ไม่จำเป็นออก (Readability) แปลงเป็น Markdown เพื่อประหยัด Token และให้ข้อมูลที่สะอาดที่สุด
12
+ 2. **Codebase Intelligence**: ระบบ `ProjectKnowledgeGraph` ที่ใช้ TypeScript Compiler API และ Regex (สำหรับ Python/Go) ในการสแกนความสัมพันธ์ระหว่างไฟล์และ Exported Symbols
13
+ 3. **Code Database (CodeStore)**: ระบบจัดเก็บ Snippets และ Architectural Patterns ลงในไฟล์ JSON ถาวร ช่วยให้ AI "จดจำ" วิธีแก้ปัญหาและนำกลับมาใช้ใหม่ได้
14
+ 4. **Deep Coding Workflow**: เครื่องมือใหม่สำหรับการแก้ไขโค้ดที่ต้องผ่านการวิเคราะห์บริบท (Context Document) และการวางแผนที่ผ่านการตรวจสอบเหตุผลแล้วเท่านั้น
15
+ 5. **Smart Notes**: ระบบบันทึกที่มี **Priority Level** และ **Expiration Date** ช่วยจัดลำดับความสำคัญของงานได้ดียิ่งขึ้น
15
16
 
16
17
  ---
17
18
 
@@ -25,7 +26,9 @@
25
26
  | `GOOGLE_SEARCH_CX` | Google Custom Search Engine ID (CX) | ❌ (Optional) |
26
27
  | `THOUGHTS_STORAGE_PATH` | ไฟล์เก็บประวัติความคิดเพื่อใช้ต่อเนื่อง | `thoughts_history.json` |
27
28
  | `NOTES_STORAGE_PATH` | ไฟล์เก็บความจำระยะยาว (กฎโปรเจกต์, ความชอบ) | `project_notes.json` |
29
+ | `CODE_DB_PATH` | ไฟล์เก็บฐานข้อมูลความรู้โค้ด (Snippets/Patterns) | `code_database.json` |
28
30
  | `THOUGHT_DELAY_MS` | เวลาหน่วงระหว่างคิด (เพื่อความสมจริงและการจัดการ Rate Limit) | `0` |
31
+ | `LOG_LEVEL` | ระดับการแสดงผล Log (debug, info, warn, error) | `info` |
29
32
 
30
33
  ---
31
34
 
@@ -39,31 +42,24 @@
39
42
  ### 💻 2. Deep Coding & Codebase Context
40
43
  * **`deep_code_analyze`**: 🌟 **(ใหม่)** สร้าง Codebase Context Document รวบรวมเนื้อหาไฟล์, Symbols และความสัมพันธ์ (Import/Export) เพื่อให้ AI "เรียนรู้" ก่อนแก้โค้ด
41
44
  * **`deep_code_edit`**: 🌟 **(ใหม่)** แก้ไขโค้ดแบบ Surgical Edit โดยต้องระบุ `reasoning` (เหตุผลเชิงลึก) ที่ผ่านการคิดวิเคราะห์มาแล้ว
42
- * **`build_project_graph`**: สแกนโปรเจกต์เพื่อสร้างแผนผังความสัมพันธ์ทั้งหมด
45
+ * **`build_project_graph`**: สแกนโปรเจกต์เพื่อสร้างแผนผังความสัมพันธ์ทั้งหมด (รองรับ TS, JS, PY, GO)
43
46
  * **`get_file_relationships`**: ตรวจสอบว่าไฟล์เป้าหมายกระทบกับส่วนไหนของระบบบ้าง
44
47
  * **`search_code`**: ค้นหา Code Pattern ทั้งโปรเจกต์แบบชาญฉลาด
45
48
 
46
- ### 🌐 3. External Research
49
+ ### 📚 3. Knowledge & Memory (CodeStore)
50
+ * **`add_code_snippet`**: 🌟 **(ใหม่)** บันทึก Snippet โค้ดที่ใช้งานบ่อยลงฐานข้อมูล
51
+ * **`search_code_db`**: 🌟 **(ใหม่)** ค้นหาความรู้เดิมจากฐานข้อมูล (ช่วยลดเวลาในการเริ่มงานใหม่)
52
+ * **`learn_architecture_pattern`**: 🌟 **(ใหม่)** บันทึกรูปแบบโครงสร้างของระบบ
53
+ * **`manage_notes`**: (ปรับปรุงใหม่!) จัดการบันทึกพร้อมระบุ **Priority** และ **Expiration**
54
+
55
+ ### 🌐 4. External Research
47
56
  * **`web_search`**: ค้นหาข้อมูลล่าสุดจากโลกภายนอก (Brave/Exa/Google)
48
57
  * **`read_webpage`**: อ่านเนื้อหาเว็บแปลงเป็น Markdown ที่สะอาดและเข้าใจง่าย
49
58
  * **`fetch`**: ดึงข้อมูล Raw จาก API ต่างๆ
50
59
 
51
- ### 📁 4. System & Persistence
60
+ ### 📁 5. System & Persistence
52
61
  * **`read_file` / `write_file` / `edit_file`**: จัดการไฟล์ในระบบ
53
- * **`shell_execute`**: รันคำสั่ง Terminal (เช่น Build, Test, Deploy)
54
- * **`manage_notes`**: จัดการหน่วยความจำระยะยาวข้ามเซสชัน
55
-
56
- ---
57
-
58
- ## 🔄 The "Deep Coding" Workflow (ขั้นตอนการทำงานที่แนะนำ)
59
-
60
- เพื่อให้ AI ทำงานได้อย่างเทพที่สุด แนะนำให้ใช้ลำดับการทำงานดังนี้:
61
-
62
- 1. **Map the Codebase**: รัน `build_project_graph` เพื่อให้ AI เข้าใจโครงสร้างโปรเจกต์ทั้งหมด
63
- 2. **Deep Analysis**: ใช้ `deep_code_analyze` กับไฟล์ที่เกี่ยวข้อง เพื่อสร้างบริบทและความเข้าใจ symbols ที่ใช้งาน
64
- 3. **Strategic Planning**: ใช้ `sequentialthinking` เพื่อวางแผนแบบ Deepest Thinking (แตกกิ่งความคิด, ทดสอบสมมติฐาน)
65
- 4. **Precision Edit**: ลงมือแก้ไขด้วย `deep_code_edit` หรือ `edit_file` พร้อมระบุเหตุผลที่วิเคราะห์มาแล้ว
66
- 5. **Verify**: ใช้ `shell_execute` รันคำสั่ง Test หรือ Lint เพื่อตรวจสอบความถูกต้อง
62
+ * **`shell_execute`**: รันคำสั่ง Terminal (มีระบบป้องกันคำสั่งอันตราย เช่น `rm -rf /`)
67
63
 
68
64
  ---
69
65
 
@@ -93,7 +89,7 @@ git clone https://github.com/gotza02/sequential-thinking.git && cd sequential-th
93
89
  ```bash
94
90
  npm run build
95
91
  ```
96
- 4. **Configuration**: เปิดไฟล์ตั้งค่า AI Client ของคุณ (เช่น `~/.gemini/settings.json`) แล้วเพิ่มการตั้งค่า MCP Server โดยอ้างอิง Path ไปยังโฟลเดอร์ที่ Build เสร็จแล้ว
92
+ 4. **Configuration**: ตั้งค่า AI Client ตามตัวอย่างด้านล่าง
97
93
 
98
94
  ---
99
95
 
@@ -117,6 +113,7 @@ git clone https://github.com/gotza02/sequential-thinking.git && cd sequential-th
117
113
  "GOOGLE_SEARCH_CX": "YOUR_GOOGLE_SEARCH_ENGINE_ID",
118
114
  "THOUGHTS_STORAGE_PATH": "thoughts_history.json",
119
115
  "NOTES_STORAGE_PATH": "project_notes.json",
116
+ "CODE_DB_PATH": "code_database.json",
120
117
  "THOUGHT_DELAY_MS": "1000",
121
118
  "DISABLE_THOUGHT_LOGGING": "false"
122
119
  }
@@ -139,6 +136,7 @@ git clone https://github.com/gotza02/sequential-thinking.git && cd sequential-th
139
136
  "EXA_API_KEY": "YOUR_EXA_KEY",
140
137
  "GOOGLE_SEARCH_API_KEY": "YOUR_GOOGLE_API_KEY",
141
138
  "GOOGLE_SEARCH_CX": "YOUR_GOOGLE_SEARCH_ENGINE_ID",
139
+ "CODE_DB_PATH": "code_database.json",
142
140
  "THOUGHT_DELAY_MS": "1000"
143
141
  }
144
142
  }
@@ -150,6 +148,16 @@ git clone https://github.com/gotza02/sequential-thinking.git && cd sequential-th
150
148
 
151
149
  ---
152
150
 
151
+ ## 🛠️ ติดตั้งคำสั่งระบบ (System Instruction Setup)
152
+
153
+ คุณสามารถติดตั้งคำสั่งระบบ **Ultimate Deep Engineer** ให้กับ Gemini และ Claude ได้โดยอัตโนมัติด้วย Script นี้:
154
+
155
+ ```bash
156
+ chmod +x install-ultimate-engineer.sh && ./install-ultimate-engineer.sh
157
+ ```
158
+
159
+ ---
160
+
153
161
  ## 🤖 Recommended System Instruction (The Ultimate Deep Engineer)
154
162
 
155
163
  ให้ Copy ข้อความด้านล่างนี้ไปใส่ใน **System Prompt** ของ AI Agent เพื่อประสิทธิภาพสูงสุด:
@@ -164,6 +172,7 @@ You are a Senior AI Software Engineer equipped with the Sequential Thinking MCP
164
172
  - **Atomic Analysis:** Use `thoughtType: 'analysis'` to break every request into atomic requirements and constraints before proposing solutions.
165
173
  - **Mandatory Reflexion:** Use `thoughtType: 'reflexion'` frequently to critique your own logic, identify potential edge cases, and challenge your assumptions.
166
174
  - **Tree of Thoughts:** For critical architectural decisions, use branching to explore multiple paths (Conservative vs. Aggressive) and evaluate them using `thoughtType: 'evaluation'`.
175
+ - **Loop Breaker:** If a bug persists after 2 attempts or reasoning loops, MUST create a new branch (`branchFromThought`) to change strategy completely.
167
176
  - **Verified Completion:** Only set `nextThoughtNeeded: false` when the solution is definitive, verified, and follows project standards.
168
177
 
169
178
  ## 🏗 CODEBASE INTELLIGENCE PROTOCOL
@@ -185,10 +194,11 @@ You are a Senior AI Software Engineer equipped with the Sequential Thinking MCP
185
194
 
186
195
  ## 📝 PERSISTENT MEMORY
187
196
  - **Long-term Knowledge:** Use `manage_notes` to save architectural decisions, user preferences, project conventions, and "lessons learned" that must persist across sessions.
197
+ - **Code Database:** Use `add_code_snippet` to store reusable patterns and `search_code_db` to retrieve them.
188
198
  - **Session Continuity:** Your thought history is saved. If you restart, review the history to maintain context.
189
199
  ```
190
200
 
191
201
  ---
192
202
 
193
203
  ## License
194
- MIT - พัฒนาโดย @gotza02
204
+ MIT - พัฒนาโดย @gotza02/sequential-thinking
@@ -0,0 +1,69 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ import { AsyncMutex } from './utils.js';
4
+ export class CodeDatabase {
5
+ filePath;
6
+ db = { snippets: [], patterns: {} };
7
+ loaded = false;
8
+ mutex = new AsyncMutex();
9
+ constructor(storagePath = 'code_database.json') {
10
+ this.filePath = path.resolve(storagePath);
11
+ }
12
+ async load() {
13
+ if (this.loaded)
14
+ return;
15
+ try {
16
+ const data = await fs.readFile(this.filePath, 'utf-8');
17
+ this.db = JSON.parse(data);
18
+ }
19
+ catch (error) {
20
+ this.db = { snippets: [], patterns: {} };
21
+ }
22
+ this.loaded = true;
23
+ }
24
+ async save() {
25
+ await fs.writeFile(this.filePath, JSON.stringify(this.db, null, 2), 'utf-8');
26
+ }
27
+ async addSnippet(snippet) {
28
+ return this.mutex.dispatch(async () => {
29
+ await this.load();
30
+ const newSnippet = {
31
+ ...snippet,
32
+ id: Math.random().toString(36).substring(2, 9),
33
+ updatedAt: new Date().toISOString()
34
+ };
35
+ this.db.snippets.push(newSnippet);
36
+ await this.save();
37
+ return newSnippet;
38
+ });
39
+ }
40
+ async searchSnippets(query) {
41
+ return this.mutex.dispatch(async () => {
42
+ await this.load();
43
+ const q = query.toLowerCase();
44
+ return this.db.snippets.filter(s => s.title.toLowerCase().includes(q) ||
45
+ s.description.toLowerCase().includes(q) ||
46
+ s.tags.some(t => t.toLowerCase().includes(q)) ||
47
+ s.code.toLowerCase().includes(q));
48
+ });
49
+ }
50
+ async learnPattern(name, description) {
51
+ return this.mutex.dispatch(async () => {
52
+ await this.load();
53
+ this.db.patterns[name] = description;
54
+ await this.save();
55
+ });
56
+ }
57
+ async getPattern(name) {
58
+ return this.mutex.dispatch(async () => {
59
+ await this.load();
60
+ return this.db.patterns[name] || null;
61
+ });
62
+ }
63
+ async listAllPatterns() {
64
+ return this.mutex.dispatch(async () => {
65
+ await this.load();
66
+ return this.db.patterns;
67
+ });
68
+ }
69
+ }
@@ -0,0 +1,59 @@
1
+ import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
2
+ import { CodeDatabase } from './codestore.js';
3
+ import * as fs from 'fs/promises';
4
+ vi.mock('fs/promises');
5
+ describe('CodeDatabase', () => {
6
+ let db;
7
+ const testPath = 'test_code_db.json';
8
+ let mockStore = '{"snippets": [], "patterns": {}}';
9
+ beforeEach(() => {
10
+ mockStore = '{"snippets": [], "patterns": {}}';
11
+ fs.readFile.mockImplementation(async () => mockStore);
12
+ fs.writeFile.mockImplementation(async (path, data) => {
13
+ mockStore = data;
14
+ });
15
+ db = new CodeDatabase(testPath);
16
+ });
17
+ afterEach(() => {
18
+ vi.clearAllMocks();
19
+ });
20
+ it('should add a code snippet', async () => {
21
+ const snippet = await db.addSnippet({
22
+ title: "Quick Sort",
23
+ language: "typescript",
24
+ code: "function sort() {}",
25
+ description: "A sorting algorithm",
26
+ tags: ["algo"]
27
+ });
28
+ expect(snippet.id).toBeDefined();
29
+ expect(snippet.title).toBe("Quick Sort");
30
+ const stored = JSON.parse(mockStore);
31
+ expect(stored.snippets).toHaveLength(1);
32
+ });
33
+ it('should search snippets', async () => {
34
+ await db.addSnippet({
35
+ title: "React Hook",
36
+ language: "ts",
37
+ code: "const useX = () => {}",
38
+ description: "Custom hook",
39
+ tags: ["react"]
40
+ });
41
+ await db.addSnippet({
42
+ title: "Python Script",
43
+ language: "py",
44
+ code: "print('hello')",
45
+ description: "Hello world",
46
+ tags: ["python"]
47
+ });
48
+ const results = await db.searchSnippets("hook");
49
+ expect(results).toHaveLength(1);
50
+ expect(results[0].title).toBe("React Hook");
51
+ });
52
+ it('should learn and retrieve patterns', async () => {
53
+ await db.learnPattern("Repository Pattern", "Separate data access from business logic.");
54
+ const pattern = await db.getPattern("Repository Pattern");
55
+ expect(pattern).toContain("Separate data access");
56
+ const all = await db.listAllPatterns();
57
+ expect(all["Repository Pattern"]).toBeDefined();
58
+ });
59
+ });
@@ -0,0 +1,48 @@
1
+ import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest';
2
+ import { validatePath } from './utils.js';
3
+ import * as path from 'path';
4
+ describe('FileSystem Security', () => {
5
+ // Mock process.cwd to be a known fixed path
6
+ const mockCwd = '/app/project';
7
+ // We need to spy on process.cwd and path.resolve behavior if we want to be strict,
8
+ // but validatePath uses path.resolve(requestedPath).
9
+ // Let's just mock path.resolve to behave as expected, or trust the real path module
10
+ // but ensure we set up the cwd check correctly.
11
+ // The easiest way is to mock process.cwd().
12
+ const originalCwd = process.cwd;
13
+ beforeEach(() => {
14
+ vi.spyOn(process, 'cwd').mockReturnValue(mockCwd);
15
+ });
16
+ afterEach(() => {
17
+ vi.restoreAllMocks();
18
+ });
19
+ it('should allow paths within the project root', () => {
20
+ const p = validatePath('src/index.ts');
21
+ expect(p).toBe(path.resolve(mockCwd, 'src/index.ts'));
22
+ });
23
+ it('should allow explicit ./ paths', () => {
24
+ const p = validatePath('./package.json');
25
+ expect(p).toBe(path.resolve(mockCwd, 'package.json'));
26
+ });
27
+ it('should block traversal to parent directory', () => {
28
+ expect(() => {
29
+ validatePath('../outside.txt');
30
+ }).toThrow(/Access denied/);
31
+ });
32
+ it('should block multiple level traversal', () => {
33
+ expect(() => {
34
+ validatePath('src/../../etc/passwd');
35
+ }).toThrow(/Access denied/);
36
+ });
37
+ it('should block absolute paths outside root', () => {
38
+ // path.resolve('/etc/passwd') returns '/etc/passwd' on linux-like
39
+ // path.resolve('C:/Windows') on windows...
40
+ // We rely on path.resolve behavior.
41
+ // If system is linux-like:
42
+ if (path.sep === '/') {
43
+ expect(() => {
44
+ validatePath('/etc/passwd');
45
+ }).toThrow(/Access denied/);
46
+ }
47
+ });
48
+ });
package/dist/graph.js CHANGED
@@ -103,6 +103,14 @@ export class ProjectKnowledgeGraph {
103
103
  }
104
104
  }
105
105
  }
106
+ // 4. Import Equals: import x = require('...')
107
+ else if (ts.isImportEqualsDeclaration(node)) {
108
+ if (ts.isExternalModuleReference(node.moduleReference)) {
109
+ if (ts.isStringLiteral(node.moduleReference.expression)) {
110
+ imports.push(node.moduleReference.expression.text);
111
+ }
112
+ }
113
+ }
106
114
  ts.forEachChild(node, visit);
107
115
  };
108
116
  visit(sourceFile);
@@ -121,13 +129,13 @@ export class ProjectKnowledgeGraph {
121
129
  // Basic Regex for generic symbols and imports
122
130
  if (ext === '.py') {
123
131
  // Python: import x, from x import y, def func, class Cls
124
- const importMatches = content.matchAll(/^(?:import|from)\s+([a-zA-Z0-9_.]+)/gm);
132
+ const importMatches = content.matchAll(/^\s*(?:import|from)\s+([a-zA-Z0-9_.]+)/gm);
125
133
  for (const match of importMatches)
126
134
  imports.push(match[1]);
127
- const funcMatches = content.matchAll(/^def\s+([a-zA-Z0-9_]+)/gm);
135
+ const funcMatches = content.matchAll(/^\s*def\s+([a-zA-Z0-9_]+)/gm);
128
136
  for (const match of funcMatches)
129
137
  symbols.push(`def:${match[1]}`);
130
- const classMatches = content.matchAll(/^class\s+([a-zA-Z0-9_]+)/gm);
138
+ const classMatches = content.matchAll(/^\s*class\s+([a-zA-Z0-9_]+)/gm);
131
139
  for (const match of classMatches)
132
140
  symbols.push(`class:${match[1]}`);
133
141
  }
@@ -136,7 +144,7 @@ export class ProjectKnowledgeGraph {
136
144
  const importMatches = content.matchAll(/import\s+"([^"]+)"/g);
137
145
  for (const match of importMatches)
138
146
  imports.push(match[1]);
139
- const funcMatches = content.matchAll(/^func\s+([a-zA-Z0-9_]+)/gm);
147
+ const funcMatches = content.matchAll(/^\s*func\s+([a-zA-Z0-9_]+)/gm);
140
148
  for (const match of funcMatches)
141
149
  symbols.push(`func:${match[1]}`);
142
150
  }
@@ -176,7 +184,7 @@ export class ProjectKnowledgeGraph {
176
184
  return absolutePath;
177
185
  }
178
186
  // 2. Try appending extensions
179
- const extensions = ['.ts', '.js', '.tsx', '.jsx', '.json', '/index.ts', '/index.js'];
187
+ const extensions = ['.ts', '.js', '.tsx', '.jsx', '.json', '.py', '.go', '.rs', '.java', '.c', '.cpp', '.h', '/index.ts', '/index.js'];
180
188
  for (const ext of extensions) {
181
189
  const p = absolutePath + ext;
182
190
  if (this.nodes.has(p)) {
@@ -76,4 +76,57 @@ describe('ProjectKnowledgeGraph', () => {
76
76
  const relationships = graph.getRelationships('/app/index.js');
77
77
  expect(relationships?.imports).toContain('Button.jsx');
78
78
  });
79
+ it('should handle circular dependencies', async () => {
80
+ const contentA = `import { b } from './b'; export const a = 1;`;
81
+ const contentB = `import { a } from './a'; export const b = 2;`;
82
+ fs.readdir.mockResolvedValue([
83
+ { name: 'a.ts', isDirectory: () => false },
84
+ { name: 'b.ts', isDirectory: () => false }
85
+ ]);
86
+ fs.readFile.mockImplementation(async (filePath) => {
87
+ if (filePath.endsWith('a.ts'))
88
+ return contentA;
89
+ if (filePath.endsWith('b.ts'))
90
+ return contentB;
91
+ return '';
92
+ });
93
+ await graph.build('/app');
94
+ const relA = graph.getRelationships('/app/a.ts');
95
+ const relB = graph.getRelationships('/app/b.ts');
96
+ expect(relA?.imports).toContain('b.ts');
97
+ expect(relA?.importedBy).toContain('b.ts');
98
+ expect(relB?.imports).toContain('a.ts');
99
+ expect(relB?.importedBy).toContain('a.ts');
100
+ });
101
+ it('should gracefully handle missing imports', async () => {
102
+ const contentA = `import { ghost } from './ghost';`;
103
+ fs.readdir.mockResolvedValue([
104
+ { name: 'a.ts', isDirectory: () => false }
105
+ ]);
106
+ fs.readFile.mockImplementation(async (filePath) => {
107
+ if (filePath.endsWith('a.ts'))
108
+ return contentA;
109
+ return '';
110
+ });
111
+ await graph.build('/app');
112
+ const relA = graph.getRelationships('/app/a.ts');
113
+ // ghost.ts doesn't exist, so imports should be empty (filtered out)
114
+ expect(relA?.imports).toHaveLength(0);
115
+ });
116
+ it('should ignore directory traversal attempts outside root', async () => {
117
+ // If we pretend root is /app, and we try to import ../outside
118
+ const contentA = `import { secret } from '../secret';`;
119
+ fs.readdir.mockResolvedValue([
120
+ { name: 'a.ts', isDirectory: () => false }
121
+ ]);
122
+ fs.readFile.mockImplementation(async (filePath) => {
123
+ if (filePath.endsWith('a.ts'))
124
+ return contentA;
125
+ return '';
126
+ });
127
+ await graph.build('/app');
128
+ const relA = graph.getRelationships('/app/a.ts');
129
+ // Should be empty because '../secret' is not in the scanned file list (nodes)
130
+ expect(relA?.imports).toHaveLength(0);
131
+ });
79
132
  });
package/dist/index.js CHANGED
@@ -4,12 +4,14 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
4
4
  import { SequentialThinkingServer } from './lib.js';
5
5
  import { ProjectKnowledgeGraph } from './graph.js';
6
6
  import { NotesManager } from './notes.js';
7
+ import { CodeDatabase } from './codestore.js';
7
8
  import { registerThinkingTools } from './tools/thinking.js';
8
9
  import { registerWebTools } from './tools/web.js';
9
10
  import { registerFileSystemTools } from './tools/filesystem.js';
10
11
  import { registerGraphTools } from './tools/graph.js';
11
12
  import { registerNoteTools } from './tools/notes.js';
12
13
  import { registerCodingTools } from './tools/coding.js';
14
+ import { registerCodeDbTools } from './tools/codestore.js';
13
15
  const server = new McpServer({
14
16
  name: "sequential-thinking-server",
15
17
  version: "2026.2.6",
@@ -17,6 +19,7 @@ const server = new McpServer({
17
19
  const thinkingServer = new SequentialThinkingServer(process.env.THOUGHTS_STORAGE_PATH || 'thoughts_history.json', parseInt(process.env.THOUGHT_DELAY_MS || '0', 10));
18
20
  const knowledgeGraph = new ProjectKnowledgeGraph();
19
21
  const notesManager = new NotesManager(process.env.NOTES_STORAGE_PATH || 'project_notes.json');
22
+ const codeDb = new CodeDatabase(process.env.CODE_DB_PATH || 'code_database.json');
20
23
  // Register tools
21
24
  registerThinkingTools(server, thinkingServer);
22
25
  registerWebTools(server);
@@ -24,6 +27,7 @@ registerFileSystemTools(server);
24
27
  registerGraphTools(server, knowledgeGraph);
25
28
  registerNoteTools(server, notesManager);
26
29
  registerCodingTools(server, knowledgeGraph);
30
+ registerCodeDbTools(server, codeDb);
27
31
  async function runServer() {
28
32
  const transport = new StdioServerTransport();
29
33
  await server.connect(transport);
@@ -0,0 +1,58 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ // We need to import the register functions to get the callbacks,
3
+ // but we can just use the classes directly for this integration logic test.
4
+ // Actually, testing the *interaction* via the tool layer is better.
5
+ import { registerThinkingTools } from './tools/thinking.js';
6
+ import { registerGraphTools } from './tools/graph.js';
7
+ import { registerNoteTools } from './tools/notes.js';
8
+ describe('Integration Workflow', () => {
9
+ let toolCallbacks = {};
10
+ const mockServer = {
11
+ tool: (name, desc, schema, cb) => {
12
+ toolCallbacks[name] = cb;
13
+ }
14
+ };
15
+ // Mocks
16
+ const mockThinking = { processThought: vi.fn(), clearHistory: vi.fn() };
17
+ const mockGraph = { build: vi.fn(), getRelationships: vi.fn() };
18
+ const mockNotes = { addNote: vi.fn() };
19
+ beforeEach(() => {
20
+ toolCallbacks = {};
21
+ registerThinkingTools(mockServer, mockThinking);
22
+ registerGraphTools(mockServer, mockGraph);
23
+ registerNoteTools(mockServer, mockNotes);
24
+ vi.clearAllMocks();
25
+ });
26
+ it('should support a full analysis workflow', async () => {
27
+ // Step 1: Start Thinking
28
+ mockThinking.processThought.mockResolvedValue({
29
+ content: [{ type: "text", text: "Thought processed" }]
30
+ });
31
+ await toolCallbacks['sequentialthinking']({
32
+ thought: "Analyze architecture",
33
+ thoughtNumber: 1,
34
+ totalThoughts: 5,
35
+ nextThoughtNeeded: true,
36
+ thoughtType: 'analysis'
37
+ });
38
+ expect(mockThinking.processThought).toHaveBeenCalledWith(expect.objectContaining({
39
+ thought: "Analyze architecture"
40
+ }));
41
+ // Step 2: Build Graph
42
+ mockGraph.build.mockResolvedValue({ nodeCount: 10, totalFiles: 20 });
43
+ await toolCallbacks['build_project_graph']({ path: '.' });
44
+ expect(mockGraph.build).toHaveBeenCalled();
45
+ // Step 3: Get Relationships
46
+ mockGraph.getRelationships.mockReturnValue({ imports: ['utils.ts'] });
47
+ const relResult = await toolCallbacks['get_file_relationships']({ filePath: 'src/index.ts' });
48
+ expect(JSON.parse(relResult.content[0].text).imports).toContain('utils.ts');
49
+ // Step 4: Add Note
50
+ mockNotes.addNote.mockResolvedValue({ id: '123', title: 'Arch Note' });
51
+ await toolCallbacks['manage_notes']({
52
+ action: 'add',
53
+ title: 'Architecture Review',
54
+ content: 'Found circular deps'
55
+ });
56
+ expect(mockNotes.addNote).toHaveBeenCalledWith('Architecture Review', 'Found circular deps', undefined, undefined, undefined);
57
+ });
58
+ });
package/dist/lib.js CHANGED
@@ -9,6 +9,7 @@ export class SequentialThinkingServer {
9
9
  storagePath;
10
10
  delayMs;
11
11
  isSaving = false;
12
+ hasPendingSave = false;
12
13
  constructor(storagePath = 'thoughts_history.json', delayMs = 0) {
13
14
  this.disableThoughtLogging = (process.env.DISABLE_THOUGHT_LOGGING || "").toLowerCase() === "true";
14
15
  this.storagePath = path.resolve(storagePath);
@@ -33,8 +34,7 @@ export class SequentialThinkingServer {
33
34
  }
34
35
  async saveHistory() {
35
36
  if (this.isSaving) {
36
- // Simple retry if already saving
37
- setTimeout(() => this.saveHistory(), 100);
37
+ this.hasPendingSave = true;
38
38
  return;
39
39
  }
40
40
  this.isSaving = true;
@@ -49,6 +49,10 @@ export class SequentialThinkingServer {
49
49
  }
50
50
  finally {
51
51
  this.isSaving = false;
52
+ if (this.hasPendingSave) {
53
+ this.hasPendingSave = false;
54
+ this.saveHistory();
55
+ }
52
56
  }
53
57
  }
54
58
  async clearHistory() {
@@ -70,9 +74,30 @@ export class SequentialThinkingServer {
70
74
  // Remove the range and insert summary
71
75
  const removedCount = endIndex - startIndex + 1;
72
76
  this.thoughtHistory.splice(startIndex - 1, removedCount, summaryThought);
73
- // Renumber subsequent thoughts
77
+ // Renumber subsequent thoughts and update references
78
+ const shiftAmount = removedCount - 1;
74
79
  for (let i = startIndex; i < this.thoughtHistory.length; i++) {
75
- this.thoughtHistory[i].thoughtNumber -= (removedCount - 1);
80
+ const t = this.thoughtHistory[i];
81
+ // Update own number
82
+ t.thoughtNumber -= shiftAmount;
83
+ // Update references (branchFromThought)
84
+ if (t.branchFromThought) {
85
+ if (t.branchFromThought > endIndex) {
86
+ t.branchFromThought -= shiftAmount;
87
+ }
88
+ else if (t.branchFromThought >= startIndex) {
89
+ t.branchFromThought = startIndex; // Point to summary
90
+ }
91
+ }
92
+ // Update references (revisesThought)
93
+ if (t.revisesThought) {
94
+ if (t.revisesThought > endIndex) {
95
+ t.revisesThought -= shiftAmount;
96
+ }
97
+ else if (t.revisesThought >= startIndex) {
98
+ t.revisesThought = startIndex; // Point to summary
99
+ }
100
+ }
76
101
  }
77
102
  // Rebuild branches (simplification: clear and let it rebuild if needed, or just clear)
78
103
  this.branches = {};