@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,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
+ }