@feelingmindful/thinking-graph 1.10.1 → 1.11.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/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)) {
|
|
@@ -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
|
+
}>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { routeSkills } from './skill-routing.js';
|
|
3
|
+
export const routeSkillsSchema = z.object({
|
|
4
|
+
request: z.string().describe('Task or problem to route to the best installed skill workflow'),
|
|
5
|
+
platform: z.enum(['ios', 'android', 'web', 'all']).optional().describe('Preferred platform filter'),
|
|
6
|
+
goalVerb: z.string().optional().describe('Desired outcome verb such as audit, create, refactor, configure, research, or full'),
|
|
7
|
+
areas: z.array(z.string()).optional().describe('Explicit areas to weight such as accessibility, auth, observability, or copy'),
|
|
8
|
+
currentState: z.enum(['new', 'existing', 'unknown']).optional().describe('Whether the target is greenfield or an existing project'),
|
|
9
|
+
detectedNeeds: z.array(z.string()).optional().describe('Known states such as missing, needs-work, or good'),
|
|
10
|
+
maxCandidates: z.coerce.number().int().min(1).max(10).optional().describe('Maximum ranked candidates to return'),
|
|
11
|
+
});
|
|
12
|
+
export async function routeSkillsHandler(graph, input) {
|
|
13
|
+
const routed = await routeSkills(graph, input);
|
|
14
|
+
return {
|
|
15
|
+
content: [{
|
|
16
|
+
type: 'text',
|
|
17
|
+
text: JSON.stringify({
|
|
18
|
+
request: input.request,
|
|
19
|
+
desiredVerb: routed.desiredVerb,
|
|
20
|
+
inferredAreas: routed.inferredAreas,
|
|
21
|
+
heuristicNotes: routed.heuristicNotes,
|
|
22
|
+
candidates: routed.candidates,
|
|
23
|
+
}),
|
|
24
|
+
}],
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { ThinkingGraph } from '../engine/graph.js';
|
|
2
|
+
export interface SkillRoutingInput {
|
|
3
|
+
request: string;
|
|
4
|
+
platform?: 'ios' | 'android' | 'web' | 'all';
|
|
5
|
+
goalVerb?: string;
|
|
6
|
+
areas?: string[];
|
|
7
|
+
currentState?: 'new' | 'existing' | 'unknown';
|
|
8
|
+
detectedNeeds?: string[];
|
|
9
|
+
maxCandidates?: number;
|
|
10
|
+
selectedInvocation?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface RoutedSkillCandidate {
|
|
13
|
+
id: string;
|
|
14
|
+
plugin: string;
|
|
15
|
+
skill: string;
|
|
16
|
+
invocation: string;
|
|
17
|
+
verb?: string;
|
|
18
|
+
platform?: string;
|
|
19
|
+
areas: string[];
|
|
20
|
+
detects: string[];
|
|
21
|
+
produces: string[];
|
|
22
|
+
invokes: string[];
|
|
23
|
+
score: number;
|
|
24
|
+
reasons: string[];
|
|
25
|
+
}
|
|
26
|
+
export interface SkillPlanStep {
|
|
27
|
+
stepNumber: number;
|
|
28
|
+
invocation: string;
|
|
29
|
+
plugin: string;
|
|
30
|
+
skill: string;
|
|
31
|
+
verb?: string;
|
|
32
|
+
purpose: string;
|
|
33
|
+
approvalGateIds: string[];
|
|
34
|
+
}
|
|
35
|
+
export interface ApprovalGate {
|
|
36
|
+
id: string;
|
|
37
|
+
title: string;
|
|
38
|
+
beforeStepNumber: number;
|
|
39
|
+
reason: string;
|
|
40
|
+
prompt: string;
|
|
41
|
+
}
|
|
42
|
+
export declare function buildPlanId(input: {
|
|
43
|
+
request?: string;
|
|
44
|
+
plan: SkillPlanStep[];
|
|
45
|
+
approvalGates: ApprovalGate[];
|
|
46
|
+
}): string;
|
|
47
|
+
export declare function routeSkills(graph: ThinkingGraph, input: SkillRoutingInput): Promise<{
|
|
48
|
+
desiredVerb?: string;
|
|
49
|
+
inferredAreas: string[];
|
|
50
|
+
heuristicNotes: string[];
|
|
51
|
+
candidates: RoutedSkillCandidate[];
|
|
52
|
+
}>;
|
|
53
|
+
export declare function buildSkillPlan(graph: ThinkingGraph, input: SkillRoutingInput & {
|
|
54
|
+
requireApproval?: boolean;
|
|
55
|
+
}): Promise<{
|
|
56
|
+
desiredVerb?: string;
|
|
57
|
+
inferredAreas: string[];
|
|
58
|
+
heuristicNotes: string[];
|
|
59
|
+
route: RoutedSkillCandidate | null;
|
|
60
|
+
plan: SkillPlanStep[];
|
|
61
|
+
approvalGates: ApprovalGate[];
|
|
62
|
+
fallbacks: RoutedSkillCandidate[];
|
|
63
|
+
}>;
|