fluffy-context 0.7.7 → 0.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/README.md +97 -36
- package/dist/src/agent/api.d.ts +41 -0
- package/dist/src/agent/api.js +37 -0
- package/dist/src/agent/index.d.ts +6 -1
- package/dist/src/cli/main.js +244 -6
- package/dist/src/cognition/evidence-graph.d.ts +4 -0
- package/dist/src/cognition/evidence-graph.js +187 -0
- package/dist/src/cognition/evolution.d.ts +36 -0
- package/dist/src/cognition/evolution.js +221 -0
- package/dist/src/cognition/journal.js +130 -1
- package/dist/src/cognition/projections.js +124 -2
- package/dist/src/cognition/recipes.d.ts +25 -0
- package/dist/src/cognition/recipes.js +149 -0
- package/dist/src/cognition/skills.d.ts +45 -0
- package/dist/src/cognition/skills.js +184 -0
- package/dist/src/cognition/types.d.ts +181 -1
- package/dist/src/compiler/compile.js +72 -3
- package/dist/src/compiler/graph.d.ts +7 -0
- package/dist/src/compiler/graph.js +117 -0
- package/dist/src/compiler/identity.d.ts +1 -1
- package/dist/src/compiler/identity.js +2 -0
- package/dist/src/compiler/sources.d.ts +3 -1
- package/dist/src/compiler/sources.js +10 -1
- package/dist/src/compiler/types.d.ts +22 -1
- package/dist/src/mcp/server.js +202 -1
- package/dist/src/storage/layout.d.ts +5 -0
- package/dist/src/storage/layout.js +15 -0
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/package.json +1 -1
- package/skills/fluffy-context/SKILL.md +33 -5
|
@@ -4,6 +4,7 @@ import { queryTerms } from '../cognition/retrieval.js';
|
|
|
4
4
|
import { listDeadends, listKnowledge } from '../runtime/knowledge.js';
|
|
5
5
|
import { listNotes } from '../runtime/notes.js';
|
|
6
6
|
import { extractActivity } from './activity.js';
|
|
7
|
+
import { graphOrdering } from './graph.js';
|
|
7
8
|
import { latestHostIntent, loadCompilerSources, sourceDescriptor } from './sources.js';
|
|
8
9
|
const scenarioKeywords = [
|
|
9
10
|
{ scenario: 'debugging', words: ['bug', 'error', 'fail', 'failure', 'exception', 'regression', 'debug', '失败', '报错', '异常', '修复'] },
|
|
@@ -137,6 +138,50 @@ function sortCandidates(left, right) {
|
|
|
137
138
|
|| right.matchedTerms.length - left.matchedTerms.length
|
|
138
139
|
|| stableCompare(left.candidateId, right.candidateId);
|
|
139
140
|
}
|
|
141
|
+
function recipeParticipation(sources) {
|
|
142
|
+
const recipe = sources.recipe?.recipe;
|
|
143
|
+
if (!recipe)
|
|
144
|
+
return undefined;
|
|
145
|
+
return {
|
|
146
|
+
recipeId: recipe.recipeId,
|
|
147
|
+
contentHash: recipe.contentHash,
|
|
148
|
+
interpreterVersion: 'recipe-interpreter-v1',
|
|
149
|
+
preferredSources: [...(recipe.preferredSources ?? [])],
|
|
150
|
+
preferredSections: [...(recipe.preferredSections ?? [])],
|
|
151
|
+
sectionBudgets: { ...(recipe.sectionBudgets ?? {}) },
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function sourcePreference(candidate, recipe) {
|
|
155
|
+
const source = candidate.source.kind === 'current-context' ? 'context' : candidate.source.kind;
|
|
156
|
+
const index = recipe.preferredSources.indexOf(source);
|
|
157
|
+
return index === -1 ? Number.MAX_SAFE_INTEGER : index;
|
|
158
|
+
}
|
|
159
|
+
function sectionPreference(candidate, recipe) {
|
|
160
|
+
const index = recipe.preferredSections.indexOf(candidate.section);
|
|
161
|
+
return index === -1 ? Number.MAX_SAFE_INTEGER : index;
|
|
162
|
+
}
|
|
163
|
+
function recipeApplicable(recipe, sources, paths) {
|
|
164
|
+
const applicability = sources.recipe.recipe.applicability;
|
|
165
|
+
if (applicability.branchMode === 'global')
|
|
166
|
+
return true;
|
|
167
|
+
if (applicability.branchMode !== 'exact')
|
|
168
|
+
return false;
|
|
169
|
+
const branch = sources.anchor.ref ?? sources.anchor.branch ?? 'detached';
|
|
170
|
+
if (applicability.branches?.length && !applicability.branches.includes(branch) && !applicability.branches.includes(sources.anchor.branch ?? ''))
|
|
171
|
+
return false;
|
|
172
|
+
if (applicability.sourceCommit && applicability.sourceCommit !== sources.anchor.head)
|
|
173
|
+
return false;
|
|
174
|
+
if (paths.length > 0 && applicability.paths?.length && !applicability.paths.some((path) => paths.includes(path.replaceAll('\\', '/'))))
|
|
175
|
+
return false;
|
|
176
|
+
return recipe.recipeId === sources.recipe.recipe.recipeId;
|
|
177
|
+
}
|
|
178
|
+
function sortWithRecipe(recipe) {
|
|
179
|
+
if (!recipe)
|
|
180
|
+
return sortCandidates;
|
|
181
|
+
return (left, right) => sourcePreference(left, recipe) - sourcePreference(right, recipe)
|
|
182
|
+
|| sectionPreference(left, recipe) - sectionPreference(right, recipe)
|
|
183
|
+
|| sortCandidates(left, right);
|
|
184
|
+
}
|
|
140
185
|
function truncateText(value, maxChars) {
|
|
141
186
|
if (value.length <= maxChars)
|
|
142
187
|
return { text: value, truncated: false };
|
|
@@ -237,6 +282,9 @@ export async function compileContext(startPath, options = {}) {
|
|
|
237
282
|
const sources = await loadCompilerSources(startPath, options);
|
|
238
283
|
const normalizedPaths = options.paths ? await filterPaths(sources.anchor.projectRoot, options.paths) : [];
|
|
239
284
|
const intent = intentFor(sources, options, normalizedPaths);
|
|
285
|
+
const recipe = recipeParticipation(sources);
|
|
286
|
+
if (recipe && !recipeApplicable(recipe, sources, normalizedPaths))
|
|
287
|
+
throw new Error(`compiler recipe is not applicable: ${recipe.recipeId}`);
|
|
240
288
|
const orientation = sources.orientation;
|
|
241
289
|
const maxChars = intent.budget.total;
|
|
242
290
|
const notes = orientation.status === 'ready' ? (await listNotes(sources.anchor.projectRoot, { contextId: orientation.context.id, openOnly: true, limit: options.noteLimit ?? 50, maxChars: Number.MAX_SAFE_INTEGER })).filter((note) => orientation.notes.some((selected) => selected.noteId === note.noteId)) : [];
|
|
@@ -250,14 +298,31 @@ export async function compileContext(startPath, options = {}) {
|
|
|
250
298
|
if (!check.ok)
|
|
251
299
|
excluded.push({ candidateId: candidate.candidateId, source: candidate.source, reason: check.reason ?? 'query_no_match' });
|
|
252
300
|
return check.ok;
|
|
253
|
-
}).sort(
|
|
301
|
+
}).sort(sortWithRecipe(recipe));
|
|
302
|
+
const graph = options.graphHops === undefined ? undefined : graphOrdering(sources.evidenceGraph, options.graphHops, sources.anchor, eligible);
|
|
303
|
+
if (graph) {
|
|
304
|
+
eligible.sort((left, right) => {
|
|
305
|
+
if (recipe) {
|
|
306
|
+
const sourceOrder = sourcePreference(left, recipe) - sourcePreference(right, recipe);
|
|
307
|
+
if (sourceOrder)
|
|
308
|
+
return sourceOrder;
|
|
309
|
+
const sectionOrder = sectionPreference(left, recipe) - sectionPreference(right, recipe);
|
|
310
|
+
if (sectionOrder)
|
|
311
|
+
return sectionOrder;
|
|
312
|
+
}
|
|
313
|
+
return (graph.distanceFor(left) ?? Number.MAX_SAFE_INTEGER) - (graph.distanceFor(right) ?? Number.MAX_SAFE_INTEGER)
|
|
314
|
+
|| sortCandidates(left, right);
|
|
315
|
+
});
|
|
316
|
+
}
|
|
254
317
|
const selected = [];
|
|
255
318
|
const truncation = emptyTruncation();
|
|
256
319
|
const sectionStats = {};
|
|
257
320
|
let usedChars = 0;
|
|
258
321
|
for (const candidate of eligible) {
|
|
259
322
|
const separator = selected.length > 0 ? 1 : 0;
|
|
260
|
-
const
|
|
323
|
+
const sectionBudget = recipe?.sectionBudgets[candidate.section] ?? maxChars;
|
|
324
|
+
const consumedInSection = sectionStats[candidate.section]?.consumed ?? 0;
|
|
325
|
+
const available = Math.min(maxChars - usedChars - separator, sectionBudget - consumedInSection - separator);
|
|
261
326
|
if (available <= 0) {
|
|
262
327
|
excluded.push({ candidateId: candidate.candidateId, source: candidate.source, reason: 'budget' });
|
|
263
328
|
truncation.candidatesOmittedByBudget += 1;
|
|
@@ -274,7 +339,7 @@ export async function compileContext(startPath, options = {}) {
|
|
|
274
339
|
const item = { ...candidate, text: limited.text, selectedChars: limited.text.length, selected: true, truncated: limited.truncated };
|
|
275
340
|
selected.push(item);
|
|
276
341
|
usedChars += separator + item.selectedChars;
|
|
277
|
-
const prior = sectionStats[item.section] ?? { allocated: maxChars, consumed: 0, truncated: false };
|
|
342
|
+
const prior = sectionStats[item.section] ?? { allocated: recipe?.sectionBudgets[item.section] ?? maxChars, consumed: 0, truncated: false };
|
|
278
343
|
sectionStats[item.section] = { ...prior, consumed: prior.consumed + item.selectedChars + separator, truncated: prior.truncated || limited.truncated };
|
|
279
344
|
if (limited.truncated) {
|
|
280
345
|
truncation.candidatesPartiallyTruncated += 1;
|
|
@@ -318,6 +383,8 @@ export async function compileContext(startPath, options = {}) {
|
|
|
318
383
|
selected,
|
|
319
384
|
budget: { requested: intent.budget, usedChars, remainingChars: Math.max(0, maxChars - usedChars), sections: sectionStats },
|
|
320
385
|
truncation,
|
|
386
|
+
...(recipe ? { recipe } : {}),
|
|
387
|
+
...(graph ? { graph: graph.participation } : {}),
|
|
321
388
|
});
|
|
322
389
|
return {
|
|
323
390
|
schemaVersion: 1,
|
|
@@ -348,6 +415,8 @@ export async function compileContext(startPath, options = {}) {
|
|
|
348
415
|
selectedEventIds: [...new Set(selected.flatMap((item) => item.source.provenance.eventId ? [item.source.provenance.eventId] : []))].sort(stableCompare),
|
|
349
416
|
fallbackReason: sources.fallbackReason,
|
|
350
417
|
},
|
|
418
|
+
...(recipe ? { recipe } : {}),
|
|
419
|
+
...(graph ? { graph: graph.participation } : {}),
|
|
351
420
|
text: manifestText(selected),
|
|
352
421
|
};
|
|
353
422
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { EvidenceGraphProjection } from '../cognition/types.js';
|
|
2
|
+
import type { ContextAnchor, ContextCandidate, ContextGraphParticipation } from './types.js';
|
|
3
|
+
export interface GraphOrdering {
|
|
4
|
+
participation: ContextGraphParticipation;
|
|
5
|
+
distanceFor(candidate: ContextCandidate): number | null;
|
|
6
|
+
}
|
|
7
|
+
export declare function graphOrdering(graph: EvidenceGraphProjection | null, hops: 0 | 1 | 2, anchor: ContextAnchor, eligible: ContextCandidate[]): GraphOrdering;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { EVIDENCE_GRAPH_DERIVATION_VERSION, workspaceNodeId } from '../cognition/evidence-graph.js';
|
|
2
|
+
import { contentHash } from './identity.js';
|
|
3
|
+
const MAX_VISITED_NODES = 256;
|
|
4
|
+
const MAX_TRAVERSED_EDGES = 512;
|
|
5
|
+
const MAX_FAN_OUT = 32;
|
|
6
|
+
function compare(left, right) {
|
|
7
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
8
|
+
}
|
|
9
|
+
function candidateNodeId(candidate) {
|
|
10
|
+
if (candidate.section === 'knowledge')
|
|
11
|
+
return `knowledge:${candidate.source.entityId}`;
|
|
12
|
+
if (candidate.section === 'deadend')
|
|
13
|
+
return `deadend:${candidate.source.entityId}`;
|
|
14
|
+
if (candidate.source.kind === 'current-context')
|
|
15
|
+
return `context:${candidate.source.contextId ?? candidate.source.entityId}`;
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
function validGraph(graph) {
|
|
19
|
+
if (graph.schemaVersion !== 1 || graph.derivationVersion !== EVIDENCE_GRAPH_DERIVATION_VERSION || !Array.isArray(graph.nodes) || !Array.isArray(graph.edges))
|
|
20
|
+
return false;
|
|
21
|
+
const nodeKinds = new Set(['context', 'snapshot', 'experience', 'proposal', 'knowledge', 'deadend', 'skill', 'recipe', 'path', 'test', 'commit', 'branch-worktree']);
|
|
22
|
+
const edgeTypes = new Set(['derived_from', 'supports', 'contradicts', 'supersedes', 'used_for', 'observed_in', 'changed_in', 'co_changed_with', 'tests']);
|
|
23
|
+
const nodes = new Set();
|
|
24
|
+
for (const node of graph.nodes) {
|
|
25
|
+
if (!node || typeof node.nodeId !== 'string' || node.nodeId.length === 0 || !nodeKinds.has(node.kind) || nodes.has(node.nodeId))
|
|
26
|
+
return false;
|
|
27
|
+
nodes.add(node.nodeId);
|
|
28
|
+
}
|
|
29
|
+
const edges = new Set();
|
|
30
|
+
for (const edge of graph.edges) {
|
|
31
|
+
if (!edge || typeof edge.edgeId !== 'string' || edge.edgeId.length === 0 || edges.has(edge.edgeId) || !nodes.has(edge.from) || !nodes.has(edge.to) || !edgeTypes.has(edge.type) || edge.derivationVersion !== EVIDENCE_GRAPH_DERIVATION_VERSION || typeof edge.occurredAt !== 'string' || edge.occurredAt.length === 0 || !Array.isArray(edge.sourceEventIds) || edge.sourceEventIds.some((id) => typeof id !== 'string' || !id) || (edge.confidence !== undefined && (typeof edge.confidence !== 'number' || !Number.isFinite(edge.confidence) || edge.confidence < 0 || edge.confidence > 1)))
|
|
32
|
+
return false;
|
|
33
|
+
edges.add(edge.edgeId);
|
|
34
|
+
}
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
function graphProjectionHash(graph) {
|
|
38
|
+
return contentHash(graph);
|
|
39
|
+
}
|
|
40
|
+
export function graphOrdering(graph, hops, anchor, eligible) {
|
|
41
|
+
const empty = (fallbackReason, seedNodeIds = []) => ({
|
|
42
|
+
participation: {
|
|
43
|
+
derivationVersion: EVIDENCE_GRAPH_DERIVATION_VERSION,
|
|
44
|
+
projectionHash: null,
|
|
45
|
+
hops,
|
|
46
|
+
seedNodeIds,
|
|
47
|
+
selectedNodeIds: [],
|
|
48
|
+
selectedEdgeIds: [],
|
|
49
|
+
fallbackReason,
|
|
50
|
+
},
|
|
51
|
+
distanceFor: () => null,
|
|
52
|
+
});
|
|
53
|
+
if (!graph)
|
|
54
|
+
return empty('graph-unavailable');
|
|
55
|
+
if (!validGraph(graph))
|
|
56
|
+
return empty('graph-invalid');
|
|
57
|
+
const projectionHash = graphProjectionHash(graph);
|
|
58
|
+
const nodes = new Set(graph.nodes.map((node) => node.nodeId));
|
|
59
|
+
const candidateNodes = new Map(eligible.map((candidate) => [candidate.candidateId, candidateNodeId(candidate)]));
|
|
60
|
+
const seedNodeIds = [...new Set([
|
|
61
|
+
workspaceNodeId(anchor),
|
|
62
|
+
...eligible.filter((candidate) => candidate.source.kind === 'current-context').map(candidateNodeId).filter((node) => node !== null),
|
|
63
|
+
].filter((node) => nodes.has(node)))].sort(compare);
|
|
64
|
+
if (seedNodeIds.length === 0)
|
|
65
|
+
return empty('graph-no-seeds');
|
|
66
|
+
if (hops === 0) {
|
|
67
|
+
const distances = new Map(seedNodeIds.map((node) => [node, 0]));
|
|
68
|
+
return {
|
|
69
|
+
participation: { derivationVersion: EVIDENCE_GRAPH_DERIVATION_VERSION, projectionHash, hops, seedNodeIds, selectedNodeIds: seedNodeIds, selectedEdgeIds: [], fallbackReason: null },
|
|
70
|
+
distanceFor: (candidate) => {
|
|
71
|
+
const node = candidateNodes.get(candidate.candidateId);
|
|
72
|
+
return node ? distances.get(node) ?? null : null;
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const adjacency = new Map();
|
|
77
|
+
for (const edge of graph.edges) {
|
|
78
|
+
adjacency.set(edge.from, [...(adjacency.get(edge.from) ?? []), edge]);
|
|
79
|
+
adjacency.set(edge.to, [...(adjacency.get(edge.to) ?? []), edge]);
|
|
80
|
+
}
|
|
81
|
+
for (const edges of adjacency.values())
|
|
82
|
+
edges.sort((left, right) => compare(left.edgeId, right.edgeId));
|
|
83
|
+
const distances = new Map(seedNodeIds.map((node) => [node, 0]));
|
|
84
|
+
const queue = [...seedNodeIds];
|
|
85
|
+
const selectedEdgeIds = new Set();
|
|
86
|
+
while (queue.length > 0 && distances.size < MAX_VISITED_NODES && selectedEdgeIds.size < MAX_TRAVERSED_EDGES) {
|
|
87
|
+
const node = queue.shift();
|
|
88
|
+
const distance = distances.get(node);
|
|
89
|
+
if (distance >= hops)
|
|
90
|
+
continue;
|
|
91
|
+
for (const edge of (adjacency.get(node) ?? []).slice(0, MAX_FAN_OUT)) {
|
|
92
|
+
if (selectedEdgeIds.size >= MAX_TRAVERSED_EDGES)
|
|
93
|
+
break;
|
|
94
|
+
const adjacent = edge.from === node ? edge.to : edge.from;
|
|
95
|
+
selectedEdgeIds.add(edge.edgeId);
|
|
96
|
+
if (!distances.has(adjacent) && distances.size < MAX_VISITED_NODES) {
|
|
97
|
+
distances.set(adjacent, distance + 1);
|
|
98
|
+
queue.push(adjacent);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
participation: {
|
|
104
|
+
derivationVersion: EVIDENCE_GRAPH_DERIVATION_VERSION,
|
|
105
|
+
projectionHash,
|
|
106
|
+
hops,
|
|
107
|
+
seedNodeIds,
|
|
108
|
+
selectedNodeIds: [...distances.keys()].sort(compare),
|
|
109
|
+
selectedEdgeIds: [...selectedEdgeIds].sort(compare),
|
|
110
|
+
fallbackReason: null,
|
|
111
|
+
},
|
|
112
|
+
distanceFor: (candidate) => {
|
|
113
|
+
const node = candidateNodes.get(candidate.candidateId);
|
|
114
|
+
return node ? distances.get(node) ?? null : null;
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
@@ -2,5 +2,5 @@ import type { ContextManifest } from './types.js';
|
|
|
2
2
|
export declare function canonicalJson(value: unknown): string;
|
|
3
3
|
export declare function contentHash(value: unknown): string;
|
|
4
4
|
export declare function estimateTokens(value: string): number;
|
|
5
|
-
export declare function rebuildManifestHash(manifest: Pick<ContextManifest, 'status' | 'projectRoot' | 'mode' | 'anchor' | 'intent' | 'selected' | 'budget' | 'truncation'>): string;
|
|
5
|
+
export declare function rebuildManifestHash(manifest: Pick<ContextManifest, 'status' | 'projectRoot' | 'mode' | 'anchor' | 'intent' | 'selected' | 'budget' | 'truncation' | 'recipe' | 'graph'>): string;
|
|
6
6
|
export declare function validateManifestIdentity(manifest: ContextManifest): boolean;
|
|
@@ -26,6 +26,8 @@ export function rebuildManifestHash(manifest) {
|
|
|
26
26
|
selectedItemHashes: manifest.selected.map((item) => item.itemHash),
|
|
27
27
|
budget: { usedChars: manifest.budget.usedChars, maxChars: manifest.budget.requested.total },
|
|
28
28
|
truncation: manifest.truncation,
|
|
29
|
+
...(manifest.recipe ? { recipe: { recipeId: manifest.recipe.recipeId, contentHash: manifest.recipe.contentHash, interpreterVersion: manifest.recipe.interpreterVersion } } : {}),
|
|
30
|
+
...(manifest.graph ? { graph: manifest.graph } : {}),
|
|
29
31
|
});
|
|
30
32
|
}
|
|
31
33
|
export function validateManifestIdentity(manifest) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BranchWorkspace, CognitionEvent } from '../cognition/types.js';
|
|
1
|
+
import type { BranchWorkspace, CognitionEvent, CompilerRecipeState, EvidenceGraphProjection } from '../cognition/types.js';
|
|
2
2
|
import type { ContextOrientResult } from '../agent/types.js';
|
|
3
3
|
import type { ContextAnchor, ContextProvenance, ContextSourceDescriptor, ContextSourceApplicability } from './types.js';
|
|
4
4
|
import type { ContextCompileOptions } from './types.js';
|
|
@@ -7,6 +7,8 @@ export interface CompilerSources {
|
|
|
7
7
|
anchor: ContextAnchor;
|
|
8
8
|
events: CognitionEvent[];
|
|
9
9
|
workspace: BranchWorkspace | null;
|
|
10
|
+
recipe: CompilerRecipeState | null;
|
|
11
|
+
evidenceGraph: EvidenceGraphProjection | null;
|
|
10
12
|
fallbackReason: string | null;
|
|
11
13
|
}
|
|
12
14
|
export declare function sourceApplicability(event: CognitionEvent | null, anchor: ContextAnchor): ContextSourceApplicability;
|
|
@@ -107,7 +107,16 @@ export async function loadCompilerSources(startPath, options) {
|
|
|
107
107
|
const anchor = anchorFromGit(git, projectRoot);
|
|
108
108
|
const projection = events.length > 0 ? projectEvents(events) : null;
|
|
109
109
|
const workspace = projection?.workspaces[workspaceKey(anchor)] ?? null;
|
|
110
|
-
|
|
110
|
+
const recipeId = options.recipeId?.trim();
|
|
111
|
+
if (options.recipeId !== undefined && !recipeId)
|
|
112
|
+
throw new Error('compiler recipe ID must not be empty');
|
|
113
|
+
const recipe = recipeId ? projection?.recipes[recipeId] ?? null : null;
|
|
114
|
+
if (recipeId && !recipe)
|
|
115
|
+
throw new Error(`compiler recipe not found: ${recipeId}`);
|
|
116
|
+
if (recipe && !['verified', 'published'].includes(recipe.status))
|
|
117
|
+
throw new Error(`compiler recipe is not active: ${recipe.recipe.recipeId}`);
|
|
118
|
+
const evidenceGraph = options.graphHops !== undefined ? projection?.evidenceGraph ?? null : null;
|
|
119
|
+
return { orientation, anchor, events, workspace, recipe, evidenceGraph, fallbackReason };
|
|
111
120
|
}
|
|
112
121
|
export function latestHostIntent(events, anchor) {
|
|
113
122
|
const event = [...events].reverse().find((candidate) => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CognitionGitAnchor, WorkspacePathChange } from '../cognition/types.js';
|
|
1
|
+
import type { CognitionGitAnchor, CompilerRecipeSection, CompilerRecipeSource, WorkspacePathChange } from '../cognition/types.js';
|
|
2
2
|
import type { ContextOrientResult } from '../agent/types.js';
|
|
3
3
|
import type { Deadend, Knowledge, Note, ResumeSummary, StructuredContext } from '../runtime/types.js';
|
|
4
4
|
export type ContextScenario = 'resume' | 'implementation' | 'debugging' | 'verification' | 'handoff' | 'exploration' | 'unknown';
|
|
@@ -155,8 +155,27 @@ export interface ContextManifestIdentity {
|
|
|
155
155
|
selectedItemHashes: string[];
|
|
156
156
|
tokenEstimate: ContextTokenEstimate;
|
|
157
157
|
}
|
|
158
|
+
export interface ContextRecipeParticipation {
|
|
159
|
+
recipeId: string;
|
|
160
|
+
contentHash: string;
|
|
161
|
+
interpreterVersion: 'recipe-interpreter-v1';
|
|
162
|
+
preferredSources: CompilerRecipeSource[];
|
|
163
|
+
preferredSections: CompilerRecipeSection[];
|
|
164
|
+
sectionBudgets: Partial<Record<ContextCandidateSection, number>>;
|
|
165
|
+
}
|
|
166
|
+
export interface ContextGraphParticipation {
|
|
167
|
+
derivationVersion: 'evidence-graph-v1';
|
|
168
|
+
projectionHash: string | null;
|
|
169
|
+
hops: 0 | 1 | 2;
|
|
170
|
+
seedNodeIds: string[];
|
|
171
|
+
selectedNodeIds: string[];
|
|
172
|
+
selectedEdgeIds: string[];
|
|
173
|
+
fallbackReason: string | null;
|
|
174
|
+
}
|
|
158
175
|
export interface ContextCompileOptions {
|
|
159
176
|
contextId?: string;
|
|
177
|
+
recipeId?: string;
|
|
178
|
+
graphHops?: 0 | 1 | 2;
|
|
160
179
|
query?: string;
|
|
161
180
|
scenario?: ContextScenario;
|
|
162
181
|
scope?: string;
|
|
@@ -227,6 +246,8 @@ export interface ContextManifest {
|
|
|
227
246
|
budget: ContextBudgetReport;
|
|
228
247
|
truncation: ContextTruncation;
|
|
229
248
|
provenance: ContextManifestProvenance;
|
|
249
|
+
recipe?: ContextRecipeParticipation;
|
|
250
|
+
graph?: ContextGraphParticipation;
|
|
230
251
|
text: string;
|
|
231
252
|
}
|
|
232
253
|
export type CompilerRecord = StructuredContext | Note | Knowledge | Deadend;
|
package/dist/src/mcp/server.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/server';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { addNote, listNotes } from '../runtime/notes.js';
|
|
4
|
-
import { contextCompile, contextExpand, contextOrient, contextUsageReport, loadContext, saveContext } from '../agent/api.js';
|
|
4
|
+
import { contextCompile, contextExpand, contextOrient, contextUsageReport, inspectRecipe, inspectSkill, listRecipes, listSkills, loadContext, materializeContextRecipe, materializeContextSkill, recordContextSkillInvocation, recordContextSkillOutcome, saveContext, transitionContextRecipe, transitionContextSkill, acceptContextEvolutionCandidate, inspectContextEvolutionProposal, listContextEvolutionProposals, proposeContextEvolution, transitionContextEvolutionProposal } from '../agent/api.js';
|
|
5
5
|
import { ensureRuntimeForSession } from '../cognition/runtime.js';
|
|
6
6
|
import { VERSION } from '../version.js';
|
|
7
7
|
const orientInput = z.object({
|
|
@@ -21,6 +21,8 @@ const noteActor = z.enum(['agent', 'human']);
|
|
|
21
21
|
const noteStatus = z.enum(['open', 'resolved']);
|
|
22
22
|
const compileOptions = z.object({
|
|
23
23
|
contextId: z.string().optional(),
|
|
24
|
+
recipeId: z.string().optional(),
|
|
25
|
+
graphHops: z.union([z.literal(0), z.literal(1), z.literal(2)]).optional(),
|
|
24
26
|
query: z.string().optional(),
|
|
25
27
|
scenario: scenario.optional(),
|
|
26
28
|
scope: z.string().optional(),
|
|
@@ -93,6 +95,25 @@ const usageReportInput = z.object({
|
|
|
93
95
|
baselineTokens: z.number().nonnegative().optional(),
|
|
94
96
|
baselineSource: z.enum(['manual', 'recorded-control']).optional(),
|
|
95
97
|
});
|
|
98
|
+
const skillOutcome = z.enum(['success', 'failure', 'partial', 'irrelevant', 'unknown']);
|
|
99
|
+
const recipeEntity = z.enum(['knowledge', 'deadend', 'skill', 'compiler-recipe', 'experience', 'proposal']);
|
|
100
|
+
const recipeListInput = z.object({ path: z.string().optional() });
|
|
101
|
+
const recipeInspectInput = z.object({ path: z.string().optional(), recipeId: z.string() });
|
|
102
|
+
const recipeMaterializeInput = z.object({ path: z.string().optional(), proposalId: z.string(), title: z.string(), preferredSources: z.array(z.enum(['context', 'knowledge', 'deadend', 'note'])).optional(), preferredSections: z.array(z.enum(['summary', 'pending', 'decision', 'risk', 'note', 'knowledge', 'deadend', 'activity'])).optional(), sectionBudgets: z.record(z.string(), z.int().nonnegative().max(100_000)).optional() });
|
|
103
|
+
const recipeTransitionInput = z.object({ path: z.string().optional(), recipeId: z.string(), to: z.enum(['verified', 'published', 'deprecated', 'superseded']), rationale: z.string(), supersedes: z.object({ entity: recipeEntity, entityId: z.string(), relation: z.literal('supersedes') }).optional() });
|
|
104
|
+
const skillEntity = z.enum(['knowledge', 'deadend', 'skill', 'compiler-recipe', 'experience', 'proposal']);
|
|
105
|
+
const skillListInput = z.object({ path: z.string().optional() });
|
|
106
|
+
const skillInspectInput = z.object({ path: z.string().optional(), skillId: z.string() });
|
|
107
|
+
const skillMaterializeInput = z.object({ path: z.string().optional(), proposalId: z.string(), title: z.string(), procedure: z.array(z.string()) });
|
|
108
|
+
const skillTransitionInput = z.object({ path: z.string().optional(), skillId: z.string(), to: z.enum(['verified', 'published', 'deprecated', 'superseded']), rationale: z.string(), supersedes: z.object({ entity: skillEntity, entityId: z.string(), relation: z.literal('supersedes') }).optional() });
|
|
109
|
+
const skillInvocationInput = z.object({ path: z.string().optional(), skillId: z.string(), invocationId: z.string(), callerEventId: z.string(), inputHash: z.string() });
|
|
110
|
+
const skillOutcomeInput = z.object({ path: z.string().optional(), skillId: z.string(), invocationId: z.string(), outcomeId: z.string(), callerEventId: z.string(), outcome: skillOutcome, summary: z.string() });
|
|
111
|
+
const evolutionEntity = z.enum(['knowledge', 'deadend', 'skill', 'compiler-recipe', 'experience', 'proposal']);
|
|
112
|
+
const evolutionProposeInput = z.object({ path: z.string().optional(), eventIds: z.array(z.string()).min(1).max(32).optional(), recentLimit: z.int().nonnegative().max(32).optional(), targetKind: z.enum(['skill', 'compiler-recipe']).optional() });
|
|
113
|
+
const evolutionListInput = z.object({ path: z.string().optional() });
|
|
114
|
+
const evolutionInspectInput = z.object({ path: z.string().optional(), proposalId: z.string() });
|
|
115
|
+
const evolutionAcceptInput = z.object({ path: z.string().optional(), proposalId: z.string(), rationale: z.string().min(1) });
|
|
116
|
+
const evolutionTransitionInput = z.object({ path: z.string().optional(), proposalId: z.string(), to: z.enum(['verified', 'rejected', 'deprecated', 'superseded']), rationale: z.string().min(1), supersedes: z.object({ entity: evolutionEntity, entityId: z.string(), relation: z.literal('supersedes') }).optional() });
|
|
96
117
|
function errorMessage(error) {
|
|
97
118
|
return error instanceof Error ? error.message : String(error);
|
|
98
119
|
}
|
|
@@ -200,6 +221,186 @@ export function createMcpServer() {
|
|
|
200
221
|
return failureResponse('context_usage_report', error);
|
|
201
222
|
}
|
|
202
223
|
});
|
|
224
|
+
server.registerTool('recipe_list', {
|
|
225
|
+
title: 'List Recipes',
|
|
226
|
+
description: 'List immutable v2 compiler Recipes without modifying project state.',
|
|
227
|
+
inputSchema: recipeListInput,
|
|
228
|
+
}, async ({ path }) => {
|
|
229
|
+
try {
|
|
230
|
+
return successResponse(await listRecipes(path));
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
return failureResponse('recipe_list', error);
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
server.registerTool('recipe_inspect', {
|
|
237
|
+
title: 'Inspect Recipe',
|
|
238
|
+
description: 'Inspect a compiler Recipe and its lifecycle.',
|
|
239
|
+
inputSchema: recipeInspectInput,
|
|
240
|
+
}, async ({ path, recipeId }) => {
|
|
241
|
+
try {
|
|
242
|
+
return successResponse(await inspectRecipe(path, recipeId));
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
return failureResponse('recipe_inspect', error);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
server.registerTool('recipe_materialize', {
|
|
249
|
+
title: 'Materialize Recipe',
|
|
250
|
+
description: 'Materialize a Recipe from an accepted compiler-recipe proposal.',
|
|
251
|
+
inputSchema: recipeMaterializeInput,
|
|
252
|
+
}, async ({ path, ...input }) => {
|
|
253
|
+
try {
|
|
254
|
+
return successResponse(await materializeContextRecipe(path, input));
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
return failureResponse('recipe_materialize', error);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
server.registerTool('recipe_transition', {
|
|
261
|
+
title: 'Transition Recipe',
|
|
262
|
+
description: 'Apply an explicit append-only Recipe lifecycle transition.',
|
|
263
|
+
inputSchema: recipeTransitionInput,
|
|
264
|
+
}, async ({ path, ...input }) => {
|
|
265
|
+
try {
|
|
266
|
+
return successResponse(await transitionContextRecipe(path, input));
|
|
267
|
+
}
|
|
268
|
+
catch (error) {
|
|
269
|
+
return failureResponse('recipe_transition', error);
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
server.registerTool('skill_list', {
|
|
273
|
+
title: 'List Skills',
|
|
274
|
+
description: 'List immutable v2 Skills without modifying project state.',
|
|
275
|
+
inputSchema: skillListInput,
|
|
276
|
+
}, async ({ path }) => {
|
|
277
|
+
try {
|
|
278
|
+
return successResponse(await listSkills(path));
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
return failureResponse('skill_list', error);
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
server.registerTool('skill_inspect', {
|
|
285
|
+
title: 'Inspect Skill',
|
|
286
|
+
description: 'Inspect an immutable v2 Skill and its lifecycle without modifying project state.',
|
|
287
|
+
inputSchema: skillInspectInput,
|
|
288
|
+
}, async ({ path, skillId }) => {
|
|
289
|
+
try {
|
|
290
|
+
return successResponse(await inspectSkill(path, skillId));
|
|
291
|
+
}
|
|
292
|
+
catch (error) {
|
|
293
|
+
return failureResponse('skill_inspect', error);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
server.registerTool('skill_materialize', {
|
|
297
|
+
title: 'Materialize Skill',
|
|
298
|
+
description: 'Materialize a Skill from an accepted candidate Skill proposal.',
|
|
299
|
+
inputSchema: skillMaterializeInput,
|
|
300
|
+
}, async ({ path, ...input }) => {
|
|
301
|
+
try {
|
|
302
|
+
return successResponse(await materializeContextSkill(path, input));
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
return failureResponse('skill_materialize', error);
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
server.registerTool('skill_transition', {
|
|
309
|
+
title: 'Transition Skill',
|
|
310
|
+
description: 'Apply an explicit append-only Skill lifecycle transition.',
|
|
311
|
+
inputSchema: skillTransitionInput,
|
|
312
|
+
}, async ({ path, ...input }) => {
|
|
313
|
+
try {
|
|
314
|
+
return successResponse(await transitionContextSkill(path, input));
|
|
315
|
+
}
|
|
316
|
+
catch (error) {
|
|
317
|
+
return failureResponse('skill_transition', error);
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
server.registerTool('skill_record_invocation', {
|
|
321
|
+
title: 'Record Skill invocation',
|
|
322
|
+
description: 'Record a published Skill invocation using only an input hash.',
|
|
323
|
+
inputSchema: skillInvocationInput,
|
|
324
|
+
}, async ({ path, ...input }) => {
|
|
325
|
+
try {
|
|
326
|
+
return successResponse(await recordContextSkillInvocation(path, input));
|
|
327
|
+
}
|
|
328
|
+
catch (error) {
|
|
329
|
+
return failureResponse('skill_record_invocation', error);
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
server.registerTool('skill_record_outcome', {
|
|
333
|
+
title: 'Record Skill outcome',
|
|
334
|
+
description: 'Record one explicit terminal outcome for a Skill invocation.',
|
|
335
|
+
inputSchema: skillOutcomeInput,
|
|
336
|
+
}, async ({ path, ...input }) => {
|
|
337
|
+
try {
|
|
338
|
+
return successResponse(await recordContextSkillOutcome(path, input));
|
|
339
|
+
}
|
|
340
|
+
catch (error) {
|
|
341
|
+
return failureResponse('skill_record_outcome', error);
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
server.registerTool('evolution_propose', {
|
|
345
|
+
title: 'Propose Evolution',
|
|
346
|
+
description: 'Derive a bounded candidate Evolution proposal from authorized metadata.',
|
|
347
|
+
inputSchema: evolutionProposeInput,
|
|
348
|
+
}, async ({ path, ...input }) => {
|
|
349
|
+
try {
|
|
350
|
+
return successResponse(await proposeContextEvolution(path, input));
|
|
351
|
+
}
|
|
352
|
+
catch (error) {
|
|
353
|
+
return failureResponse('evolution_propose', error);
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
server.registerTool('evolution_list', {
|
|
357
|
+
title: 'List Evolution proposals',
|
|
358
|
+
description: 'List Evolution proposals without modifying project state.',
|
|
359
|
+
inputSchema: evolutionListInput,
|
|
360
|
+
}, async ({ path }) => {
|
|
361
|
+
try {
|
|
362
|
+
return successResponse(await listContextEvolutionProposals(path));
|
|
363
|
+
}
|
|
364
|
+
catch (error) {
|
|
365
|
+
return failureResponse('evolution_list', error);
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
server.registerTool('evolution_inspect', {
|
|
369
|
+
title: 'Inspect Evolution proposal',
|
|
370
|
+
description: 'Inspect one Evolution proposal and its lifecycle.',
|
|
371
|
+
inputSchema: evolutionInspectInput,
|
|
372
|
+
}, async ({ path, proposalId }) => {
|
|
373
|
+
try {
|
|
374
|
+
return successResponse(await inspectContextEvolutionProposal(path, proposalId));
|
|
375
|
+
}
|
|
376
|
+
catch (error) {
|
|
377
|
+
return failureResponse('evolution_inspect', error);
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
server.registerTool('evolution_accept', {
|
|
381
|
+
title: 'Accept Evolution candidate',
|
|
382
|
+
description: 'Accept a proposal into an immutable candidate artifact.',
|
|
383
|
+
inputSchema: evolutionAcceptInput,
|
|
384
|
+
}, async ({ path, ...input }) => {
|
|
385
|
+
try {
|
|
386
|
+
return successResponse(await acceptContextEvolutionCandidate(path, input));
|
|
387
|
+
}
|
|
388
|
+
catch (error) {
|
|
389
|
+
return failureResponse('evolution_accept', error);
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
server.registerTool('evolution_transition', {
|
|
393
|
+
title: 'Transition Evolution proposal',
|
|
394
|
+
description: 'Apply an explicit append-only Evolution proposal lifecycle transition.',
|
|
395
|
+
inputSchema: evolutionTransitionInput,
|
|
396
|
+
}, async ({ path, ...input }) => {
|
|
397
|
+
try {
|
|
398
|
+
return successResponse(await transitionContextEvolutionProposal(path, input));
|
|
399
|
+
}
|
|
400
|
+
catch (error) {
|
|
401
|
+
return failureResponse('evolution_transition', error);
|
|
402
|
+
}
|
|
403
|
+
});
|
|
203
404
|
server.registerTool('context_checkpoint', {
|
|
204
405
|
title: 'Checkpoint context',
|
|
205
406
|
description: 'Explicitly save context progress and create a Snapshot when changes are present; runtime rate limits remain enforced.',
|
|
@@ -22,6 +22,11 @@ export declare function cognitionContextsProjectionPath(projectRoot: string): st
|
|
|
22
22
|
export declare function cognitionKnowledgeProjectionPath(projectRoot: string): string;
|
|
23
23
|
export declare function cognitionDeadendsProjectionPath(projectRoot: string): string;
|
|
24
24
|
export declare function cognitionNotesProjectionPath(projectRoot: string): string;
|
|
25
|
+
export declare function cognitionExperiencesProjectionPath(projectRoot: string): string;
|
|
26
|
+
export declare function cognitionProposalsProjectionPath(projectRoot: string): string;
|
|
27
|
+
export declare function cognitionSkillsProjectionPath(projectRoot: string): string;
|
|
28
|
+
export declare function cognitionRecipesProjectionPath(projectRoot: string): string;
|
|
29
|
+
export declare function cognitionEvidenceGraphProjectionPath(projectRoot: string): string;
|
|
25
30
|
export declare function cognitionRetrievalProjectionPath(projectRoot: string): string;
|
|
26
31
|
export declare function cognitionMigrationsDirectory(projectRoot: string): string;
|
|
27
32
|
export declare function cognitionV1ImportPath(projectRoot: string): string;
|
|
@@ -71,6 +71,21 @@ export function cognitionDeadendsProjectionPath(projectRoot) {
|
|
|
71
71
|
export function cognitionNotesProjectionPath(projectRoot) {
|
|
72
72
|
return path.join(cognitionProjectionsDirectory(projectRoot), 'notes.json');
|
|
73
73
|
}
|
|
74
|
+
export function cognitionExperiencesProjectionPath(projectRoot) {
|
|
75
|
+
return path.join(cognitionProjectionsDirectory(projectRoot), 'experiences.json');
|
|
76
|
+
}
|
|
77
|
+
export function cognitionProposalsProjectionPath(projectRoot) {
|
|
78
|
+
return path.join(cognitionProjectionsDirectory(projectRoot), 'proposals.json');
|
|
79
|
+
}
|
|
80
|
+
export function cognitionSkillsProjectionPath(projectRoot) {
|
|
81
|
+
return path.join(cognitionProjectionsDirectory(projectRoot), 'skills.json');
|
|
82
|
+
}
|
|
83
|
+
export function cognitionRecipesProjectionPath(projectRoot) {
|
|
84
|
+
return path.join(cognitionProjectionsDirectory(projectRoot), 'recipes.json');
|
|
85
|
+
}
|
|
86
|
+
export function cognitionEvidenceGraphProjectionPath(projectRoot) {
|
|
87
|
+
return path.join(cognitionProjectionsDirectory(projectRoot), 'evidence-graph.json');
|
|
88
|
+
}
|
|
74
89
|
export function cognitionRetrievalProjectionPath(projectRoot) {
|
|
75
90
|
return path.join(cognitionProjectionsDirectory(projectRoot), 'retrieval.json');
|
|
76
91
|
}
|
package/dist/src/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "0.
|
|
1
|
+
export declare const VERSION = "0.8.0";
|
package/dist/src/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '0.
|
|
1
|
+
export const VERSION = '0.8.0';
|