@feelingmindful/thinking-graph 1.10.1 → 1.12.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 +8 -0
- package/dist/tools/execute-plan.d.ts +195 -0
- package/dist/tools/execute-plan.js +416 -0
- package/dist/tools/execute-skills.d.ts +104 -0
- package/dist/tools/execute-skills.js +274 -0
- package/dist/tools/learn.d.ts +3 -0
- package/dist/tools/learn.js +4 -1
- package/dist/tools/plan-skills.d.ts +40 -0
- package/dist/tools/plan-skills.js +40 -0
- package/dist/tools/recommend-skills.js +2 -2
- package/dist/tools/research.d.ts +9 -3
- package/dist/tools/research.js +45 -0
- package/dist/tools/route-skills.d.ts +34 -0
- package/dist/tools/route-skills.js +26 -0
- package/dist/tools/skill-routing.d.ts +63 -0
- package/dist/tools/skill-routing.js +343 -0
- package/dist/tools/think.js +3 -3
- package/package.json +1 -1
- package/seeds/skill-registry.json +144 -54
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { ThinkingGraph } from '../engine/graph.js';
|
|
3
|
+
import type { VaultBridge } from '../vault/bridge.js';
|
|
4
|
+
export declare const executeSkillsSchema: z.ZodObject<{
|
|
5
|
+
executionId: z.ZodOptional<z.ZodString>;
|
|
6
|
+
planId: z.ZodOptional<z.ZodString>;
|
|
7
|
+
runId: z.ZodOptional<z.ZodString>;
|
|
8
|
+
stepNumber: z.ZodNumber;
|
|
9
|
+
invocation: z.ZodString;
|
|
10
|
+
plugin: z.ZodString;
|
|
11
|
+
skill: z.ZodString;
|
|
12
|
+
purpose: z.ZodOptional<z.ZodString>;
|
|
13
|
+
status: z.ZodDefault<z.ZodEnum<["completed", "failed"]>>;
|
|
14
|
+
resultSummary: z.ZodString;
|
|
15
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
16
|
+
decisions: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
17
|
+
content: z.ZodString;
|
|
18
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
19
|
+
}, "strip", z.ZodTypeAny, {
|
|
20
|
+
content: string;
|
|
21
|
+
metadata?: Record<string, unknown> | undefined;
|
|
22
|
+
}, {
|
|
23
|
+
content: string;
|
|
24
|
+
metadata?: Record<string, unknown> | undefined;
|
|
25
|
+
}>, "many">>;
|
|
26
|
+
techDebt: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
27
|
+
content: z.ZodString;
|
|
28
|
+
severity: z.ZodOptional<z.ZodEnum<["critical", "high", "medium", "low"]>>;
|
|
29
|
+
effort: z.ZodOptional<z.ZodString>;
|
|
30
|
+
impact: z.ZodOptional<z.ZodString>;
|
|
31
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
32
|
+
}, "strip", z.ZodTypeAny, {
|
|
33
|
+
content: string;
|
|
34
|
+
metadata?: Record<string, unknown> | undefined;
|
|
35
|
+
severity?: "critical" | "high" | "medium" | "low" | undefined;
|
|
36
|
+
effort?: string | undefined;
|
|
37
|
+
impact?: string | undefined;
|
|
38
|
+
}, {
|
|
39
|
+
content: string;
|
|
40
|
+
metadata?: Record<string, unknown> | undefined;
|
|
41
|
+
severity?: "critical" | "high" | "medium" | "low" | undefined;
|
|
42
|
+
effort?: string | undefined;
|
|
43
|
+
impact?: string | undefined;
|
|
44
|
+
}>, "many">>;
|
|
45
|
+
sessionId: z.ZodOptional<z.ZodString>;
|
|
46
|
+
projectId: z.ZodOptional<z.ZodString>;
|
|
47
|
+
}, "strip", z.ZodTypeAny, {
|
|
48
|
+
invocation: string;
|
|
49
|
+
status: "completed" | "failed";
|
|
50
|
+
skill: string;
|
|
51
|
+
stepNumber: number;
|
|
52
|
+
plugin: string;
|
|
53
|
+
resultSummary: string;
|
|
54
|
+
sessionId?: string | undefined;
|
|
55
|
+
projectId?: string | undefined;
|
|
56
|
+
metadata?: Record<string, unknown> | undefined;
|
|
57
|
+
purpose?: string | undefined;
|
|
58
|
+
planId?: string | undefined;
|
|
59
|
+
runId?: string | undefined;
|
|
60
|
+
executionId?: string | undefined;
|
|
61
|
+
decisions?: {
|
|
62
|
+
content: string;
|
|
63
|
+
metadata?: Record<string, unknown> | undefined;
|
|
64
|
+
}[] | undefined;
|
|
65
|
+
techDebt?: {
|
|
66
|
+
content: string;
|
|
67
|
+
metadata?: Record<string, unknown> | undefined;
|
|
68
|
+
severity?: "critical" | "high" | "medium" | "low" | undefined;
|
|
69
|
+
effort?: string | undefined;
|
|
70
|
+
impact?: string | undefined;
|
|
71
|
+
}[] | undefined;
|
|
72
|
+
}, {
|
|
73
|
+
invocation: string;
|
|
74
|
+
skill: string;
|
|
75
|
+
stepNumber: number;
|
|
76
|
+
plugin: string;
|
|
77
|
+
resultSummary: string;
|
|
78
|
+
sessionId?: string | undefined;
|
|
79
|
+
projectId?: string | undefined;
|
|
80
|
+
metadata?: Record<string, unknown> | undefined;
|
|
81
|
+
status?: "completed" | "failed" | undefined;
|
|
82
|
+
purpose?: string | undefined;
|
|
83
|
+
planId?: string | undefined;
|
|
84
|
+
runId?: string | undefined;
|
|
85
|
+
executionId?: string | undefined;
|
|
86
|
+
decisions?: {
|
|
87
|
+
content: string;
|
|
88
|
+
metadata?: Record<string, unknown> | undefined;
|
|
89
|
+
}[] | undefined;
|
|
90
|
+
techDebt?: {
|
|
91
|
+
content: string;
|
|
92
|
+
metadata?: Record<string, unknown> | undefined;
|
|
93
|
+
severity?: "critical" | "high" | "medium" | "low" | undefined;
|
|
94
|
+
effort?: string | undefined;
|
|
95
|
+
impact?: string | undefined;
|
|
96
|
+
}[] | undefined;
|
|
97
|
+
}>;
|
|
98
|
+
export type ExecuteSkillsInput = z.infer<typeof executeSkillsSchema>;
|
|
99
|
+
export declare function executeSkillsHandler(graph: ThinkingGraph, input: ExecuteSkillsInput, vault?: VaultBridge, projectSlug?: string): Promise<{
|
|
100
|
+
content: {
|
|
101
|
+
type: "text";
|
|
102
|
+
text: string;
|
|
103
|
+
}[];
|
|
104
|
+
}>;
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { learnHandler } from './learn.js';
|
|
3
|
+
import { findExecutionInstances, findStoredPlan, parseExecutionId, resolveExecutionInstance, resolveExecutionProgress, } from './execute-plan.js';
|
|
4
|
+
const metadataSchema = z.record(z.unknown());
|
|
5
|
+
const decisionOutputSchema = z.object({
|
|
6
|
+
content: z.string(),
|
|
7
|
+
metadata: metadataSchema.optional(),
|
|
8
|
+
});
|
|
9
|
+
const techDebtOutputSchema = z.object({
|
|
10
|
+
content: z.string(),
|
|
11
|
+
severity: z.enum(['critical', 'high', 'medium', 'low']).optional(),
|
|
12
|
+
effort: z.string().optional(),
|
|
13
|
+
impact: z.string().optional(),
|
|
14
|
+
metadata: metadataSchema.optional(),
|
|
15
|
+
});
|
|
16
|
+
export const executeSkillsSchema = z.object({
|
|
17
|
+
executionId: z.string().optional().describe('Execution ID returned by execute-plan for a specific run. Prefer passing this exact value forward once a run has started.'),
|
|
18
|
+
planId: z.string().optional().describe('Plan ID returned by plan-skills'),
|
|
19
|
+
runId: z.string().optional().describe('Execution instance ID for this plan within the project. Required to disambiguate when execute-plan returned availableRunIds.'),
|
|
20
|
+
stepNumber: z.coerce.number().int().min(1),
|
|
21
|
+
invocation: z.string(),
|
|
22
|
+
plugin: z.string(),
|
|
23
|
+
skill: z.string(),
|
|
24
|
+
purpose: z.string().optional(),
|
|
25
|
+
status: z.enum(['completed', 'failed']).default('completed'),
|
|
26
|
+
resultSummary: z.string().describe('Human-readable summary of what the step produced or why it failed'),
|
|
27
|
+
metadata: metadataSchema.optional(),
|
|
28
|
+
decisions: z.array(decisionOutputSchema).optional(),
|
|
29
|
+
techDebt: z.array(techDebtOutputSchema).optional(),
|
|
30
|
+
sessionId: z.string().optional(),
|
|
31
|
+
projectId: z.string().optional(),
|
|
32
|
+
});
|
|
33
|
+
function asRecord(value) {
|
|
34
|
+
return value && typeof value === 'object' ? value : {};
|
|
35
|
+
}
|
|
36
|
+
async function resolveSession(graph, sessionId, projectId) {
|
|
37
|
+
if (!sessionId)
|
|
38
|
+
return graph.getCurrentSession();
|
|
39
|
+
return (await graph.storage.getSession(sessionId)) ?? graph.createSession({ id: sessionId, projectId });
|
|
40
|
+
}
|
|
41
|
+
function parseLearnResponse(result) {
|
|
42
|
+
return JSON.parse(result.content[0].text);
|
|
43
|
+
}
|
|
44
|
+
async function getPriorSkillResults(graph, _sessionId, projectId, executionId) {
|
|
45
|
+
const result = await graph.findNodes({ type: 'skill_result', projectId, limit: 1000 });
|
|
46
|
+
return result.items.filter(node => asRecord(node.metadata).executionId === executionId);
|
|
47
|
+
}
|
|
48
|
+
export async function executeSkillsHandler(graph, input, vault, projectSlug) {
|
|
49
|
+
const session = await resolveSession(graph, input.sessionId, input.projectId);
|
|
50
|
+
const parsedExecution = input.executionId ? parseExecutionId(input.executionId) : null;
|
|
51
|
+
const planId = input.planId ?? parsedExecution?.planId;
|
|
52
|
+
const status = input.status ?? 'completed';
|
|
53
|
+
if (input.executionId && !parsedExecution) {
|
|
54
|
+
return { content: [{ type: 'text', text: JSON.stringify({ status: 'error', error: 'Invalid executionId format.' }) }] };
|
|
55
|
+
}
|
|
56
|
+
if (input.planId && parsedExecution && input.planId !== parsedExecution.planId) {
|
|
57
|
+
return { content: [{ type: 'text', text: JSON.stringify({ status: 'error', error: 'planId does not match executionId.' }) }] };
|
|
58
|
+
}
|
|
59
|
+
if (input.runId && parsedExecution && input.runId !== parsedExecution.runId) {
|
|
60
|
+
return { content: [{ type: 'text', text: JSON.stringify({ status: 'error', error: 'runId does not match executionId.' }) }] };
|
|
61
|
+
}
|
|
62
|
+
if (!planId) {
|
|
63
|
+
return { content: [{ type: 'text', text: JSON.stringify({ status: 'error', error: 'executionId or planId is required. When multiple runs exist, pass runId from execute-plan.availableRunIds.' }) }] };
|
|
64
|
+
}
|
|
65
|
+
const storedPlan = await findStoredPlan(graph, session.id, input.projectId, planId);
|
|
66
|
+
if (!storedPlan) {
|
|
67
|
+
return { content: [{ type: 'text', text: JSON.stringify({ status: 'error', error: `No stored plan found for ${planId}. Run execute-plan first.` }) }] };
|
|
68
|
+
}
|
|
69
|
+
let executionId = input.executionId;
|
|
70
|
+
let runId = input.runId ?? parsedExecution?.runId;
|
|
71
|
+
if (executionId) {
|
|
72
|
+
const knownInstances = await findExecutionInstances(graph, input.projectId, planId);
|
|
73
|
+
const matchingInstance = knownInstances.find(instance => instance.executionId === executionId);
|
|
74
|
+
if (!matchingInstance) {
|
|
75
|
+
return {
|
|
76
|
+
content: [{
|
|
77
|
+
type: 'text',
|
|
78
|
+
text: JSON.stringify({
|
|
79
|
+
status: 'error',
|
|
80
|
+
planId,
|
|
81
|
+
error: `Execution ${executionId} is unknown. Run execute-plan first for this run and reuse its executionId/runId pair.`,
|
|
82
|
+
availableRunIds: knownInstances.map(instance => instance.runId).sort(),
|
|
83
|
+
}),
|
|
84
|
+
}],
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
runId = matchingInstance.runId;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
const executionInstance = await resolveExecutionInstance(graph, session, input.projectId, storedPlan, input.runId);
|
|
91
|
+
if (!executionInstance.instance) {
|
|
92
|
+
return {
|
|
93
|
+
content: [{
|
|
94
|
+
type: 'text',
|
|
95
|
+
text: JSON.stringify({
|
|
96
|
+
status: 'error',
|
|
97
|
+
planId,
|
|
98
|
+
error: executionInstance.error,
|
|
99
|
+
availableRunIds: executionInstance.availableRunIds ?? [],
|
|
100
|
+
}),
|
|
101
|
+
}],
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
executionId = executionInstance.instance.executionId;
|
|
105
|
+
runId = executionInstance.instance.runId;
|
|
106
|
+
}
|
|
107
|
+
if (!runId) {
|
|
108
|
+
runId = parseExecutionId(executionId)?.runId;
|
|
109
|
+
}
|
|
110
|
+
const progress = await resolveExecutionProgress(graph, session.id, input.projectId, storedPlan, executionId);
|
|
111
|
+
if (!progress.nextStep) {
|
|
112
|
+
return { content: [{ type: 'text', text: JSON.stringify({ status: 'error', error: `Execution ${executionId} is already complete.` }) }] };
|
|
113
|
+
}
|
|
114
|
+
if (progress.blockingGate) {
|
|
115
|
+
return {
|
|
116
|
+
content: [{
|
|
117
|
+
type: 'text',
|
|
118
|
+
text: JSON.stringify({
|
|
119
|
+
status: 'awaiting_approval',
|
|
120
|
+
executionId,
|
|
121
|
+
planId,
|
|
122
|
+
runId,
|
|
123
|
+
blockedByGate: progress.blockingGate,
|
|
124
|
+
nextStep: progress.nextStep,
|
|
125
|
+
error: `Approval gate ${progress.blockingGate.id} must be approved before step ${progress.nextStep.stepNumber}. Rerun execute-plan with runId ${runId}.`,
|
|
126
|
+
}),
|
|
127
|
+
}],
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
if (progress.nextStep.stepNumber !== input.stepNumber) {
|
|
131
|
+
return {
|
|
132
|
+
content: [{
|
|
133
|
+
type: 'text',
|
|
134
|
+
text: JSON.stringify({
|
|
135
|
+
status: 'error',
|
|
136
|
+
executionId,
|
|
137
|
+
planId,
|
|
138
|
+
runId,
|
|
139
|
+
error: `Step ${input.stepNumber} is out of order. Record step ${progress.nextStep.stepNumber} next.`,
|
|
140
|
+
nextStep: progress.nextStep,
|
|
141
|
+
completedSteps: progress.completedSteps,
|
|
142
|
+
}),
|
|
143
|
+
}],
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
if (input.invocation !== progress.nextStep.invocation
|
|
147
|
+
|| input.plugin !== progress.nextStep.plugin
|
|
148
|
+
|| input.skill !== progress.nextStep.skill) {
|
|
149
|
+
return {
|
|
150
|
+
content: [{
|
|
151
|
+
type: 'text',
|
|
152
|
+
text: JSON.stringify({
|
|
153
|
+
status: 'error',
|
|
154
|
+
executionId,
|
|
155
|
+
planId,
|
|
156
|
+
runId,
|
|
157
|
+
error: `Step ${input.stepNumber} does not match the stored plan entry.`,
|
|
158
|
+
expectedStep: progress.nextStep,
|
|
159
|
+
}),
|
|
160
|
+
}],
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
const priorResults = await getPriorSkillResults(graph, session.id, input.projectId, executionId);
|
|
164
|
+
const attempt = priorResults.filter(node => asRecord(node.metadata).stepNumber === input.stepNumber).length + 1;
|
|
165
|
+
const priorStepNode = priorResults
|
|
166
|
+
.filter(node => asRecord(node.metadata).stepNumber === input.stepNumber - 1)
|
|
167
|
+
.sort((a, b) => b.createdAt.localeCompare(a.createdAt))[0];
|
|
168
|
+
const skillResult = await learnHandler(graph, {
|
|
169
|
+
content: `Step ${input.stepNumber} (${input.invocation}) attempt ${attempt}: ${input.resultSummary}`,
|
|
170
|
+
type: 'skill_result',
|
|
171
|
+
projectId: input.projectId,
|
|
172
|
+
sessionId: session.id,
|
|
173
|
+
metadata: {
|
|
174
|
+
kind: 'skill_execution',
|
|
175
|
+
executionId,
|
|
176
|
+
planId,
|
|
177
|
+
runId,
|
|
178
|
+
stepNumber: input.stepNumber,
|
|
179
|
+
invocation: input.invocation,
|
|
180
|
+
plugin: input.plugin,
|
|
181
|
+
skill: input.skill,
|
|
182
|
+
purpose: input.purpose,
|
|
183
|
+
status,
|
|
184
|
+
attempt,
|
|
185
|
+
...(input.metadata ?? {}),
|
|
186
|
+
},
|
|
187
|
+
}, vault, projectSlug);
|
|
188
|
+
const skillResultData = parseLearnResponse(skillResult);
|
|
189
|
+
if (priorStepNode) {
|
|
190
|
+
await graph.addEdge({
|
|
191
|
+
sourceId: skillResultData.nodeId,
|
|
192
|
+
targetId: priorStepNode.id,
|
|
193
|
+
type: 'depends_on',
|
|
194
|
+
reasoning: 'Execution order dependency between consecutive steps.',
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
const decisionNodeIds = [];
|
|
198
|
+
for (const decision of input.decisions ?? []) {
|
|
199
|
+
const recorded = await learnHandler(graph, {
|
|
200
|
+
content: decision.content,
|
|
201
|
+
type: 'decision',
|
|
202
|
+
projectId: input.projectId,
|
|
203
|
+
sessionId: session.id,
|
|
204
|
+
metadata: {
|
|
205
|
+
kind: 'skill_output_decision',
|
|
206
|
+
executionId,
|
|
207
|
+
planId,
|
|
208
|
+
runId,
|
|
209
|
+
stepNumber: input.stepNumber,
|
|
210
|
+
invocation: input.invocation,
|
|
211
|
+
sourceSkillResultId: skillResultData.nodeId,
|
|
212
|
+
...(decision.metadata ?? {}),
|
|
213
|
+
},
|
|
214
|
+
}, vault, projectSlug);
|
|
215
|
+
const recordedData = parseLearnResponse(recorded);
|
|
216
|
+
decisionNodeIds.push(recordedData.nodeId);
|
|
217
|
+
await graph.addEdge({
|
|
218
|
+
sourceId: recordedData.nodeId,
|
|
219
|
+
targetId: skillResultData.nodeId,
|
|
220
|
+
type: 'invoked_by',
|
|
221
|
+
reasoning: 'Decision captured from skill execution output.',
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
const techDebtNodeIds = [];
|
|
225
|
+
for (const debt of input.techDebt ?? []) {
|
|
226
|
+
const recorded = await learnHandler(graph, {
|
|
227
|
+
content: debt.content,
|
|
228
|
+
type: 'tech_debt',
|
|
229
|
+
projectId: input.projectId,
|
|
230
|
+
sessionId: session.id,
|
|
231
|
+
severity: debt.severity,
|
|
232
|
+
effort: debt.effort,
|
|
233
|
+
impact: debt.impact,
|
|
234
|
+
metadata: {
|
|
235
|
+
kind: 'skill_output_tech_debt',
|
|
236
|
+
executionId,
|
|
237
|
+
planId,
|
|
238
|
+
runId,
|
|
239
|
+
stepNumber: input.stepNumber,
|
|
240
|
+
invocation: input.invocation,
|
|
241
|
+
sourceSkillResultId: skillResultData.nodeId,
|
|
242
|
+
...(debt.metadata ?? {}),
|
|
243
|
+
},
|
|
244
|
+
}, vault, projectSlug);
|
|
245
|
+
const recordedData = parseLearnResponse(recorded);
|
|
246
|
+
techDebtNodeIds.push(recordedData.nodeId);
|
|
247
|
+
await graph.addEdge({
|
|
248
|
+
sourceId: recordedData.nodeId,
|
|
249
|
+
targetId: skillResultData.nodeId,
|
|
250
|
+
type: 'invoked_by',
|
|
251
|
+
reasoning: 'Tech debt captured from skill execution output.',
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
return {
|
|
255
|
+
content: [{
|
|
256
|
+
type: 'text',
|
|
257
|
+
text: JSON.stringify({
|
|
258
|
+
status,
|
|
259
|
+
executionId,
|
|
260
|
+
planId,
|
|
261
|
+
runId,
|
|
262
|
+
skillResultNodeId: skillResultData.nodeId,
|
|
263
|
+
stepNumber: input.stepNumber,
|
|
264
|
+
attempt,
|
|
265
|
+
decisionNodeIds,
|
|
266
|
+
techDebtNodeIds,
|
|
267
|
+
recordedCount: 1 + decisionNodeIds.length + techDebtNodeIds.length,
|
|
268
|
+
nextAction: status === 'failed'
|
|
269
|
+
? `Fix or rerun ${input.invocation}, then record a successful retry before continuing.`
|
|
270
|
+
: `Rerun execute-plan for ${executionId} to continue with the next step.`,
|
|
271
|
+
}),
|
|
272
|
+
}],
|
|
273
|
+
};
|
|
274
|
+
}
|
package/dist/tools/learn.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export declare const learnSchema: z.ZodObject<{
|
|
|
5
5
|
content: z.ZodString;
|
|
6
6
|
type: z.ZodEnum<["thought", "decision", "insight", "code_fact", "assumption", "detection", "tech_debt", "principle", "pattern", "skill_result", "research"]>;
|
|
7
7
|
projectId: z.ZodOptional<z.ZodString>;
|
|
8
|
+
sessionId: z.ZodOptional<z.ZodString>;
|
|
8
9
|
filePath: z.ZodOptional<z.ZodString>;
|
|
9
10
|
lineRange: z.ZodOptional<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>;
|
|
10
11
|
severity: z.ZodOptional<z.ZodEnum<["critical", "high", "medium", "low"]>>;
|
|
@@ -28,6 +29,7 @@ export declare const learnSchema: z.ZodObject<{
|
|
|
28
29
|
}, "strip", z.ZodTypeAny, {
|
|
29
30
|
type: "thought" | "decision" | "insight" | "code_fact" | "assumption" | "detection" | "tech_debt" | "principle" | "pattern" | "skill_result" | "research";
|
|
30
31
|
content: string;
|
|
32
|
+
sessionId?: string | undefined;
|
|
31
33
|
projectId?: string | undefined;
|
|
32
34
|
metadata?: Record<string, unknown> | undefined;
|
|
33
35
|
severity?: "critical" | "high" | "medium" | "low" | undefined;
|
|
@@ -44,6 +46,7 @@ export declare const learnSchema: z.ZodObject<{
|
|
|
44
46
|
}, {
|
|
45
47
|
type: "thought" | "decision" | "insight" | "code_fact" | "assumption" | "detection" | "tech_debt" | "principle" | "pattern" | "skill_result" | "research";
|
|
46
48
|
content: string;
|
|
49
|
+
sessionId?: string | undefined;
|
|
47
50
|
projectId?: string | undefined;
|
|
48
51
|
metadata?: Record<string, unknown> | undefined;
|
|
49
52
|
severity?: "critical" | "high" | "medium" | "low" | undefined;
|
package/dist/tools/learn.js
CHANGED
|
@@ -5,6 +5,7 @@ export const learnSchema = z.object({
|
|
|
5
5
|
content: z.string().describe('What was learned'),
|
|
6
6
|
type: z.enum(NODE_TYPES).describe('Node type'),
|
|
7
7
|
projectId: z.string().optional(),
|
|
8
|
+
sessionId: z.string().optional(),
|
|
8
9
|
filePath: z.string().optional().describe('For code_facts'),
|
|
9
10
|
lineRange: z.tuple([z.coerce.number(), z.coerce.number()]).optional().describe('Line range [start, end]'),
|
|
10
11
|
severity: z.enum(['critical', 'high', 'medium', 'low']).optional().describe('For tech_debt'),
|
|
@@ -46,7 +47,9 @@ export async function learnHandler(graph, input, vault, projectSlug) {
|
|
|
46
47
|
metadata.effort = input.effort;
|
|
47
48
|
if (input.impact)
|
|
48
49
|
metadata.impact = input.impact;
|
|
49
|
-
const session =
|
|
50
|
+
const session = input.sessionId
|
|
51
|
+
? ((await graph.storage.getSession(input.sessionId)) ?? await graph.createSession({ id: input.sessionId, projectId: input.projectId }))
|
|
52
|
+
: await graph.getCurrentSession();
|
|
50
53
|
const node = await graph.addNode({
|
|
51
54
|
type: input.type,
|
|
52
55
|
content: input.content,
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { ThinkingGraph } from '../engine/graph.js';
|
|
3
|
+
export declare const planSkillsSchema: z.ZodObject<{
|
|
4
|
+
request: z.ZodString;
|
|
5
|
+
platform: z.ZodOptional<z.ZodEnum<["ios", "android", "web", "all"]>>;
|
|
6
|
+
goalVerb: z.ZodOptional<z.ZodString>;
|
|
7
|
+
areas: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
8
|
+
currentState: z.ZodOptional<z.ZodEnum<["new", "existing", "unknown"]>>;
|
|
9
|
+
detectedNeeds: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
10
|
+
selectedInvocation: z.ZodOptional<z.ZodString>;
|
|
11
|
+
requireApproval: z.ZodOptional<z.ZodEffects<z.ZodBoolean, boolean, unknown>>;
|
|
12
|
+
maxCandidates: z.ZodOptional<z.ZodNumber>;
|
|
13
|
+
}, "strip", z.ZodTypeAny, {
|
|
14
|
+
request: string;
|
|
15
|
+
areas?: string[] | undefined;
|
|
16
|
+
platform?: "all" | "web" | "ios" | "android" | undefined;
|
|
17
|
+
currentState?: "unknown" | "new" | "existing" | undefined;
|
|
18
|
+
goalVerb?: string | undefined;
|
|
19
|
+
detectedNeeds?: string[] | undefined;
|
|
20
|
+
maxCandidates?: number | undefined;
|
|
21
|
+
selectedInvocation?: string | undefined;
|
|
22
|
+
requireApproval?: boolean | undefined;
|
|
23
|
+
}, {
|
|
24
|
+
request: string;
|
|
25
|
+
areas?: string[] | undefined;
|
|
26
|
+
platform?: "all" | "web" | "ios" | "android" | undefined;
|
|
27
|
+
currentState?: "unknown" | "new" | "existing" | undefined;
|
|
28
|
+
goalVerb?: string | undefined;
|
|
29
|
+
detectedNeeds?: string[] | undefined;
|
|
30
|
+
maxCandidates?: number | undefined;
|
|
31
|
+
selectedInvocation?: string | undefined;
|
|
32
|
+
requireApproval?: unknown;
|
|
33
|
+
}>;
|
|
34
|
+
export type PlanSkillsInput = z.infer<typeof planSkillsSchema>;
|
|
35
|
+
export declare function planSkillsHandler(graph: ThinkingGraph, input: PlanSkillsInput): Promise<{
|
|
36
|
+
content: {
|
|
37
|
+
type: "text";
|
|
38
|
+
text: string;
|
|
39
|
+
}[];
|
|
40
|
+
}>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { buildPlanId, buildSkillPlan } from './skill-routing.js';
|
|
3
|
+
const coerceBool = z.preprocess((v) => (v === 'true' ? true : v === 'false' ? false : v), z.boolean());
|
|
4
|
+
export const planSkillsSchema = z.object({
|
|
5
|
+
request: z.string().describe('Task or problem to convert into an ordered skill execution plan'),
|
|
6
|
+
platform: z.enum(['ios', 'android', 'web', 'all']).optional().describe('Preferred platform filter'),
|
|
7
|
+
goalVerb: z.string().optional().describe('Desired outcome verb such as audit, create, refactor, configure, research, or full'),
|
|
8
|
+
areas: z.array(z.string()).optional().describe('Explicit areas to weight such as accessibility, auth, observability, or copy'),
|
|
9
|
+
currentState: z.enum(['new', 'existing', 'unknown']).optional().describe('Whether the target is greenfield or an existing project'),
|
|
10
|
+
detectedNeeds: z.array(z.string()).optional().describe('Known states such as missing, needs-work, or good'),
|
|
11
|
+
selectedInvocation: z.string().optional().describe('Force a specific routed invocation when you want the plan built from a chosen candidate'),
|
|
12
|
+
requireApproval: coerceBool.optional().describe('Whether to insert approval gates before execution (default true)'),
|
|
13
|
+
maxCandidates: z.coerce.number().int().min(1).max(10).optional().describe('Maximum ranked candidates to consider during routing'),
|
|
14
|
+
});
|
|
15
|
+
export async function planSkillsHandler(graph, input) {
|
|
16
|
+
const planned = await buildSkillPlan(graph, input);
|
|
17
|
+
const planId = planned.plan.length > 0
|
|
18
|
+
? buildPlanId({ request: input.request, plan: planned.plan, approvalGates: planned.approvalGates })
|
|
19
|
+
: null;
|
|
20
|
+
return {
|
|
21
|
+
content: [{
|
|
22
|
+
type: 'text',
|
|
23
|
+
text: JSON.stringify({
|
|
24
|
+
planId,
|
|
25
|
+
request: input.request,
|
|
26
|
+
desiredVerb: planned.desiredVerb,
|
|
27
|
+
inferredAreas: planned.inferredAreas,
|
|
28
|
+
heuristicNotes: planned.heuristicNotes,
|
|
29
|
+
route: planned.route,
|
|
30
|
+
plan: planned.plan,
|
|
31
|
+
approvalGates: planned.approvalGates,
|
|
32
|
+
fallbacks: planned.fallbacks,
|
|
33
|
+
executionReady: planned.approvalGates.length === 0,
|
|
34
|
+
nextAction: planned.approvalGates.length > 0
|
|
35
|
+
? `Request approval for ${planned.approvalGates[0].id} before executing step ${planned.approvalGates[0].beforeStepNumber}.`
|
|
36
|
+
: 'Execution may proceed.',
|
|
37
|
+
}),
|
|
38
|
+
}],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -60,8 +60,8 @@ export async function recommendSkillsHandler(graph, input) {
|
|
|
60
60
|
// Build suggestions for the agent
|
|
61
61
|
const suggestions = skills.slice(0, 3).map(s => ({
|
|
62
62
|
tool: 'skill',
|
|
63
|
-
when: `
|
|
64
|
-
example: { skill: `${s.plugin}:${s.skill}
|
|
63
|
+
when: `Run \`${s.invocation}\`${s.areas.length > 0 ? ` (covers: ${s.areas.join(', ')})` : ''}`,
|
|
64
|
+
example: { skill: `${s.plugin}:${s.skill}`, invocation: s.invocation },
|
|
65
65
|
}));
|
|
66
66
|
// If skills produce node types, suggest piping results back via learn
|
|
67
67
|
if (skills.some(s => s.produces.length > 0)) {
|
package/dist/tools/research.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { ThinkingGraph } from '../engine/graph.js';
|
|
|
3
3
|
import { type VaultBridge } from '../vault/bridge.js';
|
|
4
4
|
export declare const researchSchema: z.ZodObject<{
|
|
5
5
|
query: z.ZodString;
|
|
6
|
-
intent: z.ZodDefault<z.ZodEnum<["fact_check", "explore", "compare", "how_to", "current_state"]>>;
|
|
6
|
+
intent: z.ZodDefault<z.ZodEnum<["fact_check", "explore", "compare", "how_to", "current_state", "grounded_qa"]>>;
|
|
7
7
|
context: z.ZodOptional<z.ZodString>;
|
|
8
8
|
researchId: z.ZodOptional<z.ZodString>;
|
|
9
9
|
findings: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
@@ -23,9 +23,11 @@ export declare const researchSchema: z.ZodObject<{
|
|
|
23
23
|
scrapeUrls: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
24
24
|
recencyFilter: z.ZodOptional<z.ZodEnum<["hour", "day", "week", "month", "year"]>>;
|
|
25
25
|
domainFilter: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
26
|
+
notebookId: z.ZodOptional<z.ZodString>;
|
|
27
|
+
skipGrounded: z.ZodOptional<z.ZodEffects<z.ZodBoolean, boolean, unknown>>;
|
|
26
28
|
}, "strip", z.ZodTypeAny, {
|
|
27
29
|
query: string;
|
|
28
|
-
intent: "fact_check" | "explore" | "compare" | "how_to" | "current_state";
|
|
30
|
+
intent: "fact_check" | "explore" | "compare" | "how_to" | "current_state" | "grounded_qa";
|
|
29
31
|
projectId?: string | undefined;
|
|
30
32
|
context?: string | undefined;
|
|
31
33
|
researchId?: string | undefined;
|
|
@@ -37,10 +39,12 @@ export declare const researchSchema: z.ZodObject<{
|
|
|
37
39
|
scrapeUrls?: string[] | undefined;
|
|
38
40
|
recencyFilter?: "hour" | "day" | "week" | "month" | "year" | undefined;
|
|
39
41
|
domainFilter?: string[] | undefined;
|
|
42
|
+
notebookId?: string | undefined;
|
|
43
|
+
skipGrounded?: boolean | undefined;
|
|
40
44
|
}, {
|
|
41
45
|
query: string;
|
|
42
46
|
projectId?: string | undefined;
|
|
43
|
-
intent?: "fact_check" | "explore" | "compare" | "how_to" | "current_state" | undefined;
|
|
47
|
+
intent?: "fact_check" | "explore" | "compare" | "how_to" | "current_state" | "grounded_qa" | undefined;
|
|
44
48
|
context?: string | undefined;
|
|
45
49
|
researchId?: string | undefined;
|
|
46
50
|
findings?: {
|
|
@@ -51,6 +55,8 @@ export declare const researchSchema: z.ZodObject<{
|
|
|
51
55
|
scrapeUrls?: string[] | undefined;
|
|
52
56
|
recencyFilter?: "hour" | "day" | "week" | "month" | "year" | undefined;
|
|
53
57
|
domainFilter?: string[] | undefined;
|
|
58
|
+
notebookId?: string | undefined;
|
|
59
|
+
skipGrounded?: unknown;
|
|
54
60
|
}>;
|
|
55
61
|
export type ResearchInput = z.infer<typeof researchSchema>;
|
|
56
62
|
export declare function researchHandler(graph: ThinkingGraph, input: ResearchInput, vault?: VaultBridge, projectSlug?: string): Promise<{
|
package/dist/tools/research.js
CHANGED
|
@@ -7,7 +7,11 @@ const RESEARCH_INTENTS = [
|
|
|
7
7
|
'compare', // Compare options, libraries, approaches
|
|
8
8
|
'how_to', // Find implementation guidance
|
|
9
9
|
'current_state', // Get current state of something (version, status, etc.)
|
|
10
|
+
'grounded_qa', // Query a curated knowledge base (NotebookLM notebook) — zero hallucinations
|
|
10
11
|
];
|
|
12
|
+
// Intents that should preferentially route through a NotebookLM notebook when available.
|
|
13
|
+
// These are the cases where a curated, citation-backed source is most valuable.
|
|
14
|
+
const GROUNDED_PREFERRED_INTENTS = new Set(['how_to', 'fact_check', 'current_state', 'grounded_qa']);
|
|
11
15
|
const findingSchema = z.object({
|
|
12
16
|
content: z.string().describe('What was found'),
|
|
13
17
|
source: z.string().optional().describe('URL or citation'),
|
|
@@ -26,10 +30,51 @@ export const researchSchema = z.object({
|
|
|
26
30
|
scrapeUrls: z.array(z.string()).optional().describe('Specific URLs to scrape with Firecrawl'),
|
|
27
31
|
recencyFilter: z.enum(['hour', 'day', 'week', 'month', 'year']).optional().describe('How recent results should be'),
|
|
28
32
|
domainFilter: z.array(z.string()).optional().describe('Restrict to these domains'),
|
|
33
|
+
notebookId: z.string().optional().describe('Specific NotebookLM notebook ID to query. If omitted, action plan will list notebooks first so the caller can pick the best match.'),
|
|
34
|
+
skipGrounded: coerceBool.optional().describe('Skip NotebookLM step even when it would normally auto-prepend'),
|
|
29
35
|
});
|
|
36
|
+
function buildGroundedSteps(input) {
|
|
37
|
+
const steps = [];
|
|
38
|
+
const query = input.query;
|
|
39
|
+
// If caller specified a notebookId, select it then ask directly.
|
|
40
|
+
if (input.notebookId) {
|
|
41
|
+
steps.push({
|
|
42
|
+
tool: 'mcp__notebooklm__select_notebook',
|
|
43
|
+
description: 'Select the specified NotebookLM notebook',
|
|
44
|
+
args: { id: input.notebookId },
|
|
45
|
+
});
|
|
46
|
+
steps.push({
|
|
47
|
+
tool: 'mcp__notebooklm__ask_question',
|
|
48
|
+
description: 'Ask NotebookLM (grounded, citation-backed)',
|
|
49
|
+
args: { question: query },
|
|
50
|
+
});
|
|
51
|
+
return steps;
|
|
52
|
+
}
|
|
53
|
+
// Otherwise: search the library for the best match, then the agent picks one and asks.
|
|
54
|
+
steps.push({
|
|
55
|
+
tool: 'mcp__notebooklm__search_notebooks',
|
|
56
|
+
description: 'Find a NotebookLM notebook matching this query. Skip remaining NotebookLM steps if no match.',
|
|
57
|
+
args: { query },
|
|
58
|
+
});
|
|
59
|
+
steps.push({
|
|
60
|
+
tool: 'mcp__notebooklm__ask_question',
|
|
61
|
+
description: 'Ask the selected NotebookLM notebook. Only run after search_notebooks returns a match; fall back to the web-grounded steps below if the library has no relevant notebook.',
|
|
62
|
+
args: { question: query },
|
|
63
|
+
});
|
|
64
|
+
return steps;
|
|
65
|
+
}
|
|
30
66
|
function buildActionPlan(input) {
|
|
31
67
|
const steps = [];
|
|
32
68
|
const query = input.query;
|
|
69
|
+
// Grounded NotebookLM path (preferred for how_to / fact_check / current_state / grounded_qa).
|
|
70
|
+
const shouldPrependGrounded = !input.skipGrounded && GROUNDED_PREFERRED_INTENTS.has(input.intent ?? 'explore');
|
|
71
|
+
if (shouldPrependGrounded) {
|
|
72
|
+
steps.push(...buildGroundedSteps(input));
|
|
73
|
+
}
|
|
74
|
+
// grounded_qa is NotebookLM-only — no web fallback steps.
|
|
75
|
+
if (input.intent === 'grounded_qa') {
|
|
76
|
+
return steps;
|
|
77
|
+
}
|
|
33
78
|
// Choose Perplexity tool based on intent
|
|
34
79
|
switch (input.intent) {
|
|
35
80
|
case 'fact_check':
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { ThinkingGraph } from '../engine/graph.js';
|
|
3
|
+
export declare const routeSkillsSchema: z.ZodObject<{
|
|
4
|
+
request: z.ZodString;
|
|
5
|
+
platform: z.ZodOptional<z.ZodEnum<["ios", "android", "web", "all"]>>;
|
|
6
|
+
goalVerb: z.ZodOptional<z.ZodString>;
|
|
7
|
+
areas: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
8
|
+
currentState: z.ZodOptional<z.ZodEnum<["new", "existing", "unknown"]>>;
|
|
9
|
+
detectedNeeds: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
10
|
+
maxCandidates: z.ZodOptional<z.ZodNumber>;
|
|
11
|
+
}, "strip", z.ZodTypeAny, {
|
|
12
|
+
request: string;
|
|
13
|
+
areas?: string[] | undefined;
|
|
14
|
+
platform?: "all" | "web" | "ios" | "android" | undefined;
|
|
15
|
+
currentState?: "unknown" | "new" | "existing" | undefined;
|
|
16
|
+
goalVerb?: string | undefined;
|
|
17
|
+
detectedNeeds?: string[] | undefined;
|
|
18
|
+
maxCandidates?: number | undefined;
|
|
19
|
+
}, {
|
|
20
|
+
request: string;
|
|
21
|
+
areas?: string[] | undefined;
|
|
22
|
+
platform?: "all" | "web" | "ios" | "android" | undefined;
|
|
23
|
+
currentState?: "unknown" | "new" | "existing" | undefined;
|
|
24
|
+
goalVerb?: string | undefined;
|
|
25
|
+
detectedNeeds?: string[] | undefined;
|
|
26
|
+
maxCandidates?: number | undefined;
|
|
27
|
+
}>;
|
|
28
|
+
export type RouteSkillsInput = z.infer<typeof routeSkillsSchema>;
|
|
29
|
+
export declare function routeSkillsHandler(graph: ThinkingGraph, input: RouteSkillsInput): Promise<{
|
|
30
|
+
content: {
|
|
31
|
+
type: "text";
|
|
32
|
+
text: string;
|
|
33
|
+
}[];
|
|
34
|
+
}>;
|