@feelingmindful/thinking-graph 1.6.0 → 1.8.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/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import { recallSchema, recallHandler } from './tools/recall.js';
|
|
|
10
10
|
import { learnSchema, learnHandler } from './tools/learn.js';
|
|
11
11
|
import { exportSchema, exportHandler } from './tools/export.js';
|
|
12
12
|
import { researchSchema, researchHandler } from './tools/research.js';
|
|
13
|
+
import { recommendSkillsSchema, recommendSkillsHandler } from './tools/recommend-skills.js';
|
|
13
14
|
// Legacy compat shim removed — use `think` tool directly
|
|
14
15
|
// ─── Storage setup ───────────────────────────────────────
|
|
15
16
|
const memoryOnly = process.env.THINKING_GRAPH_MEMORY_ONLY === 'true';
|
|
@@ -32,6 +33,7 @@ server.tool('recall', 'Query the thinking graph — search by text, filter by ty
|
|
|
32
33
|
server.tool('learn', 'Store durable knowledge — code facts, tech debt, insights, principles. Deduplicates similar content.', learnSchema.shape, async (input) => learnHandler(graph, input));
|
|
33
34
|
server.tool('export', 'Export the thinking graph as JSON or a human-readable markdown summary.', exportSchema.shape, async (input) => exportHandler(graph, input));
|
|
34
35
|
server.tool('research', 'Research a topic using Perplexity/Firecrawl, then ingest findings into the graph. Two-phase: call once to get an action plan, then again with findings to store them.', researchSchema.shape, async (input) => researchHandler(graph, input));
|
|
36
|
+
server.tool('recommend-skills', 'Recommend installed marketplace skills by area, verb, platform, or what they produce/detect. Use during reasoning to find skills that can help with the current task.', recommendSkillsSchema.shape, async (input) => recommendSkillsHandler(graph, input));
|
|
35
37
|
// ─── Startup ─────────────────────────────────────────────
|
|
36
38
|
async function main() {
|
|
37
39
|
await storage.initialize();
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { ThinkingGraph } from '../engine/graph.js';
|
|
3
|
+
export declare const findSkillsSchema: z.ZodObject<{
|
|
4
|
+
query: z.ZodOptional<z.ZodString>;
|
|
5
|
+
area: z.ZodOptional<z.ZodString>;
|
|
6
|
+
verb: z.ZodOptional<z.ZodString>;
|
|
7
|
+
platform: z.ZodOptional<z.ZodEnum<["ios", "android", "web", "all"]>>;
|
|
8
|
+
produces: z.ZodOptional<z.ZodString>;
|
|
9
|
+
detects: z.ZodOptional<z.ZodString>;
|
|
10
|
+
}, "strip", z.ZodTypeAny, {
|
|
11
|
+
verb?: string | undefined;
|
|
12
|
+
detects?: string | undefined;
|
|
13
|
+
produces?: string | undefined;
|
|
14
|
+
platform?: "all" | "web" | "ios" | "android" | undefined;
|
|
15
|
+
area?: string | undefined;
|
|
16
|
+
query?: string | undefined;
|
|
17
|
+
}, {
|
|
18
|
+
verb?: string | undefined;
|
|
19
|
+
detects?: string | undefined;
|
|
20
|
+
produces?: string | undefined;
|
|
21
|
+
platform?: "all" | "web" | "ios" | "android" | undefined;
|
|
22
|
+
area?: string | undefined;
|
|
23
|
+
query?: string | undefined;
|
|
24
|
+
}>;
|
|
25
|
+
export type FindSkillsInput = z.infer<typeof findSkillsSchema>;
|
|
26
|
+
export declare function findSkillsHandler(graph: ThinkingGraph, input: FindSkillsInput): Promise<{
|
|
27
|
+
content: {
|
|
28
|
+
type: "text";
|
|
29
|
+
text: string;
|
|
30
|
+
}[];
|
|
31
|
+
}>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const findSkillsSchema = z.object({
|
|
3
|
+
query: z.string().optional().describe('Free-text search across skill names, areas, and verbs'),
|
|
4
|
+
area: z.string().optional().describe('Filter by area (e.g., accessibility, security, monetization, copy, architecture)'),
|
|
5
|
+
verb: z.string().optional().describe('Filter by action verb (e.g., audit, create, refactor, research)'),
|
|
6
|
+
platform: z.enum(['ios', 'android', 'web', 'all']).optional().describe('Filter by platform'),
|
|
7
|
+
produces: z.string().optional().describe('Filter by node type the skill produces (e.g., detection, tech_debt, code_fact, decision)'),
|
|
8
|
+
detects: z.string().optional().describe('Filter by what the skill detects (e.g., missing, needs-work)'),
|
|
9
|
+
});
|
|
10
|
+
function matchesQuery(skill, query) {
|
|
11
|
+
const q = query.toLowerCase();
|
|
12
|
+
return (skill.skillName.toLowerCase().includes(q) ||
|
|
13
|
+
skill.pluginName.toLowerCase().includes(q) ||
|
|
14
|
+
(skill.verb?.toLowerCase().includes(q) ?? false) ||
|
|
15
|
+
skill.areas.some(a => a.toLowerCase().includes(q)) ||
|
|
16
|
+
skill.detects.some(d => d.toLowerCase().includes(q)) ||
|
|
17
|
+
skill.produces.some(p => p.toLowerCase().includes(q)));
|
|
18
|
+
}
|
|
19
|
+
export async function findSkillsHandler(graph, input) {
|
|
20
|
+
// Build filter for structured fields
|
|
21
|
+
const filter = {};
|
|
22
|
+
if (input.verb)
|
|
23
|
+
filter.verb = input.verb;
|
|
24
|
+
if (input.platform)
|
|
25
|
+
filter.platform = input.platform;
|
|
26
|
+
// Query the registry
|
|
27
|
+
let results = await graph.storage.querySkills(filter);
|
|
28
|
+
// Apply free-text search
|
|
29
|
+
if (input.query) {
|
|
30
|
+
results = results.filter(s => matchesQuery(s, input.query));
|
|
31
|
+
}
|
|
32
|
+
// Apply area filter (areas is an array, need partial match)
|
|
33
|
+
if (input.area) {
|
|
34
|
+
const area = input.area.toLowerCase();
|
|
35
|
+
results = results.filter(s => s.areas.some(a => a.toLowerCase().includes(area)));
|
|
36
|
+
}
|
|
37
|
+
// Apply produces filter
|
|
38
|
+
if (input.produces) {
|
|
39
|
+
const produces = input.produces.toLowerCase();
|
|
40
|
+
results = results.filter(s => s.produces.some(p => p.toLowerCase().includes(produces)));
|
|
41
|
+
}
|
|
42
|
+
// Apply detects filter
|
|
43
|
+
if (input.detects) {
|
|
44
|
+
const detects = input.detects.toLowerCase();
|
|
45
|
+
results = results.filter(s => s.detects.some(d => d.toLowerCase().includes(detects)));
|
|
46
|
+
}
|
|
47
|
+
// Format results with invocation commands
|
|
48
|
+
const skills = results.map(s => ({
|
|
49
|
+
id: s.id,
|
|
50
|
+
plugin: s.pluginName,
|
|
51
|
+
skill: s.skillName,
|
|
52
|
+
invocation: s.invocation,
|
|
53
|
+
verb: s.verb,
|
|
54
|
+
areas: s.areas,
|
|
55
|
+
detects: s.detects,
|
|
56
|
+
produces: s.produces,
|
|
57
|
+
invokes: s.invokes,
|
|
58
|
+
platform: s.platform,
|
|
59
|
+
}));
|
|
60
|
+
// Build suggestions for the agent
|
|
61
|
+
const suggestions = skills.slice(0, 3).map(s => ({
|
|
62
|
+
tool: 'skill',
|
|
63
|
+
when: `Invoke ${s.plugin}:${s.skill} to ${s.verb ?? 'run'} ${s.areas.length > 0 ? `(covers: ${s.areas.join(', ')})` : ''}`,
|
|
64
|
+
example: { skill: `${s.plugin}:${s.skill}` },
|
|
65
|
+
}));
|
|
66
|
+
// If skills produce node types, suggest piping results back via learn
|
|
67
|
+
if (skills.some(s => s.produces.length > 0)) {
|
|
68
|
+
suggestions.push({
|
|
69
|
+
tool: 'learn',
|
|
70
|
+
when: 'After invoking a skill, store its key findings back in the graph',
|
|
71
|
+
example: { skill: 'learn', content: '<skill findings>', type: 'skill_result' },
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
content: [{
|
|
76
|
+
type: 'text',
|
|
77
|
+
text: JSON.stringify({
|
|
78
|
+
matchCount: skills.length,
|
|
79
|
+
skills,
|
|
80
|
+
...(suggestions.length > 0 && { suggestions }),
|
|
81
|
+
}),
|
|
82
|
+
}],
|
|
83
|
+
};
|
|
84
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { ThinkingGraph } from '../engine/graph.js';
|
|
3
|
+
export declare const recommendSkillsSchema: z.ZodObject<{
|
|
4
|
+
query: z.ZodOptional<z.ZodString>;
|
|
5
|
+
area: z.ZodOptional<z.ZodString>;
|
|
6
|
+
verb: z.ZodOptional<z.ZodString>;
|
|
7
|
+
platform: z.ZodOptional<z.ZodEnum<["ios", "android", "web", "all"]>>;
|
|
8
|
+
produces: z.ZodOptional<z.ZodString>;
|
|
9
|
+
detects: z.ZodOptional<z.ZodString>;
|
|
10
|
+
}, "strip", z.ZodTypeAny, {
|
|
11
|
+
verb?: string | undefined;
|
|
12
|
+
detects?: string | undefined;
|
|
13
|
+
produces?: string | undefined;
|
|
14
|
+
platform?: "all" | "web" | "ios" | "android" | undefined;
|
|
15
|
+
area?: string | undefined;
|
|
16
|
+
query?: string | undefined;
|
|
17
|
+
}, {
|
|
18
|
+
verb?: string | undefined;
|
|
19
|
+
detects?: string | undefined;
|
|
20
|
+
produces?: string | undefined;
|
|
21
|
+
platform?: "all" | "web" | "ios" | "android" | undefined;
|
|
22
|
+
area?: string | undefined;
|
|
23
|
+
query?: string | undefined;
|
|
24
|
+
}>;
|
|
25
|
+
export type RecommendSkillsInput = z.infer<typeof recommendSkillsSchema>;
|
|
26
|
+
export declare function recommendSkillsHandler(graph: ThinkingGraph, input: RecommendSkillsInput): Promise<{
|
|
27
|
+
content: {
|
|
28
|
+
type: "text";
|
|
29
|
+
text: string;
|
|
30
|
+
}[];
|
|
31
|
+
}>;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const recommendSkillsSchema = z.object({
|
|
3
|
+
query: z.string().optional().describe('Free-text search across skill names, areas, and verbs'),
|
|
4
|
+
area: z.string().optional().describe('Filter by area (e.g., accessibility, security, monetization, copy, architecture)'),
|
|
5
|
+
verb: z.string().optional().describe('Filter by action verb (e.g., audit, create, refactor, research)'),
|
|
6
|
+
platform: z.enum(['ios', 'android', 'web', 'all']).optional().describe('Filter by platform'),
|
|
7
|
+
produces: z.string().optional().describe('Filter by node type the skill produces (e.g., detection, tech_debt, code_fact, decision)'),
|
|
8
|
+
detects: z.string().optional().describe('Filter by what the skill detects (e.g., missing, needs-work)'),
|
|
9
|
+
});
|
|
10
|
+
function matchesQuery(skill, query) {
|
|
11
|
+
const q = query.toLowerCase();
|
|
12
|
+
return (skill.skillName.toLowerCase().includes(q) ||
|
|
13
|
+
skill.pluginName.toLowerCase().includes(q) ||
|
|
14
|
+
(skill.verb?.toLowerCase().includes(q) ?? false) ||
|
|
15
|
+
skill.areas.some(a => a.toLowerCase().includes(q)) ||
|
|
16
|
+
skill.detects.some(d => d.toLowerCase().includes(q)) ||
|
|
17
|
+
skill.produces.some(p => p.toLowerCase().includes(q)));
|
|
18
|
+
}
|
|
19
|
+
export async function recommendSkillsHandler(graph, input) {
|
|
20
|
+
// Build filter for structured fields
|
|
21
|
+
const filter = {};
|
|
22
|
+
if (input.verb)
|
|
23
|
+
filter.verb = input.verb;
|
|
24
|
+
if (input.platform)
|
|
25
|
+
filter.platform = input.platform;
|
|
26
|
+
// Query the registry
|
|
27
|
+
let results = await graph.storage.querySkills(filter);
|
|
28
|
+
// Apply free-text search
|
|
29
|
+
if (input.query) {
|
|
30
|
+
results = results.filter(s => matchesQuery(s, input.query));
|
|
31
|
+
}
|
|
32
|
+
// Apply area filter (areas is an array, need partial match)
|
|
33
|
+
if (input.area) {
|
|
34
|
+
const area = input.area.toLowerCase();
|
|
35
|
+
results = results.filter(s => s.areas.some(a => a.toLowerCase().includes(area)));
|
|
36
|
+
}
|
|
37
|
+
// Apply produces filter
|
|
38
|
+
if (input.produces) {
|
|
39
|
+
const produces = input.produces.toLowerCase();
|
|
40
|
+
results = results.filter(s => s.produces.some(p => p.toLowerCase().includes(produces)));
|
|
41
|
+
}
|
|
42
|
+
// Apply detects filter
|
|
43
|
+
if (input.detects) {
|
|
44
|
+
const detects = input.detects.toLowerCase();
|
|
45
|
+
results = results.filter(s => s.detects.some(d => d.toLowerCase().includes(detects)));
|
|
46
|
+
}
|
|
47
|
+
// Format results with invocation commands
|
|
48
|
+
const skills = results.map(s => ({
|
|
49
|
+
id: s.id,
|
|
50
|
+
plugin: s.pluginName,
|
|
51
|
+
skill: s.skillName,
|
|
52
|
+
invocation: s.invocation,
|
|
53
|
+
verb: s.verb,
|
|
54
|
+
areas: s.areas,
|
|
55
|
+
detects: s.detects,
|
|
56
|
+
produces: s.produces,
|
|
57
|
+
invokes: s.invokes,
|
|
58
|
+
platform: s.platform,
|
|
59
|
+
}));
|
|
60
|
+
// Build suggestions for the agent
|
|
61
|
+
const suggestions = skills.slice(0, 3).map(s => ({
|
|
62
|
+
tool: 'skill',
|
|
63
|
+
when: `Invoke ${s.plugin}:${s.skill} to ${s.verb ?? 'run'} ${s.areas.length > 0 ? `(covers: ${s.areas.join(', ')})` : ''}`,
|
|
64
|
+
example: { skill: `${s.plugin}:${s.skill}` },
|
|
65
|
+
}));
|
|
66
|
+
// If skills produce node types, suggest piping results back via learn
|
|
67
|
+
if (skills.some(s => s.produces.length > 0)) {
|
|
68
|
+
suggestions.push({
|
|
69
|
+
tool: 'learn',
|
|
70
|
+
when: 'After invoking a skill, store its key findings back in the graph',
|
|
71
|
+
example: { skill: 'learn', content: '<skill findings>', type: 'skill_result' },
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
// If no local matches, suggest external skill discovery
|
|
75
|
+
if (skills.length === 0) {
|
|
76
|
+
const searchQuery = input.query ?? input.area ?? input.verb ?? '';
|
|
77
|
+
suggestions.push({
|
|
78
|
+
tool: 'skill',
|
|
79
|
+
when: 'No installed skills match — search the marketplace for skills to install',
|
|
80
|
+
example: { skill: 'find-skills', args: searchQuery },
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
content: [{
|
|
85
|
+
type: 'text',
|
|
86
|
+
text: JSON.stringify({
|
|
87
|
+
matchCount: skills.length,
|
|
88
|
+
skills,
|
|
89
|
+
...(suggestions.length > 0 && { suggestions }),
|
|
90
|
+
}),
|
|
91
|
+
}],
|
|
92
|
+
};
|
|
93
|
+
}
|
package/dist/tools/think.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { NODE_TYPES, EDGE_TYPES, GLOBAL_NODE_TYPES } from '../engine/types.js';
|
|
3
3
|
const coerceBool = z.preprocess((v) => (v === 'true' ? true : v === 'false' ? false : v), z.boolean());
|
|
4
|
-
function buildSuggestions(type, thoughtNumber, relatedCount, stats) {
|
|
4
|
+
function buildSuggestions(type, thoughtNumber, relatedCount, stats, matchedSkills) {
|
|
5
5
|
const suggestions = [];
|
|
6
6
|
// First thought in session — suggest recall to check prior knowledge
|
|
7
7
|
if (thoughtNumber === 1 && stats.totalNodes > 1) {
|
|
@@ -80,6 +80,10 @@ function buildSuggestions(type, thoughtNumber, relatedCount, stats) {
|
|
|
80
80
|
example: { skill: 'premium-core:reasoning' },
|
|
81
81
|
});
|
|
82
82
|
}
|
|
83
|
+
// Append matched skills from the registry
|
|
84
|
+
if (matchedSkills && matchedSkills.length > 0) {
|
|
85
|
+
suggestions.push(...matchedSkills);
|
|
86
|
+
}
|
|
83
87
|
return suggestions;
|
|
84
88
|
}
|
|
85
89
|
export const thinkSchema = z.object({
|
|
@@ -134,7 +138,24 @@ export async function thinkHandler(graph, input) {
|
|
|
134
138
|
}
|
|
135
139
|
}
|
|
136
140
|
const stats = await graph.storage.getStats();
|
|
137
|
-
|
|
141
|
+
// Query skill registry for relevant skills when detection/tech_debt nodes are created
|
|
142
|
+
let matchedSkills;
|
|
143
|
+
const nodeType = input.type ?? 'thought';
|
|
144
|
+
if (nodeType === 'detection' || nodeType === 'tech_debt' || nodeType === 'code_fact') {
|
|
145
|
+
const allSkills = await graph.storage.querySkills({});
|
|
146
|
+
const content = input.thought.toLowerCase();
|
|
147
|
+
// Match skills whose areas overlap with the thought content
|
|
148
|
+
const matches = allSkills.filter(s => s.areas.some(area => content.includes(area.toLowerCase())) ||
|
|
149
|
+
(s.verb && content.includes(s.verb.toLowerCase())));
|
|
150
|
+
if (matches.length > 0) {
|
|
151
|
+
matchedSkills = matches.slice(0, 2).map(s => ({
|
|
152
|
+
tool: 'skill',
|
|
153
|
+
when: `${s.verb ?? 'Run'} with ${s.pluginName}:${s.skillName} (covers: ${s.areas.join(', ')})`,
|
|
154
|
+
example: { skill: `${s.pluginName}:${s.skillName}` },
|
|
155
|
+
}));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const suggestions = buildSuggestions(nodeType, input.thoughtNumber, relatedCount, stats, matchedSkills);
|
|
138
159
|
return {
|
|
139
160
|
content: [{
|
|
140
161
|
type: 'text',
|
package/package.json
CHANGED