@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.
- package/dist/compat/sequential.d.ts +45 -0
- package/dist/compat/sequential.js +46 -0
- package/dist/engine/dedup.d.ts +5 -0
- package/dist/engine/dedup.js +17 -0
- package/dist/engine/graph.d.ts +49 -0
- package/dist/engine/graph.js +250 -0
- package/dist/engine/seed.d.ts +7 -0
- package/dist/engine/seed.js +43 -0
- package/dist/engine/types.d.ts +114 -0
- package/dist/engine/types.js +18 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +52 -0
- package/dist/storage/adapter.d.ts +20 -0
- package/dist/storage/adapter.js +1 -0
- package/dist/storage/memory.d.ts +25 -0
- package/dist/storage/memory.js +171 -0
- package/dist/storage/sqlite.d.ts +30 -0
- package/dist/storage/sqlite.js +310 -0
- package/dist/tools/export.d.ts +31 -0
- package/dist/tools/export.js +42 -0
- package/dist/tools/learn.d.ts +66 -0
- package/dist/tools/learn.js +91 -0
- package/dist/tools/recall.d.ts +52 -0
- package/dist/tools/recall.js +63 -0
- package/dist/tools/relate.d.ts +31 -0
- package/dist/tools/relate.js +58 -0
- package/dist/tools/think.d.ts +75 -0
- package/dist/tools/think.js +69 -0
- package/migrations/001-initial.sql +108 -0
- package/package.json +44 -0
- package/seeds/principles.json +92 -0
- package/seeds/skill-registry.json +187 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { ThinkingGraph } from '../engine/graph.js';
|
|
3
|
+
export declare const learnSchema: z.ZodObject<{
|
|
4
|
+
content: z.ZodString;
|
|
5
|
+
type: z.ZodEnum<["thought", "decision", "insight", "code_fact", "assumption", "detection", "tech_debt", "principle", "pattern", "skill_result", "research"]>;
|
|
6
|
+
projectId: z.ZodOptional<z.ZodString>;
|
|
7
|
+
filePath: z.ZodOptional<z.ZodString>;
|
|
8
|
+
lineRange: z.ZodOptional<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>;
|
|
9
|
+
severity: z.ZodOptional<z.ZodEnum<["critical", "high", "medium", "low"]>>;
|
|
10
|
+
effort: z.ZodOptional<z.ZodString>;
|
|
11
|
+
impact: z.ZodOptional<z.ZodString>;
|
|
12
|
+
relates: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
13
|
+
type: z.ZodEnum<["depends_on", "contradicts", "supports", "refines", "supersedes", "similar_to", "located_in", "violates", "addresses", "detected_by", "invoked_by"]>;
|
|
14
|
+
targetId: z.ZodString;
|
|
15
|
+
reasoning: z.ZodOptional<z.ZodString>;
|
|
16
|
+
}, "strip", z.ZodTypeAny, {
|
|
17
|
+
type: "depends_on" | "contradicts" | "supports" | "refines" | "supersedes" | "similar_to" | "located_in" | "violates" | "addresses" | "detected_by" | "invoked_by";
|
|
18
|
+
targetId: string;
|
|
19
|
+
reasoning?: string | undefined;
|
|
20
|
+
}, {
|
|
21
|
+
type: "depends_on" | "contradicts" | "supports" | "refines" | "supersedes" | "similar_to" | "located_in" | "violates" | "addresses" | "detected_by" | "invoked_by";
|
|
22
|
+
targetId: string;
|
|
23
|
+
reasoning?: string | undefined;
|
|
24
|
+
}>, "many">>;
|
|
25
|
+
violatedBy: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
26
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
27
|
+
}, "strip", z.ZodTypeAny, {
|
|
28
|
+
type: "thought" | "decision" | "insight" | "code_fact" | "assumption" | "detection" | "tech_debt" | "principle" | "pattern" | "skill_result" | "research";
|
|
29
|
+
content: string;
|
|
30
|
+
projectId?: string | undefined;
|
|
31
|
+
metadata?: Record<string, unknown> | undefined;
|
|
32
|
+
severity?: "critical" | "high" | "medium" | "low" | undefined;
|
|
33
|
+
relates?: {
|
|
34
|
+
type: "depends_on" | "contradicts" | "supports" | "refines" | "supersedes" | "similar_to" | "located_in" | "violates" | "addresses" | "detected_by" | "invoked_by";
|
|
35
|
+
targetId: string;
|
|
36
|
+
reasoning?: string | undefined;
|
|
37
|
+
}[] | undefined;
|
|
38
|
+
filePath?: string | undefined;
|
|
39
|
+
lineRange?: [number, number] | undefined;
|
|
40
|
+
effort?: string | undefined;
|
|
41
|
+
impact?: string | undefined;
|
|
42
|
+
violatedBy?: string[] | undefined;
|
|
43
|
+
}, {
|
|
44
|
+
type: "thought" | "decision" | "insight" | "code_fact" | "assumption" | "detection" | "tech_debt" | "principle" | "pattern" | "skill_result" | "research";
|
|
45
|
+
content: string;
|
|
46
|
+
projectId?: string | undefined;
|
|
47
|
+
metadata?: Record<string, unknown> | undefined;
|
|
48
|
+
severity?: "critical" | "high" | "medium" | "low" | undefined;
|
|
49
|
+
relates?: {
|
|
50
|
+
type: "depends_on" | "contradicts" | "supports" | "refines" | "supersedes" | "similar_to" | "located_in" | "violates" | "addresses" | "detected_by" | "invoked_by";
|
|
51
|
+
targetId: string;
|
|
52
|
+
reasoning?: string | undefined;
|
|
53
|
+
}[] | undefined;
|
|
54
|
+
filePath?: string | undefined;
|
|
55
|
+
lineRange?: [number, number] | undefined;
|
|
56
|
+
effort?: string | undefined;
|
|
57
|
+
impact?: string | undefined;
|
|
58
|
+
violatedBy?: string[] | undefined;
|
|
59
|
+
}>;
|
|
60
|
+
export type LearnInput = z.infer<typeof learnSchema>;
|
|
61
|
+
export declare function learnHandler(graph: ThinkingGraph, input: LearnInput): Promise<{
|
|
62
|
+
content: {
|
|
63
|
+
type: "text";
|
|
64
|
+
text: string;
|
|
65
|
+
}[];
|
|
66
|
+
}>;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { NODE_TYPES, EDGE_TYPES } from '../engine/types.js';
|
|
3
|
+
export const learnSchema = z.object({
|
|
4
|
+
content: z.string().describe('What was learned'),
|
|
5
|
+
type: z.enum(NODE_TYPES).describe('Node type'),
|
|
6
|
+
projectId: z.string().optional(),
|
|
7
|
+
filePath: z.string().optional().describe('For code_facts'),
|
|
8
|
+
lineRange: z.tuple([z.number(), z.number()]).optional().describe('Line range [start, end]'),
|
|
9
|
+
severity: z.enum(['critical', 'high', 'medium', 'low']).optional().describe('For tech_debt'),
|
|
10
|
+
effort: z.string().optional().describe('Estimated fix effort'),
|
|
11
|
+
impact: z.string().optional().describe('What breaks if unfixed'),
|
|
12
|
+
relates: z.array(z.object({
|
|
13
|
+
type: z.enum(EDGE_TYPES),
|
|
14
|
+
targetId: z.string(),
|
|
15
|
+
reasoning: z.string().optional(),
|
|
16
|
+
})).optional(),
|
|
17
|
+
violatedBy: z.array(z.string()).optional().describe('Node IDs violating this'),
|
|
18
|
+
metadata: z.record(z.unknown()).optional(),
|
|
19
|
+
});
|
|
20
|
+
export async function learnHandler(graph, input) {
|
|
21
|
+
// Check for duplicate
|
|
22
|
+
const existing = await graph.findSimilar(input.content, input.type, input.projectId);
|
|
23
|
+
if (existing) {
|
|
24
|
+
return {
|
|
25
|
+
content: [{
|
|
26
|
+
type: 'text',
|
|
27
|
+
text: JSON.stringify({
|
|
28
|
+
nodeId: existing.id,
|
|
29
|
+
type: existing.type,
|
|
30
|
+
relatedCount: 0,
|
|
31
|
+
duplicateOf: existing.id,
|
|
32
|
+
}),
|
|
33
|
+
}],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
// Build metadata from structured fields
|
|
37
|
+
const metadata = { ...input.metadata };
|
|
38
|
+
if (input.filePath)
|
|
39
|
+
metadata.filePath = input.filePath;
|
|
40
|
+
if (input.lineRange)
|
|
41
|
+
metadata.lineRange = input.lineRange;
|
|
42
|
+
if (input.severity)
|
|
43
|
+
metadata.severity = input.severity;
|
|
44
|
+
if (input.effort)
|
|
45
|
+
metadata.effort = input.effort;
|
|
46
|
+
if (input.impact)
|
|
47
|
+
metadata.impact = input.impact;
|
|
48
|
+
const session = await graph.getCurrentSession();
|
|
49
|
+
const node = await graph.addNode({
|
|
50
|
+
type: input.type,
|
|
51
|
+
content: input.content,
|
|
52
|
+
sessionId: session.id,
|
|
53
|
+
projectId: input.projectId,
|
|
54
|
+
metadata,
|
|
55
|
+
});
|
|
56
|
+
// Create relationships
|
|
57
|
+
let relatedCount = 0;
|
|
58
|
+
if (input.relates) {
|
|
59
|
+
for (const rel of input.relates) {
|
|
60
|
+
await graph.addEdge({
|
|
61
|
+
sourceId: node.id,
|
|
62
|
+
targetId: rel.targetId,
|
|
63
|
+
type: rel.type,
|
|
64
|
+
reasoning: rel.reasoning,
|
|
65
|
+
});
|
|
66
|
+
relatedCount++;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Create violation edges
|
|
70
|
+
if (input.violatedBy) {
|
|
71
|
+
for (const violatorId of input.violatedBy) {
|
|
72
|
+
await graph.addEdge({
|
|
73
|
+
sourceId: violatorId,
|
|
74
|
+
targetId: node.id,
|
|
75
|
+
type: 'violates',
|
|
76
|
+
});
|
|
77
|
+
relatedCount++;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
content: [{
|
|
82
|
+
type: 'text',
|
|
83
|
+
text: JSON.stringify({
|
|
84
|
+
nodeId: node.id,
|
|
85
|
+
type: node.type,
|
|
86
|
+
relatedCount,
|
|
87
|
+
duplicateOf: null,
|
|
88
|
+
}),
|
|
89
|
+
}],
|
|
90
|
+
};
|
|
91
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { ThinkingGraph } from '../engine/graph.js';
|
|
3
|
+
export declare const recallSchema: z.ZodObject<{
|
|
4
|
+
query: z.ZodOptional<z.ZodString>;
|
|
5
|
+
type: z.ZodOptional<z.ZodUnion<[z.ZodEnum<["thought", "decision", "insight", "code_fact", "assumption", "detection", "tech_debt", "principle", "pattern", "skill_result", "research"]>, z.ZodArray<z.ZodEnum<["thought", "decision", "insight", "code_fact", "assumption", "detection", "tech_debt", "principle", "pattern", "skill_result", "research"]>, "many">]>>;
|
|
6
|
+
sessionId: z.ZodOptional<z.ZodString>;
|
|
7
|
+
projectId: z.ZodOptional<z.ZodString>;
|
|
8
|
+
crossProject: z.ZodOptional<z.ZodBoolean>;
|
|
9
|
+
relatedTo: z.ZodOptional<z.ZodString>;
|
|
10
|
+
edgeType: z.ZodOptional<z.ZodUnion<[z.ZodEnum<["depends_on", "contradicts", "supports", "refines", "supersedes", "similar_to", "located_in", "violates", "addresses", "detected_by", "invoked_by"]>, z.ZodArray<z.ZodEnum<["depends_on", "contradicts", "supports", "refines", "supersedes", "similar_to", "located_in", "violates", "addresses", "detected_by", "invoked_by"]>, "many">]>>;
|
|
11
|
+
direction: z.ZodOptional<z.ZodEnum<["outgoing", "incoming", "both"]>>;
|
|
12
|
+
depth: z.ZodOptional<z.ZodNumber>;
|
|
13
|
+
since: z.ZodOptional<z.ZodString>;
|
|
14
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
15
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
16
|
+
offset: z.ZodOptional<z.ZodNumber>;
|
|
17
|
+
}, "strip", z.ZodTypeAny, {
|
|
18
|
+
type?: "thought" | "decision" | "insight" | "code_fact" | "assumption" | "detection" | "tech_debt" | "principle" | "pattern" | "skill_result" | "research" | ("thought" | "decision" | "insight" | "code_fact" | "assumption" | "detection" | "tech_debt" | "principle" | "pattern" | "skill_result" | "research")[] | undefined;
|
|
19
|
+
sessionId?: string | undefined;
|
|
20
|
+
projectId?: string | undefined;
|
|
21
|
+
metadata?: Record<string, unknown> | undefined;
|
|
22
|
+
query?: string | undefined;
|
|
23
|
+
crossProject?: boolean | undefined;
|
|
24
|
+
relatedTo?: string | undefined;
|
|
25
|
+
edgeType?: "depends_on" | "contradicts" | "supports" | "refines" | "supersedes" | "similar_to" | "located_in" | "violates" | "addresses" | "detected_by" | "invoked_by" | ("depends_on" | "contradicts" | "supports" | "refines" | "supersedes" | "similar_to" | "located_in" | "violates" | "addresses" | "detected_by" | "invoked_by")[] | undefined;
|
|
26
|
+
direction?: "outgoing" | "incoming" | "both" | undefined;
|
|
27
|
+
depth?: number | undefined;
|
|
28
|
+
since?: string | undefined;
|
|
29
|
+
limit?: number | undefined;
|
|
30
|
+
offset?: number | undefined;
|
|
31
|
+
}, {
|
|
32
|
+
type?: "thought" | "decision" | "insight" | "code_fact" | "assumption" | "detection" | "tech_debt" | "principle" | "pattern" | "skill_result" | "research" | ("thought" | "decision" | "insight" | "code_fact" | "assumption" | "detection" | "tech_debt" | "principle" | "pattern" | "skill_result" | "research")[] | undefined;
|
|
33
|
+
sessionId?: string | undefined;
|
|
34
|
+
projectId?: string | undefined;
|
|
35
|
+
metadata?: Record<string, unknown> | undefined;
|
|
36
|
+
query?: string | undefined;
|
|
37
|
+
crossProject?: boolean | undefined;
|
|
38
|
+
relatedTo?: string | undefined;
|
|
39
|
+
edgeType?: "depends_on" | "contradicts" | "supports" | "refines" | "supersedes" | "similar_to" | "located_in" | "violates" | "addresses" | "detected_by" | "invoked_by" | ("depends_on" | "contradicts" | "supports" | "refines" | "supersedes" | "similar_to" | "located_in" | "violates" | "addresses" | "detected_by" | "invoked_by")[] | undefined;
|
|
40
|
+
direction?: "outgoing" | "incoming" | "both" | undefined;
|
|
41
|
+
depth?: number | undefined;
|
|
42
|
+
since?: string | undefined;
|
|
43
|
+
limit?: number | undefined;
|
|
44
|
+
offset?: number | undefined;
|
|
45
|
+
}>;
|
|
46
|
+
export type RecallInput = z.infer<typeof recallSchema>;
|
|
47
|
+
export declare function recallHandler(graph: ThinkingGraph, input: RecallInput): Promise<{
|
|
48
|
+
content: {
|
|
49
|
+
type: "text";
|
|
50
|
+
text: string;
|
|
51
|
+
}[];
|
|
52
|
+
}>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { NODE_TYPES, EDGE_TYPES } from '../engine/types.js';
|
|
3
|
+
export const recallSchema = z.object({
|
|
4
|
+
query: z.string().optional().describe('Full-text search'),
|
|
5
|
+
type: z.union([z.enum(NODE_TYPES), z.array(z.enum(NODE_TYPES))]).optional(),
|
|
6
|
+
sessionId: z.string().optional(),
|
|
7
|
+
projectId: z.string().optional(),
|
|
8
|
+
crossProject: z.boolean().optional().describe('Search across all projects'),
|
|
9
|
+
relatedTo: z.string().optional().describe('Find nodes connected to this ID'),
|
|
10
|
+
edgeType: z.union([z.enum(EDGE_TYPES), z.array(z.enum(EDGE_TYPES))]).optional(),
|
|
11
|
+
direction: z.enum(['outgoing', 'incoming', 'both']).optional(),
|
|
12
|
+
depth: z.number().int().min(1).optional().describe('Traversal depth'),
|
|
13
|
+
since: z.string().optional().describe('ISO timestamp'),
|
|
14
|
+
metadata: z.record(z.unknown()).optional(),
|
|
15
|
+
limit: z.number().int().min(1).max(100).optional(),
|
|
16
|
+
offset: z.number().int().min(0).optional(),
|
|
17
|
+
});
|
|
18
|
+
export async function recallHandler(graph, input) {
|
|
19
|
+
// If relatedTo is specified, use graph traversal
|
|
20
|
+
if (input.relatedTo) {
|
|
21
|
+
const edgeType = input.edgeType
|
|
22
|
+
? (Array.isArray(input.edgeType) ? input.edgeType[0] : input.edgeType)
|
|
23
|
+
: 'depends_on';
|
|
24
|
+
const depth = input.depth ?? 1;
|
|
25
|
+
const nodes = await graph.traverse(input.relatedTo, edgeType, depth);
|
|
26
|
+
// Enrich with edge previews
|
|
27
|
+
const enriched = await Promise.all(nodes.slice(0, input.limit ?? 20).map(n => graph.getNodeWithEdges(n.id)));
|
|
28
|
+
return {
|
|
29
|
+
content: [{
|
|
30
|
+
type: 'text',
|
|
31
|
+
text: JSON.stringify({
|
|
32
|
+
nodes: enriched.filter(Boolean),
|
|
33
|
+
totalCount: nodes.length,
|
|
34
|
+
hasMore: nodes.length > (input.limit ?? 20),
|
|
35
|
+
}),
|
|
36
|
+
}],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
// Standard query
|
|
40
|
+
const result = await graph.findNodes({
|
|
41
|
+
query: input.query,
|
|
42
|
+
type: input.type,
|
|
43
|
+
sessionId: input.sessionId,
|
|
44
|
+
projectId: input.projectId,
|
|
45
|
+
crossProject: input.crossProject,
|
|
46
|
+
since: input.since,
|
|
47
|
+
metadata: input.metadata,
|
|
48
|
+
limit: input.limit,
|
|
49
|
+
offset: input.offset,
|
|
50
|
+
});
|
|
51
|
+
// Enrich each node with 1-depth edge preview
|
|
52
|
+
const enriched = await Promise.all(result.items.map(n => graph.getNodeWithEdges(n.id)));
|
|
53
|
+
return {
|
|
54
|
+
content: [{
|
|
55
|
+
type: 'text',
|
|
56
|
+
text: JSON.stringify({
|
|
57
|
+
nodes: enriched.filter(Boolean),
|
|
58
|
+
totalCount: result.totalCount,
|
|
59
|
+
hasMore: result.hasMore,
|
|
60
|
+
}),
|
|
61
|
+
}],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { ThinkingGraph } from '../engine/graph.js';
|
|
3
|
+
export declare const relateSchema: z.ZodObject<{
|
|
4
|
+
sourceId: z.ZodString;
|
|
5
|
+
targetId: z.ZodString;
|
|
6
|
+
type: z.ZodEnum<["depends_on", "contradicts", "supports", "refines", "supersedes", "similar_to", "located_in", "violates", "addresses", "detected_by", "invoked_by"]>;
|
|
7
|
+
weight: z.ZodOptional<z.ZodNumber>;
|
|
8
|
+
reasoning: z.ZodOptional<z.ZodString>;
|
|
9
|
+
bidirectional: z.ZodOptional<z.ZodBoolean>;
|
|
10
|
+
}, "strip", z.ZodTypeAny, {
|
|
11
|
+
type: "depends_on" | "contradicts" | "supports" | "refines" | "supersedes" | "similar_to" | "located_in" | "violates" | "addresses" | "detected_by" | "invoked_by";
|
|
12
|
+
sourceId: string;
|
|
13
|
+
targetId: string;
|
|
14
|
+
weight?: number | undefined;
|
|
15
|
+
reasoning?: string | undefined;
|
|
16
|
+
bidirectional?: boolean | undefined;
|
|
17
|
+
}, {
|
|
18
|
+
type: "depends_on" | "contradicts" | "supports" | "refines" | "supersedes" | "similar_to" | "located_in" | "violates" | "addresses" | "detected_by" | "invoked_by";
|
|
19
|
+
sourceId: string;
|
|
20
|
+
targetId: string;
|
|
21
|
+
weight?: number | undefined;
|
|
22
|
+
reasoning?: string | undefined;
|
|
23
|
+
bidirectional?: boolean | undefined;
|
|
24
|
+
}>;
|
|
25
|
+
export type RelateInput = z.infer<typeof relateSchema>;
|
|
26
|
+
export declare function relateHandler(graph: ThinkingGraph, input: RelateInput): Promise<{
|
|
27
|
+
content: {
|
|
28
|
+
type: "text";
|
|
29
|
+
text: string;
|
|
30
|
+
}[];
|
|
31
|
+
}>;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { EDGE_TYPES } from '../engine/types.js';
|
|
3
|
+
export const relateSchema = z.object({
|
|
4
|
+
sourceId: z.string().describe('Node ID (prefix with "?" for content search)'),
|
|
5
|
+
targetId: z.string().describe('Node ID, content search, or principle name'),
|
|
6
|
+
type: z.enum(EDGE_TYPES).describe('Relationship type'),
|
|
7
|
+
weight: z.number().min(0).max(1).optional().describe('Confidence 0-1'),
|
|
8
|
+
reasoning: z.string().optional().describe('Why this relationship exists'),
|
|
9
|
+
bidirectional: z.boolean().optional().describe('Create reverse edge too'),
|
|
10
|
+
});
|
|
11
|
+
async function resolveId(graph, id) {
|
|
12
|
+
if (id.startsWith('?')) {
|
|
13
|
+
const query = id.slice(1);
|
|
14
|
+
// Try principle name first (e.g., "?orthogonality")
|
|
15
|
+
const principleId = `principle-${query.toLowerCase().replace(/\s+/g, '-')}`;
|
|
16
|
+
const principle = await graph.getNode(principleId);
|
|
17
|
+
if (principle)
|
|
18
|
+
return principleId;
|
|
19
|
+
// Fall back to content search
|
|
20
|
+
const results = await graph.searchNodes(query, 1);
|
|
21
|
+
if (results.length > 0)
|
|
22
|
+
return results[0].id;
|
|
23
|
+
throw new Error(`No node found matching: ${query}`);
|
|
24
|
+
}
|
|
25
|
+
return id;
|
|
26
|
+
}
|
|
27
|
+
export async function relateHandler(graph, input) {
|
|
28
|
+
const sourceId = await resolveId(graph, input.sourceId);
|
|
29
|
+
const targetId = await resolveId(graph, input.targetId);
|
|
30
|
+
const result = await graph.addEdge({
|
|
31
|
+
sourceId,
|
|
32
|
+
targetId,
|
|
33
|
+
type: input.type,
|
|
34
|
+
weight: input.weight,
|
|
35
|
+
reasoning: input.reasoning,
|
|
36
|
+
});
|
|
37
|
+
if (input.bidirectional) {
|
|
38
|
+
await graph.addEdge({
|
|
39
|
+
sourceId: targetId,
|
|
40
|
+
targetId: sourceId,
|
|
41
|
+
type: input.type,
|
|
42
|
+
weight: input.weight,
|
|
43
|
+
reasoning: input.reasoning,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
content: [{
|
|
48
|
+
type: 'text',
|
|
49
|
+
text: JSON.stringify({
|
|
50
|
+
edgeId: result.id,
|
|
51
|
+
sourceId,
|
|
52
|
+
targetId,
|
|
53
|
+
type: input.type,
|
|
54
|
+
created: result.created,
|
|
55
|
+
}),
|
|
56
|
+
}],
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { ThinkingGraph } from '../engine/graph.js';
|
|
3
|
+
export declare const thinkSchema: z.ZodObject<{
|
|
4
|
+
thought: z.ZodString;
|
|
5
|
+
type: z.ZodDefault<z.ZodEnum<["thought", "decision", "insight", "code_fact", "assumption", "detection", "tech_debt", "principle", "pattern", "skill_result", "research"]>>;
|
|
6
|
+
thoughtNumber: z.ZodNumber;
|
|
7
|
+
totalThoughts: z.ZodNumber;
|
|
8
|
+
nextThoughtNeeded: z.ZodBoolean;
|
|
9
|
+
sessionId: z.ZodOptional<z.ZodString>;
|
|
10
|
+
projectId: z.ZodOptional<z.ZodString>;
|
|
11
|
+
relates: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
12
|
+
type: z.ZodEnum<["depends_on", "contradicts", "supports", "refines", "supersedes", "similar_to", "located_in", "violates", "addresses", "detected_by", "invoked_by"]>;
|
|
13
|
+
targetId: z.ZodString;
|
|
14
|
+
reasoning: z.ZodOptional<z.ZodString>;
|
|
15
|
+
}, "strip", z.ZodTypeAny, {
|
|
16
|
+
type: "depends_on" | "contradicts" | "supports" | "refines" | "supersedes" | "similar_to" | "located_in" | "violates" | "addresses" | "detected_by" | "invoked_by";
|
|
17
|
+
targetId: string;
|
|
18
|
+
reasoning?: string | undefined;
|
|
19
|
+
}, {
|
|
20
|
+
type: "depends_on" | "contradicts" | "supports" | "refines" | "supersedes" | "similar_to" | "located_in" | "violates" | "addresses" | "detected_by" | "invoked_by";
|
|
21
|
+
targetId: string;
|
|
22
|
+
reasoning?: string | undefined;
|
|
23
|
+
}>, "many">>;
|
|
24
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
25
|
+
isRevision: z.ZodOptional<z.ZodBoolean>;
|
|
26
|
+
revisesThought: z.ZodOptional<z.ZodNumber>;
|
|
27
|
+
branchFromThought: z.ZodOptional<z.ZodNumber>;
|
|
28
|
+
branchId: z.ZodOptional<z.ZodString>;
|
|
29
|
+
needsMoreThoughts: z.ZodOptional<z.ZodBoolean>;
|
|
30
|
+
}, "strip", z.ZodTypeAny, {
|
|
31
|
+
thought: string;
|
|
32
|
+
type: "thought" | "decision" | "insight" | "code_fact" | "assumption" | "detection" | "tech_debt" | "principle" | "pattern" | "skill_result" | "research";
|
|
33
|
+
thoughtNumber: number;
|
|
34
|
+
totalThoughts: number;
|
|
35
|
+
nextThoughtNeeded: boolean;
|
|
36
|
+
sessionId?: string | undefined;
|
|
37
|
+
projectId?: string | undefined;
|
|
38
|
+
metadata?: Record<string, unknown> | undefined;
|
|
39
|
+
branchId?: string | undefined;
|
|
40
|
+
isRevision?: boolean | undefined;
|
|
41
|
+
revisesThought?: number | undefined;
|
|
42
|
+
relates?: {
|
|
43
|
+
type: "depends_on" | "contradicts" | "supports" | "refines" | "supersedes" | "similar_to" | "located_in" | "violates" | "addresses" | "detected_by" | "invoked_by";
|
|
44
|
+
targetId: string;
|
|
45
|
+
reasoning?: string | undefined;
|
|
46
|
+
}[] | undefined;
|
|
47
|
+
branchFromThought?: number | undefined;
|
|
48
|
+
needsMoreThoughts?: boolean | undefined;
|
|
49
|
+
}, {
|
|
50
|
+
thought: string;
|
|
51
|
+
thoughtNumber: number;
|
|
52
|
+
totalThoughts: number;
|
|
53
|
+
nextThoughtNeeded: boolean;
|
|
54
|
+
type?: "thought" | "decision" | "insight" | "code_fact" | "assumption" | "detection" | "tech_debt" | "principle" | "pattern" | "skill_result" | "research" | undefined;
|
|
55
|
+
sessionId?: string | undefined;
|
|
56
|
+
projectId?: string | undefined;
|
|
57
|
+
metadata?: Record<string, unknown> | undefined;
|
|
58
|
+
branchId?: string | undefined;
|
|
59
|
+
isRevision?: boolean | undefined;
|
|
60
|
+
revisesThought?: number | undefined;
|
|
61
|
+
relates?: {
|
|
62
|
+
type: "depends_on" | "contradicts" | "supports" | "refines" | "supersedes" | "similar_to" | "located_in" | "violates" | "addresses" | "detected_by" | "invoked_by";
|
|
63
|
+
targetId: string;
|
|
64
|
+
reasoning?: string | undefined;
|
|
65
|
+
}[] | undefined;
|
|
66
|
+
branchFromThought?: number | undefined;
|
|
67
|
+
needsMoreThoughts?: boolean | undefined;
|
|
68
|
+
}>;
|
|
69
|
+
export type ThinkInput = z.infer<typeof thinkSchema>;
|
|
70
|
+
export declare function thinkHandler(graph: ThinkingGraph, input: ThinkInput): Promise<{
|
|
71
|
+
content: {
|
|
72
|
+
type: "text";
|
|
73
|
+
text: string;
|
|
74
|
+
}[];
|
|
75
|
+
}>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { NODE_TYPES, EDGE_TYPES } from '../engine/types.js';
|
|
3
|
+
export const thinkSchema = z.object({
|
|
4
|
+
thought: z.string().describe('The reasoning content'),
|
|
5
|
+
type: z.enum(NODE_TYPES).default('thought').describe('Node type'),
|
|
6
|
+
thoughtNumber: z.number().int().min(1).describe('Current thought number'),
|
|
7
|
+
totalThoughts: z.number().int().min(1).describe('Estimated total thoughts'),
|
|
8
|
+
nextThoughtNeeded: z.boolean().describe('Whether another step is needed'),
|
|
9
|
+
sessionId: z.string().optional().describe('Session ID (auto-generated if omitted)'),
|
|
10
|
+
projectId: z.string().optional().describe('Project ID (detected from cwd if omitted)'),
|
|
11
|
+
relates: z.array(z.object({
|
|
12
|
+
type: z.enum(EDGE_TYPES),
|
|
13
|
+
targetId: z.string().describe('Node ID or "previous"'),
|
|
14
|
+
reasoning: z.string().optional(),
|
|
15
|
+
})).optional().describe('Inline relationships'),
|
|
16
|
+
metadata: z.record(z.unknown()).optional().describe('Flexible metadata'),
|
|
17
|
+
// Backward compat
|
|
18
|
+
isRevision: z.boolean().optional(),
|
|
19
|
+
revisesThought: z.number().int().min(1).optional(),
|
|
20
|
+
branchFromThought: z.number().int().min(1).optional(),
|
|
21
|
+
branchId: z.string().optional(),
|
|
22
|
+
needsMoreThoughts: z.boolean().optional(),
|
|
23
|
+
});
|
|
24
|
+
export async function thinkHandler(graph, input) {
|
|
25
|
+
const session = input.sessionId
|
|
26
|
+
? (await graph.storage.getSession(input.sessionId)) ?? await graph.createSession({ id: input.sessionId })
|
|
27
|
+
: await graph.getCurrentSession();
|
|
28
|
+
const node = await graph.addNode({
|
|
29
|
+
type: input.type,
|
|
30
|
+
content: input.thought,
|
|
31
|
+
sessionId: session.id,
|
|
32
|
+
projectId: input.projectId,
|
|
33
|
+
metadata: input.metadata ?? {},
|
|
34
|
+
thoughtNumber: input.thoughtNumber,
|
|
35
|
+
totalThoughts: input.totalThoughts,
|
|
36
|
+
branchId: input.branchId,
|
|
37
|
+
isRevision: input.isRevision,
|
|
38
|
+
revisesThought: input.revisesThought,
|
|
39
|
+
});
|
|
40
|
+
await graph.touchSession(session.id);
|
|
41
|
+
// Create inline relationships
|
|
42
|
+
let relatedCount = 0;
|
|
43
|
+
if (input.relates) {
|
|
44
|
+
for (const rel of input.relates) {
|
|
45
|
+
await graph.addEdge({
|
|
46
|
+
sourceId: node.id,
|
|
47
|
+
targetId: rel.targetId,
|
|
48
|
+
type: rel.type,
|
|
49
|
+
reasoning: rel.reasoning,
|
|
50
|
+
});
|
|
51
|
+
relatedCount++;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const stats = await graph.storage.getStats();
|
|
55
|
+
return {
|
|
56
|
+
content: [{
|
|
57
|
+
type: 'text',
|
|
58
|
+
text: JSON.stringify({
|
|
59
|
+
nodeId: node.id,
|
|
60
|
+
thoughtNumber: node.thoughtNumber,
|
|
61
|
+
totalThoughts: node.totalThoughts,
|
|
62
|
+
nextThoughtNeeded: input.nextThoughtNeeded,
|
|
63
|
+
branches: [],
|
|
64
|
+
thoughtHistoryLength: stats.totalNodes,
|
|
65
|
+
relatedNodes: relatedCount,
|
|
66
|
+
}),
|
|
67
|
+
}],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
-- Thinking Graph Schema v1
|
|
2
|
+
-- Sessions, Nodes (with FTS5), Edges, Skill Registry
|
|
3
|
+
|
|
4
|
+
PRAGMA journal_mode = WAL;
|
|
5
|
+
PRAGMA foreign_keys = ON;
|
|
6
|
+
|
|
7
|
+
-- ─── Sessions ────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
10
|
+
id TEXT PRIMARY KEY,
|
|
11
|
+
project_id TEXT,
|
|
12
|
+
project_path TEXT,
|
|
13
|
+
description TEXT,
|
|
14
|
+
started_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
15
|
+
last_active TEXT NOT NULL DEFAULT (datetime('now'))
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_id);
|
|
19
|
+
|
|
20
|
+
-- ─── Nodes ───────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
CREATE TABLE IF NOT EXISTS nodes (
|
|
23
|
+
id TEXT PRIMARY KEY,
|
|
24
|
+
type TEXT NOT NULL CHECK (type IN (
|
|
25
|
+
'thought','decision','insight','code_fact','assumption',
|
|
26
|
+
'detection','tech_debt','principle','pattern',
|
|
27
|
+
'skill_result','research'
|
|
28
|
+
)),
|
|
29
|
+
content TEXT NOT NULL,
|
|
30
|
+
session_id TEXT REFERENCES sessions(id),
|
|
31
|
+
project_id TEXT,
|
|
32
|
+
metadata TEXT DEFAULT '{}',
|
|
33
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
34
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
35
|
+
|
|
36
|
+
-- Backward compat with sequential-thinking
|
|
37
|
+
thought_number INTEGER,
|
|
38
|
+
total_thoughts INTEGER,
|
|
39
|
+
branch_id TEXT,
|
|
40
|
+
is_revision INTEGER DEFAULT 0,
|
|
41
|
+
revises_thought INTEGER
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_type ON nodes(type);
|
|
45
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_session ON nodes(session_id);
|
|
46
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_project ON nodes(project_id);
|
|
47
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_branch ON nodes(branch_id);
|
|
48
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_created ON nodes(created_at);
|
|
49
|
+
|
|
50
|
+
-- Full-text search
|
|
51
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS nodes_fts USING fts5(
|
|
52
|
+
content,
|
|
53
|
+
content='nodes',
|
|
54
|
+
content_rowid='rowid'
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
-- Keep FTS in sync
|
|
58
|
+
CREATE TRIGGER IF NOT EXISTS nodes_ai AFTER INSERT ON nodes BEGIN
|
|
59
|
+
INSERT INTO nodes_fts(rowid, content) VALUES (new.rowid, new.content);
|
|
60
|
+
END;
|
|
61
|
+
|
|
62
|
+
CREATE TRIGGER IF NOT EXISTS nodes_au AFTER UPDATE ON nodes BEGIN
|
|
63
|
+
INSERT INTO nodes_fts(nodes_fts, rowid, content) VALUES ('delete', old.rowid, old.content);
|
|
64
|
+
INSERT INTO nodes_fts(rowid, content) VALUES (new.rowid, new.content);
|
|
65
|
+
END;
|
|
66
|
+
|
|
67
|
+
CREATE TRIGGER IF NOT EXISTS nodes_ad AFTER DELETE ON nodes BEGIN
|
|
68
|
+
INSERT INTO nodes_fts(nodes_fts, rowid, content) VALUES ('delete', old.rowid, old.content);
|
|
69
|
+
END;
|
|
70
|
+
|
|
71
|
+
-- ─── Edges ───────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
CREATE TABLE IF NOT EXISTS edges (
|
|
74
|
+
id TEXT PRIMARY KEY,
|
|
75
|
+
source_id TEXT NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
|
|
76
|
+
target_id TEXT NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
|
|
77
|
+
type TEXT NOT NULL CHECK (type IN (
|
|
78
|
+
'depends_on','contradicts','supports','refines',
|
|
79
|
+
'supersedes','similar_to','located_in','violates',
|
|
80
|
+
'addresses','detected_by','invoked_by'
|
|
81
|
+
)),
|
|
82
|
+
weight REAL DEFAULT 1.0 CHECK (weight >= 0 AND weight <= 1),
|
|
83
|
+
reasoning TEXT,
|
|
84
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
85
|
+
|
|
86
|
+
UNIQUE(source_id, target_id, type)
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
CREATE INDEX IF NOT EXISTS idx_edges_source ON edges(source_id);
|
|
90
|
+
CREATE INDEX IF NOT EXISTS idx_edges_target ON edges(target_id);
|
|
91
|
+
CREATE INDEX IF NOT EXISTS idx_edges_type ON edges(type);
|
|
92
|
+
|
|
93
|
+
-- ─── Skill Registry ──────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
CREATE TABLE IF NOT EXISTS skill_registry (
|
|
96
|
+
id TEXT PRIMARY KEY,
|
|
97
|
+
plugin_name TEXT NOT NULL,
|
|
98
|
+
skill_name TEXT NOT NULL,
|
|
99
|
+
verb TEXT,
|
|
100
|
+
invocation TEXT NOT NULL,
|
|
101
|
+
areas TEXT DEFAULT '[]',
|
|
102
|
+
detects TEXT DEFAULT '[]',
|
|
103
|
+
produces TEXT DEFAULT '[]',
|
|
104
|
+
invokes TEXT DEFAULT '[]',
|
|
105
|
+
platform TEXT,
|
|
106
|
+
|
|
107
|
+
UNIQUE(plugin_name, skill_name)
|
|
108
|
+
);
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@feelingmindful/thinking-graph",
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "Persistent graph-based MCP thinking server for the feeling-mindful plugin marketplace",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"thinking-graph": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"migrations",
|
|
13
|
+
"seeds"
|
|
14
|
+
],
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/feeling-mindful/feeling-mindful-plugins.git",
|
|
18
|
+
"directory": "plugins/premium-core/mcp-servers/thinking-graph"
|
|
19
|
+
},
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public",
|
|
22
|
+
"registry": "https://registry.npmjs.org"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsc",
|
|
26
|
+
"prepublishOnly": "npm run build",
|
|
27
|
+
"dev": "tsc --watch",
|
|
28
|
+
"test": "vitest run",
|
|
29
|
+
"test:watch": "vitest",
|
|
30
|
+
"start": "node dist/index.js"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
34
|
+
"better-sqlite3": "^11.0.0",
|
|
35
|
+
"zod": "^3.23.0",
|
|
36
|
+
"uuid": "^10.0.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"vitest": "^3.0.0",
|
|
40
|
+
"typescript": "^5.5.0",
|
|
41
|
+
"@types/better-sqlite3": "^7.6.0",
|
|
42
|
+
"@types/uuid": "^10.0.0"
|
|
43
|
+
}
|
|
44
|
+
}
|