@feelingmindful/thinking-graph 1.2.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.
@@ -0,0 +1,45 @@
1
+ import { z } from 'zod';
2
+ import type { ThinkingGraph } from '../engine/graph.js';
3
+ /**
4
+ * Backward compatibility shim for the original `sequentialthinking` tool.
5
+ * Maps the old API (thought, thoughtNumber, totalThoughts, nextThoughtNeeded)
6
+ * to the new `think` tool, preserving the original response format.
7
+ */
8
+ export declare const sequentialSchema: z.ZodObject<{
9
+ thought: z.ZodString;
10
+ thoughtNumber: z.ZodNumber;
11
+ totalThoughts: z.ZodNumber;
12
+ nextThoughtNeeded: z.ZodBoolean;
13
+ isRevision: z.ZodOptional<z.ZodBoolean>;
14
+ revisesThought: z.ZodOptional<z.ZodNumber>;
15
+ branchFromThought: z.ZodOptional<z.ZodNumber>;
16
+ branchId: z.ZodOptional<z.ZodString>;
17
+ needsMoreThoughts: z.ZodOptional<z.ZodBoolean>;
18
+ }, "strip", z.ZodTypeAny, {
19
+ thought: string;
20
+ thoughtNumber: number;
21
+ totalThoughts: number;
22
+ nextThoughtNeeded: boolean;
23
+ branchId?: string | undefined;
24
+ isRevision?: boolean | undefined;
25
+ revisesThought?: number | undefined;
26
+ branchFromThought?: number | undefined;
27
+ needsMoreThoughts?: boolean | undefined;
28
+ }, {
29
+ thought: string;
30
+ thoughtNumber: number;
31
+ totalThoughts: number;
32
+ nextThoughtNeeded: boolean;
33
+ branchId?: string | undefined;
34
+ isRevision?: boolean | undefined;
35
+ revisesThought?: number | undefined;
36
+ branchFromThought?: number | undefined;
37
+ needsMoreThoughts?: boolean | undefined;
38
+ }>;
39
+ export type SequentialInput = z.infer<typeof sequentialSchema>;
40
+ export declare function compatHandler(graph: ThinkingGraph, input: SequentialInput): Promise<{
41
+ content: {
42
+ type: "text";
43
+ text: string;
44
+ }[];
45
+ }>;
@@ -0,0 +1,46 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Backward compatibility shim for the original `sequentialthinking` tool.
4
+ * Maps the old API (thought, thoughtNumber, totalThoughts, nextThoughtNeeded)
5
+ * to the new `think` tool, preserving the original response format.
6
+ */
7
+ export const sequentialSchema = z.object({
8
+ thought: z.string().describe('The current thinking step'),
9
+ thoughtNumber: z.number().int().min(1).describe('Current thought number'),
10
+ totalThoughts: z.number().int().min(1).describe('Estimated total thoughts'),
11
+ nextThoughtNeeded: z.boolean().describe('Whether another step is needed'),
12
+ isRevision: z.boolean().optional(),
13
+ revisesThought: z.number().int().min(1).optional(),
14
+ branchFromThought: z.number().int().min(1).optional(),
15
+ branchId: z.string().optional(),
16
+ needsMoreThoughts: z.boolean().optional(),
17
+ });
18
+ export async function compatHandler(graph, input) {
19
+ const session = await graph.getCurrentSession();
20
+ const node = await graph.addNode({
21
+ type: 'thought',
22
+ content: input.thought,
23
+ sessionId: session.id,
24
+ metadata: {},
25
+ thoughtNumber: input.thoughtNumber,
26
+ totalThoughts: input.totalThoughts,
27
+ branchId: input.branchId,
28
+ isRevision: input.isRevision,
29
+ revisesThought: input.revisesThought,
30
+ });
31
+ await graph.touchSession(session.id);
32
+ const stats = await graph.storage.getStats();
33
+ // Return exactly the same format as the original server
34
+ return {
35
+ content: [{
36
+ type: 'text',
37
+ text: JSON.stringify({
38
+ thoughtNumber: node.thoughtNumber,
39
+ totalThoughts: node.totalThoughts,
40
+ nextThoughtNeeded: input.nextThoughtNeeded,
41
+ branches: [],
42
+ thoughtHistoryLength: stats.totalNodes,
43
+ }),
44
+ }],
45
+ };
46
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Content deduplication using Jaccard similarity on word tokens.
3
+ */
4
+ export declare function tokenize(content: string): string[];
5
+ export declare function similarity(a: string, b: string): number;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Content deduplication using Jaccard similarity on word tokens.
3
+ */
4
+ export function tokenize(content) {
5
+ return content.toLowerCase().split(/\W+/).filter(Boolean);
6
+ }
7
+ export function similarity(a, b) {
8
+ const tokensA = new Set(tokenize(a));
9
+ const tokensB = new Set(tokenize(b));
10
+ if (tokensA.size === 0 && tokensB.size === 0)
11
+ return 1.0;
12
+ if (tokensA.size === 0 || tokensB.size === 0)
13
+ return 0.0;
14
+ const intersection = [...tokensA].filter(t => tokensB.has(t)).length;
15
+ const union = new Set([...tokensA, ...tokensB]).size;
16
+ return intersection / union;
17
+ }
@@ -0,0 +1,49 @@
1
+ import type { StorageAdapter } from '../storage/adapter.js';
2
+ import type { Node, Edge, Session, NodeType, EdgeType, NodeQuery, PaginatedResult, NodeWithEdges, ExportOpts, GraphExport, HealthReport, SkillRegistryEntry } from './types.js';
3
+ export declare class ThinkingGraph {
4
+ storage: StorageAdapter;
5
+ private currentSessionId;
6
+ constructor(storage: StorageAdapter);
7
+ addNode(input: Omit<Node, 'id' | 'createdAt' | 'updatedAt'> & {
8
+ id?: string;
9
+ }): Promise<Node>;
10
+ getNode(id: string): Promise<Node | null>;
11
+ findNodes(query: NodeQuery): Promise<PaginatedResult<Node>>;
12
+ searchNodes(text: string, limit?: number): Promise<Node[]>;
13
+ findSimilar(content: string, type: NodeType, projectId?: string, threshold?: number): Promise<Node | null>;
14
+ addEdge(input: Omit<Edge, 'id' | 'createdAt' | 'weight'> & {
15
+ id?: string;
16
+ weight?: number;
17
+ }): Promise<Edge & {
18
+ created: boolean;
19
+ }>;
20
+ getEdges(nodeId: string, direction?: 'outgoing' | 'incoming' | 'both', type?: EdgeType): Promise<Edge[]>;
21
+ traverse(startId: string, edgeType: EdgeType, depth?: number): Promise<Node[]>;
22
+ findContradictions(sessionId?: string): Promise<{
23
+ a: Node;
24
+ b: Node;
25
+ reasoning?: string;
26
+ }[]>;
27
+ findViolations(projectId?: string): Promise<{
28
+ node: Node;
29
+ principle: Node;
30
+ }[]>;
31
+ getNodeWithEdges(nodeId: string): Promise<NodeWithEdges | null>;
32
+ createSession(opts?: Partial<Session>): Promise<Session>;
33
+ getCurrentSession(): Promise<Session>;
34
+ touchSession(id: string): Promise<void>;
35
+ getSkillForDebt(debtNode: Node): Promise<SkillRegistryEntry | null>;
36
+ getSkillsForArea(area: string, platform?: string): Promise<SkillRegistryEntry[]>;
37
+ getProjectHealth(projectId: string): Promise<HealthReport>;
38
+ getTechDebtSummary(projectId: string): Promise<{
39
+ total: number;
40
+ bySeverity: Record<string, number>;
41
+ }>;
42
+ getDecisionLog(projectId?: string): Promise<Node[]>;
43
+ exportGraph(opts?: ExportOpts): Promise<GraphExport>;
44
+ exportSummary(opts?: ExportOpts): Promise<string>;
45
+ seed(): Promise<{
46
+ principles: number;
47
+ skills: number;
48
+ }>;
49
+ }
@@ -0,0 +1,250 @@
1
+ import { v4 as uuid } from 'uuid';
2
+ import { similarity } from './dedup.js';
3
+ import { seedAll } from './seed.js';
4
+ export class ThinkingGraph {
5
+ storage;
6
+ currentSessionId = null;
7
+ constructor(storage) {
8
+ this.storage = storage;
9
+ }
10
+ // ─── Node operations ─────────────────────────────────
11
+ async addNode(input) {
12
+ const now = new Date().toISOString();
13
+ const node = {
14
+ id: input.id ?? uuid(),
15
+ type: input.type,
16
+ content: input.content,
17
+ sessionId: input.sessionId,
18
+ projectId: input.projectId,
19
+ metadata: input.metadata ?? {},
20
+ createdAt: now,
21
+ updatedAt: now,
22
+ thoughtNumber: input.thoughtNumber,
23
+ totalThoughts: input.totalThoughts,
24
+ branchId: input.branchId,
25
+ isRevision: input.isRevision,
26
+ revisesThought: input.revisesThought,
27
+ };
28
+ // Auto-adjust totalThoughts if thoughtNumber exceeds it
29
+ if (node.thoughtNumber && node.totalThoughts && node.thoughtNumber > node.totalThoughts) {
30
+ node.totalThoughts = node.thoughtNumber;
31
+ }
32
+ await this.storage.insertNode(node);
33
+ return node;
34
+ }
35
+ async getNode(id) {
36
+ return this.storage.getNode(id);
37
+ }
38
+ async findNodes(query) {
39
+ return this.storage.queryNodes(query);
40
+ }
41
+ async searchNodes(text, limit = 20) {
42
+ return this.storage.searchContent(text, limit);
43
+ }
44
+ async findSimilar(content, type, projectId, threshold = 0.9) {
45
+ // Get candidates of the same type
46
+ const candidates = await this.storage.queryNodes({
47
+ type,
48
+ projectId,
49
+ limit: 100,
50
+ });
51
+ for (const node of candidates.items) {
52
+ if (similarity(content, node.content) >= threshold) {
53
+ return node;
54
+ }
55
+ }
56
+ return null;
57
+ }
58
+ // ─── Edge operations ─────────────────────────────────
59
+ async addEdge(input) {
60
+ const edge = {
61
+ id: input.id ?? uuid(),
62
+ sourceId: input.sourceId,
63
+ targetId: input.targetId,
64
+ type: input.type,
65
+ weight: input.weight ?? 1.0,
66
+ reasoning: input.reasoning,
67
+ createdAt: new Date().toISOString(),
68
+ };
69
+ const created = await this.storage.insertEdge(edge);
70
+ return { ...edge, created };
71
+ }
72
+ async getEdges(nodeId, direction = 'both', type) {
73
+ const results = [];
74
+ if (direction === 'outgoing' || direction === 'both') {
75
+ results.push(...await this.storage.getEdgesFrom(nodeId, type));
76
+ }
77
+ if (direction === 'incoming' || direction === 'both') {
78
+ results.push(...await this.storage.getEdgesTo(nodeId, type));
79
+ }
80
+ return results;
81
+ }
82
+ async traverse(startId, edgeType, depth = 1) {
83
+ return this.storage.traverseEdges(startId, edgeType, depth);
84
+ }
85
+ async findContradictions(sessionId) {
86
+ const query = sessionId ? { sessionId } : {};
87
+ const allNodes = await this.storage.queryNodes({ ...query, limit: 1000 });
88
+ const results = [];
89
+ for (const node of allNodes.items) {
90
+ const edges = await this.storage.getEdgesFrom(node.id, 'contradicts');
91
+ for (const edge of edges) {
92
+ const target = await this.storage.getNode(edge.targetId);
93
+ if (target) {
94
+ results.push({ a: node, b: target, reasoning: edge.reasoning });
95
+ }
96
+ }
97
+ }
98
+ return results;
99
+ }
100
+ async findViolations(projectId) {
101
+ const query = projectId ? { projectId } : {};
102
+ const allNodes = await this.storage.queryNodes({ ...query, limit: 1000 });
103
+ const results = [];
104
+ for (const node of allNodes.items) {
105
+ const edges = await this.storage.getEdgesFrom(node.id, 'violates');
106
+ for (const edge of edges) {
107
+ const principle = await this.storage.getNode(edge.targetId);
108
+ if (principle && principle.type === 'principle') {
109
+ results.push({ node, principle });
110
+ }
111
+ }
112
+ }
113
+ return results;
114
+ }
115
+ // ─── Node with edges (for recall responses) ──────────
116
+ async getNodeWithEdges(nodeId) {
117
+ const node = await this.storage.getNode(nodeId);
118
+ if (!node)
119
+ return null;
120
+ const outgoing = await this.storage.getEdgesFrom(nodeId);
121
+ const incoming = await this.storage.getEdgesTo(nodeId);
122
+ const edges = [];
123
+ for (const e of outgoing) {
124
+ const target = await this.storage.getNode(e.targetId);
125
+ edges.push({
126
+ type: e.type,
127
+ direction: 'outgoing',
128
+ targetId: e.targetId,
129
+ targetContent: target?.content.slice(0, 100) ?? '',
130
+ });
131
+ }
132
+ for (const e of incoming) {
133
+ const source = await this.storage.getNode(e.sourceId);
134
+ edges.push({
135
+ type: e.type,
136
+ direction: 'incoming',
137
+ targetId: e.sourceId,
138
+ targetContent: source?.content.slice(0, 100) ?? '',
139
+ });
140
+ }
141
+ return { ...node, edges };
142
+ }
143
+ // ─── Session operations ──────────────────────────────
144
+ async createSession(opts) {
145
+ const now = new Date().toISOString();
146
+ const session = {
147
+ id: opts?.id ?? uuid(),
148
+ projectId: opts?.projectId,
149
+ projectPath: opts?.projectPath,
150
+ description: opts?.description,
151
+ startedAt: now,
152
+ lastActiveAt: now,
153
+ };
154
+ await this.storage.insertSession(session);
155
+ this.currentSessionId = session.id;
156
+ return session;
157
+ }
158
+ async getCurrentSession() {
159
+ if (this.currentSessionId) {
160
+ const session = await this.storage.getSession(this.currentSessionId);
161
+ if (session)
162
+ return session;
163
+ }
164
+ // Create a new session
165
+ return this.createSession();
166
+ }
167
+ async touchSession(id) {
168
+ await this.storage.updateSession(id, { lastActiveAt: new Date().toISOString() });
169
+ }
170
+ // ─── Skill registry ──────────────────────────────────
171
+ async getSkillForDebt(debtNode) {
172
+ // Look at the debt's metadata for area, then find a skill that addresses that area
173
+ const area = debtNode.metadata?.area;
174
+ if (!area)
175
+ return null;
176
+ const skills = await this.storage.querySkills({});
177
+ for (const skill of skills) {
178
+ if (skill.areas.includes(area) && skill.detects?.includes('needs-work')) {
179
+ return skill;
180
+ }
181
+ }
182
+ return null;
183
+ }
184
+ async getSkillsForArea(area, platform) {
185
+ const allSkills = await this.storage.querySkills(platform ? { platform } : {});
186
+ return allSkills.filter(s => s.areas.includes(area));
187
+ }
188
+ // ─── Analysis ─────────────────────────────────────────
189
+ async getProjectHealth(projectId) {
190
+ const stats = await this.storage.getStats();
191
+ const debtResult = await this.storage.queryNodes({ type: 'tech_debt', projectId, limit: 10 });
192
+ const violations = await this.findViolations(projectId);
193
+ const decisions = await this.storage.queryNodes({ type: 'decision', projectId, limit: 10 });
194
+ const recent = await this.storage.queryNodes({ projectId, limit: 10 });
195
+ return {
196
+ projectId,
197
+ stats,
198
+ topDebt: debtResult.items,
199
+ violations,
200
+ openDecisions: decisions.items,
201
+ recentActivity: recent.items,
202
+ };
203
+ }
204
+ async getTechDebtSummary(projectId) {
205
+ const result = await this.storage.queryNodes({ type: 'tech_debt', projectId, limit: 1000 });
206
+ const bySeverity = {};
207
+ for (const node of result.items) {
208
+ const sev = node.metadata?.severity ?? 'unknown';
209
+ bySeverity[sev] = (bySeverity[sev] ?? 0) + 1;
210
+ }
211
+ return { total: result.totalCount, bySeverity };
212
+ }
213
+ async getDecisionLog(projectId) {
214
+ const result = await this.storage.queryNodes({ type: 'decision', projectId, limit: 100 });
215
+ return result.items;
216
+ }
217
+ // ─── Export ───────────────────────────────────────────
218
+ async exportGraph(opts) {
219
+ return this.storage.exportAll(opts);
220
+ }
221
+ async exportSummary(opts) {
222
+ const stats = await this.storage.getStats();
223
+ const lines = [
224
+ '# Thinking Graph Summary',
225
+ '',
226
+ `**Nodes:** ${stats.totalNodes}`,
227
+ `**Edges:** ${stats.totalEdges}`,
228
+ '',
229
+ '## Nodes by Type',
230
+ ];
231
+ for (const [type, count] of Object.entries(stats.nodesByType).sort((a, b) => b[1] - a[1])) {
232
+ lines.push(`- ${type}: ${count}`);
233
+ }
234
+ lines.push('', '## Edges by Type');
235
+ for (const [type, count] of Object.entries(stats.edgesByType).sort((a, b) => b[1] - a[1])) {
236
+ lines.push(`- ${type}: ${count}`);
237
+ }
238
+ if (stats.techDebtCount > 0) {
239
+ lines.push('', `## Tech Debt: ${stats.techDebtCount} items`);
240
+ }
241
+ if (stats.principleViolations > 0) {
242
+ lines.push(`## Principle Violations: ${stats.principleViolations}`);
243
+ }
244
+ return lines.join('\n');
245
+ }
246
+ // ─── Seeding ──────────────────────────────────────────
247
+ async seed() {
248
+ return seedAll(this.storage);
249
+ }
250
+ }
@@ -0,0 +1,7 @@
1
+ import type { StorageAdapter } from '../storage/adapter.js';
2
+ export declare function seedPrinciples(storage: StorageAdapter): Promise<number>;
3
+ export declare function seedSkillRegistry(storage: StorageAdapter): Promise<number>;
4
+ export declare function seedAll(storage: StorageAdapter): Promise<{
5
+ principles: number;
6
+ skills: number;
7
+ }>;
@@ -0,0 +1,43 @@
1
+ import { readFileSync } from 'fs';
2
+ import { join, dirname } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ const __dirname = dirname(fileURLToPath(import.meta.url));
5
+ const SEEDS_DIR = join(__dirname, '../../seeds');
6
+ export async function seedPrinciples(storage) {
7
+ const raw = JSON.parse(readFileSync(join(SEEDS_DIR, 'principles.json'), 'utf-8'));
8
+ const now = new Date().toISOString();
9
+ let count = 0;
10
+ for (const p of raw) {
11
+ const existing = await storage.getNode(p.id);
12
+ if (!existing) {
13
+ await storage.insertNode({
14
+ id: p.id,
15
+ type: p.type,
16
+ content: p.content,
17
+ sessionId: 'seed',
18
+ metadata: p.metadata ?? {},
19
+ createdAt: now,
20
+ updatedAt: now,
21
+ });
22
+ count++;
23
+ }
24
+ }
25
+ return count;
26
+ }
27
+ export async function seedSkillRegistry(storage) {
28
+ const skills = JSON.parse(readFileSync(join(SEEDS_DIR, 'skill-registry.json'), 'utf-8'));
29
+ let count = 0;
30
+ for (const s of skills) {
31
+ const existing = await storage.querySkills({ id: s.id });
32
+ if (existing.length === 0) {
33
+ await storage.insertSkill(s);
34
+ count++;
35
+ }
36
+ }
37
+ return count;
38
+ }
39
+ export async function seedAll(storage) {
40
+ const principles = await seedPrinciples(storage);
41
+ const skills = await seedSkillRegistry(storage);
42
+ return { principles, skills };
43
+ }
@@ -0,0 +1,114 @@
1
+ export declare const NODE_TYPES: readonly ["thought", "decision", "insight", "code_fact", "assumption", "detection", "tech_debt", "principle", "pattern", "skill_result", "research"];
2
+ export type NodeType = (typeof NODE_TYPES)[number];
3
+ export declare function isNodeType(v: string): v is NodeType;
4
+ export declare const EDGE_TYPES: readonly ["depends_on", "contradicts", "supports", "refines", "supersedes", "similar_to", "located_in", "violates", "addresses", "detected_by", "invoked_by"];
5
+ export type EdgeType = (typeof EDGE_TYPES)[number];
6
+ export declare function isEdgeType(v: string): v is EdgeType;
7
+ export declare const GLOBAL_NODE_TYPES: NodeType[];
8
+ export interface Node {
9
+ id: string;
10
+ type: NodeType;
11
+ content: string;
12
+ sessionId: string;
13
+ projectId?: string;
14
+ metadata: Record<string, unknown>;
15
+ createdAt: string;
16
+ updatedAt: string;
17
+ thoughtNumber?: number;
18
+ totalThoughts?: number;
19
+ branchId?: string;
20
+ isRevision?: boolean;
21
+ revisesThought?: number;
22
+ }
23
+ export interface Edge {
24
+ id: string;
25
+ sourceId: string;
26
+ targetId: string;
27
+ type: EdgeType;
28
+ weight: number;
29
+ reasoning?: string;
30
+ createdAt: string;
31
+ }
32
+ export interface Session {
33
+ id: string;
34
+ projectId?: string;
35
+ projectPath?: string;
36
+ description?: string;
37
+ startedAt: string;
38
+ lastActiveAt: string;
39
+ }
40
+ export interface SkillRegistryEntry {
41
+ id: string;
42
+ pluginName: string;
43
+ skillName: string;
44
+ verb?: string;
45
+ invocation: string;
46
+ areas: string[];
47
+ detects: string[];
48
+ produces: string[];
49
+ invokes: string[];
50
+ platform?: string;
51
+ }
52
+ export interface NodeQuery {
53
+ query?: string;
54
+ type?: NodeType | NodeType[];
55
+ sessionId?: string;
56
+ projectId?: string;
57
+ crossProject?: boolean;
58
+ relatedTo?: string;
59
+ edgeType?: EdgeType | EdgeType[];
60
+ direction?: 'outgoing' | 'incoming' | 'both';
61
+ depth?: number;
62
+ since?: string;
63
+ metadata?: Record<string, unknown>;
64
+ limit?: number;
65
+ offset?: number;
66
+ }
67
+ export interface PaginatedResult<T> {
68
+ items: T[];
69
+ totalCount: number;
70
+ hasMore: boolean;
71
+ }
72
+ export interface NodeWithEdges extends Node {
73
+ edges: {
74
+ type: EdgeType;
75
+ direction: 'outgoing' | 'incoming';
76
+ targetId: string;
77
+ targetContent: string;
78
+ }[];
79
+ }
80
+ export interface ExportOpts {
81
+ format: 'json' | 'summary';
82
+ sessionId?: string;
83
+ projectId?: string;
84
+ type?: NodeType | NodeType[];
85
+ includeEdges?: boolean;
86
+ outputPath?: string;
87
+ }
88
+ export interface GraphExport {
89
+ exportedAt: string;
90
+ nodeCount: number;
91
+ edgeCount: number;
92
+ sessions: Session[];
93
+ nodes: Node[];
94
+ edges: Edge[];
95
+ }
96
+ export interface GraphStats {
97
+ totalNodes: number;
98
+ totalEdges: number;
99
+ nodesByType: Record<string, number>;
100
+ edgesByType: Record<string, number>;
101
+ techDebtCount: number;
102
+ principleViolations: number;
103
+ }
104
+ export interface HealthReport {
105
+ projectId: string;
106
+ stats: GraphStats;
107
+ topDebt: Node[];
108
+ violations: {
109
+ node: Node;
110
+ principle: Node;
111
+ }[];
112
+ openDecisions: Node[];
113
+ recentActivity: Node[];
114
+ }
@@ -0,0 +1,18 @@
1
+ export const NODE_TYPES = [
2
+ 'thought', 'decision', 'insight', 'code_fact', 'assumption',
3
+ 'detection', 'tech_debt', 'principle', 'pattern',
4
+ 'skill_result', 'research',
5
+ ];
6
+ export function isNodeType(v) {
7
+ return NODE_TYPES.includes(v);
8
+ }
9
+ export const EDGE_TYPES = [
10
+ 'depends_on', 'contradicts', 'supports', 'refines',
11
+ 'supersedes', 'similar_to', 'located_in', 'violates',
12
+ 'addresses', 'detected_by', 'invoked_by',
13
+ ];
14
+ export function isEdgeType(v) {
15
+ return EDGE_TYPES.includes(v);
16
+ }
17
+ // Node types that go to the global DB (cross-project)
18
+ export const GLOBAL_NODE_TYPES = ['insight', 'principle', 'pattern'];
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { ThinkingGraph } from './engine/graph.js';
5
+ import { SQLiteAdapter } from './storage/sqlite.js';
6
+ import { InMemoryAdapter } from './storage/memory.js';
7
+ import { thinkSchema, thinkHandler } from './tools/think.js';
8
+ import { relateSchema, relateHandler } from './tools/relate.js';
9
+ import { recallSchema, recallHandler } from './tools/recall.js';
10
+ import { learnSchema, learnHandler } from './tools/learn.js';
11
+ import { exportSchema, exportHandler } from './tools/export.js';
12
+ import { sequentialSchema, compatHandler } from './compat/sequential.js';
13
+ // ─── Storage setup ───────────────────────────────────────
14
+ const memoryOnly = process.env.THINKING_GRAPH_MEMORY_ONLY === 'true';
15
+ const storage = memoryOnly
16
+ ? new InMemoryAdapter()
17
+ : new SQLiteAdapter({
18
+ dbPath: process.env.THINKING_GRAPH_PROJECT_DB || '.premium/thinking.db',
19
+ });
20
+ // ─── Graph engine ────────────────────────────────────────
21
+ const graph = new ThinkingGraph(storage);
22
+ // ─── MCP Server ──────────────────────────────────────────
23
+ const server = new McpServer({
24
+ name: 'thinking-graph',
25
+ version: '1.0.0',
26
+ });
27
+ // Register new tools
28
+ server.tool('think', 'Record a reasoning step with optional typing, relationships, and metadata. Enhanced version of sequential thinking.', thinkSchema.shape, async (input) => thinkHandler(graph, input));
29
+ server.tool('relate', 'Create a typed, directional relationship between two nodes in the thinking graph.', relateSchema.shape, async (input) => relateHandler(graph, input));
30
+ server.tool('recall', 'Query the thinking graph — search by text, filter by type, traverse relationships, or search across projects.', recallSchema.shape, async (input) => recallHandler(graph, input));
31
+ server.tool('learn', 'Store durable knowledge — code facts, tech debt, insights, principles. Deduplicates similar content.', learnSchema.shape, async (input) => learnHandler(graph, input));
32
+ server.tool('export', 'Export the thinking graph as JSON or a human-readable markdown summary.', exportSchema.shape, async (input) => exportHandler(graph, input));
33
+ // Backward compat: register the original tool name
34
+ server.tool('sequentialthinking', 'Record a sequential thinking step (backward compatible with @modelcontextprotocol/server-sequential-thinking).', sequentialSchema.shape, async (input) => compatHandler(graph, input));
35
+ // ─── Startup ─────────────────────────────────────────────
36
+ async function main() {
37
+ await storage.initialize();
38
+ await graph.seed();
39
+ const transport = new StdioServerTransport();
40
+ await server.connect(transport);
41
+ if (!process.env.DISABLE_THOUGHT_LOGGING) {
42
+ console.error('thinking-graph MCP server started');
43
+ console.error(` Storage: ${memoryOnly ? 'in-memory' : 'SQLite'}`);
44
+ if (!memoryOnly) {
45
+ console.error(` DB: ${process.env.THINKING_GRAPH_PROJECT_DB || '.premium/thinking.db'}`);
46
+ }
47
+ }
48
+ }
49
+ main().catch((err) => {
50
+ console.error('Failed to start thinking-graph:', err);
51
+ process.exit(1);
52
+ });
@@ -0,0 +1,20 @@
1
+ import type { Node, Edge, Session, SkillRegistryEntry, NodeQuery, PaginatedResult, EdgeType, ExportOpts, GraphExport, GraphStats } from '../engine/types.js';
2
+ export interface StorageAdapter {
3
+ initialize(): Promise<void>;
4
+ close(): Promise<void>;
5
+ insertNode(node: Node): Promise<void>;
6
+ getNode(id: string): Promise<Node | null>;
7
+ queryNodes(query: NodeQuery): Promise<PaginatedResult<Node>>;
8
+ searchContent(text: string, limit?: number): Promise<Node[]>;
9
+ insertEdge(edge: Edge): Promise<boolean>;
10
+ getEdgesFrom(nodeId: string, type?: EdgeType): Promise<Edge[]>;
11
+ getEdgesTo(nodeId: string, type?: EdgeType): Promise<Edge[]>;
12
+ traverseEdges(startId: string, type: EdgeType, maxDepth: number): Promise<Node[]>;
13
+ insertSession(session: Session): Promise<void>;
14
+ getSession(id: string): Promise<Session | null>;
15
+ updateSession(id: string, fields: Partial<Session>): Promise<void>;
16
+ insertSkill(entry: SkillRegistryEntry): Promise<void>;
17
+ querySkills(filter: Partial<SkillRegistryEntry>): Promise<SkillRegistryEntry[]>;
18
+ exportAll(opts?: ExportOpts): Promise<GraphExport>;
19
+ getStats(): Promise<GraphStats>;
20
+ }