@felkot/think-mcp 1.1.0
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/LICENSE +21 -0
- package/README.md +165 -0
- package/dist/constants/index.d.ts +5 -0
- package/dist/constants/index.js +5 -0
- package/dist/constants/patterns.d.ts +8 -0
- package/dist/constants/patterns.js +34 -0
- package/dist/constants/prompts.d.ts +1 -0
- package/dist/constants/prompts.js +44 -0
- package/dist/constants/thresholds.d.ts +39 -0
- package/dist/constants/thresholds.js +54 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +711 -0
- package/dist/services/burst.service.d.ts +39 -0
- package/dist/services/burst.service.js +215 -0
- package/dist/services/coaching.service.d.ts +38 -0
- package/dist/services/coaching.service.js +258 -0
- package/dist/services/consolidate.service.d.ts +17 -0
- package/dist/services/consolidate.service.js +236 -0
- package/dist/services/context.service.d.ts +15 -0
- package/dist/services/context.service.js +113 -0
- package/dist/services/export.service.d.ts +33 -0
- package/dist/services/export.service.js +129 -0
- package/dist/services/insights.service.d.ts +103 -0
- package/dist/services/insights.service.js +216 -0
- package/dist/services/logic.service.d.ts +28 -0
- package/dist/services/logic.service.js +467 -0
- package/dist/services/nudge.service.d.ts +20 -0
- package/dist/services/nudge.service.js +114 -0
- package/dist/services/recall.service.d.ts +38 -0
- package/dist/services/recall.service.js +188 -0
- package/dist/services/stagnation.service.d.ts +14 -0
- package/dist/services/stagnation.service.js +48 -0
- package/dist/services/thinking.service.d.ts +263 -0
- package/dist/services/thinking.service.js +1048 -0
- package/dist/services/validation.service.d.ts +45 -0
- package/dist/services/validation.service.js +260 -0
- package/dist/services/visualization.service.d.ts +23 -0
- package/dist/services/visualization.service.js +211 -0
- package/dist/types/thought.types.d.ts +486 -0
- package/dist/types/thought.types.js +5 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.js +6 -0
- package/dist/utils/sensitive-data.d.ts +2 -0
- package/dist/utils/sensitive-data.js +28 -0
- package/dist/utils/storage-paths.d.ts +9 -0
- package/dist/utils/storage-paths.js +28 -0
- package/dist/utils/text-analysis.d.ts +30 -0
- package/dist/utils/text-analysis.js +92 -0
- package/package.json +66 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* InsightsService - Cross-session learning from winning paths
|
|
3
|
+
* Version 1.0.0
|
|
4
|
+
*
|
|
5
|
+
* Stores successful reasoning patterns for future recall.
|
|
6
|
+
* NO LLM, NO Vector DB - just JSON persistence + Fuse.js search.
|
|
7
|
+
*/
|
|
8
|
+
import { promises as fs } from 'fs';
|
|
9
|
+
import { dirname } from 'path';
|
|
10
|
+
import Fuse from 'fuse.js';
|
|
11
|
+
import { getThinkStoragePaths } from '../utils/storage-paths.js';
|
|
12
|
+
import { redactSensitiveContent } from '../utils/sensitive-data.js';
|
|
13
|
+
// Constants
|
|
14
|
+
const STORAGE_PATHS = getThinkStoragePaths(import.meta.url);
|
|
15
|
+
const INSIGHTS_FILE = STORAGE_PATHS.insightsFile;
|
|
16
|
+
const LEGACY_INSIGHTS_FILE = STORAGE_PATHS.legacyInsightsFile;
|
|
17
|
+
const INSIGHTS_DIR = dirname(INSIGHTS_FILE);
|
|
18
|
+
const MAX_INSIGHTS = 100; // FIFO limit to prevent bloat
|
|
19
|
+
const INSIGHTS_SEARCH_THRESHOLD = 0.4;
|
|
20
|
+
export class InsightsService {
|
|
21
|
+
data = null;
|
|
22
|
+
fuseIndex = null;
|
|
23
|
+
isDirty = false;
|
|
24
|
+
sanitizeText(value) {
|
|
25
|
+
if (!value)
|
|
26
|
+
return value;
|
|
27
|
+
return redactSensitiveContent(value).slice(0, 2000);
|
|
28
|
+
}
|
|
29
|
+
async loadFromFile(filePath) {
|
|
30
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
31
|
+
return JSON.parse(content);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Extract keywords from text for pattern tracking
|
|
35
|
+
*/
|
|
36
|
+
extractKeywords(text) {
|
|
37
|
+
const stopWords = new Set([
|
|
38
|
+
'the', 'a', 'an', 'is', 'are', 'was', 'were', 'be', 'been', 'being',
|
|
39
|
+
'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', 'could',
|
|
40
|
+
'should', 'may', 'might', 'must', 'shall', 'can', 'need', 'dare',
|
|
41
|
+
'to', 'of', 'in', 'for', 'on', 'with', 'at', 'by', 'from', 'as',
|
|
42
|
+
'into', 'through', 'during', 'before', 'after', 'above', 'below',
|
|
43
|
+
'and', 'but', 'or', 'nor', 'so', 'yet', 'both', 'either', 'neither',
|
|
44
|
+
'this', 'that', 'these', 'those', 'it', 'its', 'i', 'we', 'you', 'they',
|
|
45
|
+
]);
|
|
46
|
+
return text
|
|
47
|
+
.toLowerCase()
|
|
48
|
+
.replace(/[^a-z0-9\s-]/g, ' ')
|
|
49
|
+
.split(/\s+/)
|
|
50
|
+
.filter(word => word.length >= 3 && !stopWords.has(word))
|
|
51
|
+
.slice(0, 10); // Limit to 10 keywords per insight
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Load insights from file
|
|
55
|
+
*/
|
|
56
|
+
async load() {
|
|
57
|
+
try {
|
|
58
|
+
this.data = await this.loadFromFile(INSIGHTS_FILE);
|
|
59
|
+
this.rebuildIndex();
|
|
60
|
+
console.error(`📚 Loaded ${this.data.winningPaths.length} insights from ${this.data.totalSessions} sessions`);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
try {
|
|
64
|
+
this.data = await this.loadFromFile(LEGACY_INSIGHTS_FILE);
|
|
65
|
+
this.rebuildIndex();
|
|
66
|
+
console.error(`📚 Loaded legacy insights from ${LEGACY_INSIGHTS_FILE}`);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// File doesn't exist - initialize empty
|
|
70
|
+
this.data = {
|
|
71
|
+
winningPaths: [],
|
|
72
|
+
patterns: {},
|
|
73
|
+
totalSessions: 0,
|
|
74
|
+
lastUpdated: new Date().toISOString(),
|
|
75
|
+
};
|
|
76
|
+
console.error('📚 No insights file found, starting fresh');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Rebuild Fuse.js index for search
|
|
82
|
+
*/
|
|
83
|
+
rebuildIndex() {
|
|
84
|
+
if (!this.data)
|
|
85
|
+
return;
|
|
86
|
+
this.fuseIndex = new Fuse(this.data.winningPaths, {
|
|
87
|
+
keys: [
|
|
88
|
+
{ name: 'summary', weight: 0.5 },
|
|
89
|
+
{ name: 'goal', weight: 0.3 },
|
|
90
|
+
{ name: 'keywords', weight: 0.2 },
|
|
91
|
+
],
|
|
92
|
+
threshold: INSIGHTS_SEARCH_THRESHOLD,
|
|
93
|
+
includeScore: true,
|
|
94
|
+
ignoreLocation: true,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Save insights to file (atomic write)
|
|
99
|
+
*/
|
|
100
|
+
async save() {
|
|
101
|
+
if (!this.data || !this.isDirty)
|
|
102
|
+
return;
|
|
103
|
+
this.data.lastUpdated = new Date().toISOString();
|
|
104
|
+
const tempFile = `${INSIGHTS_FILE}.tmp`;
|
|
105
|
+
try {
|
|
106
|
+
await fs.mkdir(INSIGHTS_DIR, { recursive: true });
|
|
107
|
+
await fs.writeFile(tempFile, JSON.stringify(this.data, null, 2), 'utf-8');
|
|
108
|
+
await fs.rename(tempFile, INSIGHTS_FILE);
|
|
109
|
+
this.isDirty = false;
|
|
110
|
+
console.error(`💾 Saved ${this.data.winningPaths.length} insights`);
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
console.error('Failed to save insights:', error);
|
|
114
|
+
try {
|
|
115
|
+
await fs.unlink(tempFile);
|
|
116
|
+
}
|
|
117
|
+
catch { /* ignore */ }
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Save a winning path as an insight
|
|
122
|
+
*/
|
|
123
|
+
async saveWinningPath(input) {
|
|
124
|
+
if (!this.data)
|
|
125
|
+
await this.load();
|
|
126
|
+
const { path, summary, goal, avgConfidence, sessionLength } = input;
|
|
127
|
+
const safeSummary = this.sanitizeText(summary) ?? '';
|
|
128
|
+
const safeGoal = this.sanitizeText(goal);
|
|
129
|
+
// Extract keywords from summary and goal
|
|
130
|
+
const keywords = [
|
|
131
|
+
...this.extractKeywords(safeSummary),
|
|
132
|
+
...(safeGoal ? this.extractKeywords(safeGoal) : []),
|
|
133
|
+
];
|
|
134
|
+
// Create record
|
|
135
|
+
const record = {
|
|
136
|
+
path,
|
|
137
|
+
summary: safeSummary,
|
|
138
|
+
goal: safeGoal,
|
|
139
|
+
keywords: [...new Set(keywords)], // Dedupe
|
|
140
|
+
timestamp: new Date().toISOString(),
|
|
141
|
+
avgConfidence,
|
|
142
|
+
sessionLength,
|
|
143
|
+
};
|
|
144
|
+
// Add to winningPaths (FIFO)
|
|
145
|
+
this.data.winningPaths.push(record);
|
|
146
|
+
if (this.data.winningPaths.length > MAX_INSIGHTS) {
|
|
147
|
+
this.data.winningPaths.shift();
|
|
148
|
+
}
|
|
149
|
+
// Update pattern counts
|
|
150
|
+
for (const keyword of record.keywords) {
|
|
151
|
+
this.data.patterns[keyword] = (this.data.patterns[keyword] || 0) + 1;
|
|
152
|
+
}
|
|
153
|
+
this.data.totalSessions++;
|
|
154
|
+
this.isDirty = true;
|
|
155
|
+
// Rebuild index and save
|
|
156
|
+
this.rebuildIndex();
|
|
157
|
+
await this.save();
|
|
158
|
+
console.error(`📝 Saved insight: "${summary.substring(0, 50)}..." (${record.keywords.length} keywords)`);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Search insights by query
|
|
162
|
+
*/
|
|
163
|
+
async search(query, limit = 3) {
|
|
164
|
+
if (!this.data)
|
|
165
|
+
await this.load();
|
|
166
|
+
if (!this.fuseIndex || this.data.winningPaths.length === 0) {
|
|
167
|
+
return {
|
|
168
|
+
matches: [],
|
|
169
|
+
totalInsights: 0,
|
|
170
|
+
topPatterns: [],
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
// Search using Fuse.js
|
|
174
|
+
const results = this.fuseIndex.search(query, { limit });
|
|
175
|
+
const matches = results.map(r => ({
|
|
176
|
+
insight: r.item,
|
|
177
|
+
relevance: r.score ?? 1,
|
|
178
|
+
}));
|
|
179
|
+
// Get top patterns
|
|
180
|
+
const topPatterns = Object.entries(this.data.patterns)
|
|
181
|
+
.sort((a, b) => b[1] - a[1])
|
|
182
|
+
.slice(0, 5)
|
|
183
|
+
.map(([keyword, count]) => ({ keyword, count }));
|
|
184
|
+
return {
|
|
185
|
+
matches,
|
|
186
|
+
totalInsights: this.data.winningPaths.length,
|
|
187
|
+
topPatterns,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Get statistics about stored insights
|
|
192
|
+
*/
|
|
193
|
+
async getStats() {
|
|
194
|
+
if (!this.data)
|
|
195
|
+
await this.load();
|
|
196
|
+
const paths = this.data.winningPaths;
|
|
197
|
+
const avgSessionLength = paths.length > 0
|
|
198
|
+
? paths.reduce((sum, p) => sum + p.sessionLength, 0) / paths.length
|
|
199
|
+
: 0;
|
|
200
|
+
const withConfidence = paths.filter(p => p.avgConfidence !== undefined);
|
|
201
|
+
const avgConfidence = withConfidence.length > 0
|
|
202
|
+
? withConfidence.reduce((sum, p) => sum + (p.avgConfidence ?? 0), 0) / withConfidence.length
|
|
203
|
+
: 0;
|
|
204
|
+
const topPatterns = Object.entries(this.data.patterns)
|
|
205
|
+
.sort((a, b) => b[1] - a[1])
|
|
206
|
+
.slice(0, 10)
|
|
207
|
+
.map(([keyword, count]) => ({ keyword, count }));
|
|
208
|
+
return {
|
|
209
|
+
totalInsights: paths.length,
|
|
210
|
+
totalSessions: this.data.totalSessions,
|
|
211
|
+
topPatterns,
|
|
212
|
+
avgSessionLength: Math.round(avgSessionLength * 10) / 10,
|
|
213
|
+
avgConfidence: Math.round(avgConfidence * 10) / 10,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LogicService - Pure Thinking Methodology Generator
|
|
3
|
+
* Version 5.0.0 - Methodology Edition
|
|
4
|
+
*
|
|
5
|
+
* PURPOSE: Teach AI HOW to think about code analysis, not WHAT to find.
|
|
6
|
+
* Output is a thinking ALGORITHM that AI applies to specific code.
|
|
7
|
+
*
|
|
8
|
+
* Philosophy: "Teach to fish, don't give fish"
|
|
9
|
+
* - Not: "Is there N+1 query?" (specific question)
|
|
10
|
+
* - But: "For each data access, ask: is this inside a loop?" (thinking pattern)
|
|
11
|
+
*/
|
|
12
|
+
import type { LogicAnalysisInput, LogicAnalysisResult } from '../types/thought.types.js';
|
|
13
|
+
export declare class LogicService {
|
|
14
|
+
/**
|
|
15
|
+
* Generate thinking methodology for the target
|
|
16
|
+
*/
|
|
17
|
+
analyze(input: LogicAnalysisInput): LogicAnalysisResult;
|
|
18
|
+
private buildMethodology;
|
|
19
|
+
private buildExecutionOrder;
|
|
20
|
+
private buildPriorityPlan;
|
|
21
|
+
private buildVerifyChecklist;
|
|
22
|
+
private buildPreMortem;
|
|
23
|
+
private errorResult;
|
|
24
|
+
/**
|
|
25
|
+
* Format as markdown for AI consumption
|
|
26
|
+
*/
|
|
27
|
+
formatAsMarkdown(result: LogicAnalysisResult): string;
|
|
28
|
+
}
|