fluffy-context 0.7.6 → 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 +115 -36
- package/dist/src/agent/api.d.ts +49 -4
- package/dist/src/agent/api.js +183 -131
- package/dist/src/agent/index.d.ts +7 -1
- package/dist/src/agent/index.js +1 -0
- package/dist/src/cli/main.js +301 -15
- 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 +143 -2
- 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 +196 -2
- package/dist/src/cognition/usage-report.d.ts +40 -0
- package/dist/src/cognition/usage-report.js +232 -0
- package/dist/src/cognition/usage.d.ts +5 -0
- package/dist/src/cognition/usage.js +48 -0
- package/dist/src/compiler/compile.js +73 -4
- 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 +3 -1
- package/dist/src/compiler/sources.d.ts +3 -1
- package/dist/src/compiler/sources.js +11 -2
- package/dist/src/compiler/types.d.ts +23 -2
- package/dist/src/mcp/server.js +228 -6
- 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 +53 -5
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import { readGitWorktreeState } from '../git/git-adapter.js';
|
|
3
|
+
import { resolveProjectRoot } from '../project/project-resolver.js';
|
|
4
|
+
import { inspectEvolutionProposal } from './evolution.js';
|
|
5
|
+
import { appendEvent, readJournal } from './journal.js';
|
|
6
|
+
import { v2Enabled } from './orient.js';
|
|
7
|
+
import { projectEvents, rebuildProjections } from './projections.js';
|
|
8
|
+
const sources = new Set(['context', 'knowledge', 'deadend', 'note']);
|
|
9
|
+
const sections = new Set(['summary', 'pending', 'decision', 'risk', 'note', 'knowledge', 'deadend', 'activity']);
|
|
10
|
+
function canonical(value) {
|
|
11
|
+
if (value === null || typeof value !== 'object')
|
|
12
|
+
return JSON.stringify(value);
|
|
13
|
+
if (Array.isArray(value))
|
|
14
|
+
return `[${value.map(canonical).join(',')}]`;
|
|
15
|
+
const record = value;
|
|
16
|
+
return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${canonical(record[key])}`).join(',')}}`;
|
|
17
|
+
}
|
|
18
|
+
function hash(value) {
|
|
19
|
+
return crypto.createHash('sha256').update(canonical(value)).digest('hex');
|
|
20
|
+
}
|
|
21
|
+
function requireText(value, label) {
|
|
22
|
+
const text = value.trim();
|
|
23
|
+
if (!text)
|
|
24
|
+
throw new Error(`${label} must not be empty`);
|
|
25
|
+
return text;
|
|
26
|
+
}
|
|
27
|
+
function preferences(value, allowed, label) {
|
|
28
|
+
if (value === undefined)
|
|
29
|
+
return undefined;
|
|
30
|
+
if (!Array.isArray(value) || value.length > allowed.size || value.some((item) => !allowed.has(item)) || new Set(value).size !== value.length)
|
|
31
|
+
throw new Error(`invalid recipe ${label}`);
|
|
32
|
+
return [...value];
|
|
33
|
+
}
|
|
34
|
+
function budgets(value) {
|
|
35
|
+
if (value === undefined)
|
|
36
|
+
return undefined;
|
|
37
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value) || Object.entries(value).some(([section, budget]) => !sections.has(section) || !Number.isSafeInteger(budget) || budget < 0 || budget > 100_000))
|
|
38
|
+
throw new Error('invalid recipe section budgets');
|
|
39
|
+
return Object.fromEntries(Object.entries(value).sort());
|
|
40
|
+
}
|
|
41
|
+
function isRecord(value) {
|
|
42
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
43
|
+
}
|
|
44
|
+
function eventRecord(event) {
|
|
45
|
+
const payload = event.payload;
|
|
46
|
+
return isRecord(payload.record) ? payload.record : null;
|
|
47
|
+
}
|
|
48
|
+
function matchingRecord(record, expected, ignored = []) {
|
|
49
|
+
const actual = { ...record };
|
|
50
|
+
for (const field of ignored)
|
|
51
|
+
delete actual[field];
|
|
52
|
+
return canonical(actual) === canonical(expected);
|
|
53
|
+
}
|
|
54
|
+
function anchor(git) {
|
|
55
|
+
return { worktreeId: git.worktreeId, branch: git.branch, ref: git.ref, head: git.commit };
|
|
56
|
+
}
|
|
57
|
+
async function enabledProject(startPath) {
|
|
58
|
+
const projectRoot = await resolveProjectRoot(startPath);
|
|
59
|
+
if (!await v2Enabled(projectRoot))
|
|
60
|
+
throw new Error('recipes require an enabled v2 runtime');
|
|
61
|
+
return projectRoot;
|
|
62
|
+
}
|
|
63
|
+
export async function listRecipes(startPath) {
|
|
64
|
+
const projectRoot = await enabledProject(startPath);
|
|
65
|
+
return Object.values(projectEvents(await readJournal(projectRoot)).recipes).sort((left, right) => left.recipe.recipeId.localeCompare(right.recipe.recipeId));
|
|
66
|
+
}
|
|
67
|
+
export async function inspectRecipe(startPath, recipeId) {
|
|
68
|
+
const id = requireText(recipeId, 'recipe ID');
|
|
69
|
+
const recipe = (await listRecipes(startPath)).find((item) => item.recipe.recipeId === id);
|
|
70
|
+
if (!recipe)
|
|
71
|
+
throw new Error(`recipe not found: ${id}`);
|
|
72
|
+
return recipe;
|
|
73
|
+
}
|
|
74
|
+
export async function materializeRecipe(startPath, input) {
|
|
75
|
+
const projectRoot = await enabledProject(startPath);
|
|
76
|
+
const proposalId = requireText(input.proposalId, 'proposal ID');
|
|
77
|
+
const proposalState = await inspectEvolutionProposal(projectRoot, proposalId);
|
|
78
|
+
if (proposalState.proposal.targetKind !== 'compiler-recipe' || proposalState.status !== 'candidate')
|
|
79
|
+
throw new Error(`recipe materialization requires a candidate compiler-recipe proposal: ${proposalId}`);
|
|
80
|
+
const artifact = proposalState.acceptedArtifact;
|
|
81
|
+
if (!artifact || artifact.kind !== 'compiler-recipe' || artifact.proposalId !== proposalId)
|
|
82
|
+
throw new Error(`recipe materialization requires an accepted compiler-recipe candidate: ${proposalId}`);
|
|
83
|
+
const title = requireText(input.title, 'recipe title');
|
|
84
|
+
const preferredSources = preferences(input.preferredSources, sources, 'source preferences');
|
|
85
|
+
const preferredSections = preferences(input.preferredSections, sections, 'section preferences');
|
|
86
|
+
const sectionBudgets = budgets(input.sectionBudgets);
|
|
87
|
+
const basis = { proposalId, title, applicability: artifact.applicability, preferredSources, preferredSections, sectionBudgets, lineage: artifact.lineage, proposalContentHash: artifact.contentHash };
|
|
88
|
+
const expected = {
|
|
89
|
+
recipeId: artifact.artifactId,
|
|
90
|
+
schemaVersion: 1,
|
|
91
|
+
proposalId,
|
|
92
|
+
title,
|
|
93
|
+
applicability: artifact.applicability,
|
|
94
|
+
...(preferredSources ? { preferredSources } : {}),
|
|
95
|
+
...(preferredSections ? { preferredSections } : {}),
|
|
96
|
+
...(sectionBudgets ? { sectionBudgets } : {}),
|
|
97
|
+
status: 'candidate',
|
|
98
|
+
contentHash: hash(basis),
|
|
99
|
+
lineage: artifact.lineage,
|
|
100
|
+
};
|
|
101
|
+
const existing = (await readJournal(projectRoot)).find((event) => event.type === 'recipe.materialized' && eventRecord(event)?.recipeId === expected.recipeId);
|
|
102
|
+
if (existing) {
|
|
103
|
+
const record = eventRecord(existing);
|
|
104
|
+
if (!matchingRecord(record, expected, ['createdAt']))
|
|
105
|
+
throw new Error(`Recipe already materialized with conflicting content: ${expected.recipeId}`);
|
|
106
|
+
return { materialized: false, recipeId: expected.recipeId, eventId: existing.eventId };
|
|
107
|
+
}
|
|
108
|
+
const recipe = { ...expected, createdAt: new Date().toISOString() };
|
|
109
|
+
const git = await readGitWorktreeState(projectRoot);
|
|
110
|
+
const result = await appendEvent(projectRoot, {
|
|
111
|
+
type: 'recipe.materialized', source: { kind: 'cli' }, idempotencyKey: `recipe-materialized:${recipe.recipeId}:${recipe.contentHash}`,
|
|
112
|
+
git: anchor(git), payload: { record: recipe },
|
|
113
|
+
});
|
|
114
|
+
if (result.appended)
|
|
115
|
+
await rebuildProjections(projectRoot);
|
|
116
|
+
return { materialized: result.appended, recipeId: recipe.recipeId, eventId: result.event.eventId };
|
|
117
|
+
}
|
|
118
|
+
export async function transitionRecipe(startPath, input) {
|
|
119
|
+
const projectRoot = await enabledProject(startPath);
|
|
120
|
+
const recipe = await inspectRecipe(projectRoot, input.recipeId);
|
|
121
|
+
const rationale = requireText(input.rationale, 'recipe rationale');
|
|
122
|
+
if (input.to === 'superseded' && !input.supersedes)
|
|
123
|
+
throw new Error('superseding a Recipe requires lineage');
|
|
124
|
+
const request = { entity: 'compiler-recipe', entityId: recipe.recipe.recipeId, to: input.to, reason: rationale, ...(input.supersedes ? { supersedes: input.supersedes } : {}) };
|
|
125
|
+
const existing = (await readJournal(projectRoot)).find((event) => {
|
|
126
|
+
if (!event.type.startsWith('evolution.'))
|
|
127
|
+
return false;
|
|
128
|
+
const transition = event.payload.transition;
|
|
129
|
+
return isRecord(transition) && matchingRecord(transition, request, ['from']);
|
|
130
|
+
});
|
|
131
|
+
if (existing)
|
|
132
|
+
return { transitioned: false, eventId: existing.eventId };
|
|
133
|
+
if (!((recipe.status === 'candidate' && (input.to === 'verified' || input.to === 'deprecated'))
|
|
134
|
+
|| (recipe.status === 'verified' && (input.to === 'published' || input.to === 'deprecated' || input.to === 'superseded'))
|
|
135
|
+
|| (recipe.status === 'published' && (input.to === 'deprecated' || input.to === 'superseded')))) {
|
|
136
|
+
throw new Error(`invalid Recipe transition: ${recipe.status} to ${input.to}`);
|
|
137
|
+
}
|
|
138
|
+
const git = await readGitWorktreeState(projectRoot);
|
|
139
|
+
const result = await appendEvent(projectRoot, {
|
|
140
|
+
type: `evolution.${input.to}`,
|
|
141
|
+
source: { kind: 'cli' },
|
|
142
|
+
idempotencyKey: `recipe-transition:${recipe.recipe.recipeId}:${input.to}:${hash({ rationale, supersedes: input.supersedes }).slice(0, 16)}`,
|
|
143
|
+
git: anchor(git),
|
|
144
|
+
payload: { transition: { ...request, from: recipe.status } },
|
|
145
|
+
});
|
|
146
|
+
if (result.appended)
|
|
147
|
+
await rebuildProjections(projectRoot);
|
|
148
|
+
return { transitioned: result.appended, eventId: result.event.eventId };
|
|
149
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { EvolutionArtifactStatus, EvolutionLineageReference, SkillOutcome, SkillState } from './types.js';
|
|
2
|
+
export interface MaterializeSkillInput {
|
|
3
|
+
proposalId: string;
|
|
4
|
+
title: string;
|
|
5
|
+
procedure: string[];
|
|
6
|
+
}
|
|
7
|
+
export interface TransitionSkillInput {
|
|
8
|
+
skillId: string;
|
|
9
|
+
to: Extract<EvolutionArtifactStatus, 'verified' | 'published' | 'deprecated' | 'superseded'>;
|
|
10
|
+
rationale: string;
|
|
11
|
+
supersedes?: EvolutionLineageReference;
|
|
12
|
+
}
|
|
13
|
+
export interface RecordSkillInvocationInput {
|
|
14
|
+
skillId: string;
|
|
15
|
+
invocationId: string;
|
|
16
|
+
callerEventId: string;
|
|
17
|
+
inputHash: string;
|
|
18
|
+
}
|
|
19
|
+
export interface RecordSkillOutcomeInput {
|
|
20
|
+
skillId: string;
|
|
21
|
+
invocationId: string;
|
|
22
|
+
outcomeId: string;
|
|
23
|
+
callerEventId: string;
|
|
24
|
+
outcome: SkillOutcome['outcome'];
|
|
25
|
+
summary: string;
|
|
26
|
+
}
|
|
27
|
+
export declare function listSkills(startPath?: string): Promise<SkillState[]>;
|
|
28
|
+
export declare function inspectSkill(startPath: string | undefined, skillId: string): Promise<SkillState>;
|
|
29
|
+
export declare function materializeSkill(startPath: string | undefined, input: MaterializeSkillInput): Promise<{
|
|
30
|
+
materialized: boolean;
|
|
31
|
+
skillId: string;
|
|
32
|
+
eventId: string;
|
|
33
|
+
}>;
|
|
34
|
+
export declare function transitionSkill(startPath: string | undefined, input: TransitionSkillInput): Promise<{
|
|
35
|
+
transitioned: boolean;
|
|
36
|
+
eventId: string;
|
|
37
|
+
}>;
|
|
38
|
+
export declare function recordSkillInvocation(startPath: string | undefined, input: RecordSkillInvocationInput): Promise<{
|
|
39
|
+
recorded: boolean;
|
|
40
|
+
eventId: string;
|
|
41
|
+
}>;
|
|
42
|
+
export declare function recordSkillOutcome(startPath: string | undefined, input: RecordSkillOutcomeInput): Promise<{
|
|
43
|
+
recorded: boolean;
|
|
44
|
+
eventId: string;
|
|
45
|
+
}>;
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import { readGitWorktreeState } from '../git/git-adapter.js';
|
|
3
|
+
import { resolveProjectRoot } from '../project/project-resolver.js';
|
|
4
|
+
import { appendEvent, readJournal } from './journal.js';
|
|
5
|
+
import { inspectEvolutionProposal } from './evolution.js';
|
|
6
|
+
import { v2Enabled } from './orient.js';
|
|
7
|
+
import { projectEvents, rebuildProjections } from './projections.js';
|
|
8
|
+
function canonical(value) {
|
|
9
|
+
if (value === null || typeof value !== 'object')
|
|
10
|
+
return JSON.stringify(value);
|
|
11
|
+
if (Array.isArray(value))
|
|
12
|
+
return `[${value.map(canonical).join(',')}]`;
|
|
13
|
+
const record = value;
|
|
14
|
+
return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${canonical(record[key])}`).join(',')}}`;
|
|
15
|
+
}
|
|
16
|
+
function hash(value) {
|
|
17
|
+
return crypto.createHash('sha256').update(canonical(value)).digest('hex');
|
|
18
|
+
}
|
|
19
|
+
function requireText(value, label) {
|
|
20
|
+
const text = value.trim();
|
|
21
|
+
if (!text)
|
|
22
|
+
throw new Error(`${label} must not be empty`);
|
|
23
|
+
return text;
|
|
24
|
+
}
|
|
25
|
+
function requireProcedure(procedure) {
|
|
26
|
+
if (!Array.isArray(procedure) || procedure.length === 0 || procedure.length > 32)
|
|
27
|
+
throw new Error('skill procedure must contain 1 to 32 steps');
|
|
28
|
+
return procedure.map((step) => requireText(step, 'skill procedure step'));
|
|
29
|
+
}
|
|
30
|
+
function isRecord(value) {
|
|
31
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
32
|
+
}
|
|
33
|
+
function eventRecord(event) {
|
|
34
|
+
const payload = event.payload;
|
|
35
|
+
return isRecord(payload.record) ? payload.record : null;
|
|
36
|
+
}
|
|
37
|
+
function matchingRecord(record, expected, ignored = []) {
|
|
38
|
+
const actual = { ...record };
|
|
39
|
+
for (const field of ignored)
|
|
40
|
+
delete actual[field];
|
|
41
|
+
return canonical(actual) === canonical(expected);
|
|
42
|
+
}
|
|
43
|
+
function anchor(git) {
|
|
44
|
+
return { worktreeId: git.worktreeId, branch: git.branch, ref: git.ref, head: git.commit };
|
|
45
|
+
}
|
|
46
|
+
async function enabledProject(startPath) {
|
|
47
|
+
const projectRoot = await resolveProjectRoot(startPath);
|
|
48
|
+
if (!await v2Enabled(projectRoot))
|
|
49
|
+
throw new Error('skills require an enabled v2 runtime');
|
|
50
|
+
return projectRoot;
|
|
51
|
+
}
|
|
52
|
+
export async function listSkills(startPath) {
|
|
53
|
+
const projectRoot = await enabledProject(startPath);
|
|
54
|
+
return Object.values(projectEvents(await readJournal(projectRoot)).skills).sort((left, right) => left.skill.skillId.localeCompare(right.skill.skillId));
|
|
55
|
+
}
|
|
56
|
+
export async function inspectSkill(startPath, skillId) {
|
|
57
|
+
const id = requireText(skillId, 'skill ID');
|
|
58
|
+
const skill = (await listSkills(startPath)).find((item) => item.skill.skillId === id);
|
|
59
|
+
if (!skill)
|
|
60
|
+
throw new Error(`skill not found: ${id}`);
|
|
61
|
+
return skill;
|
|
62
|
+
}
|
|
63
|
+
export async function materializeSkill(startPath, input) {
|
|
64
|
+
const projectRoot = await enabledProject(startPath);
|
|
65
|
+
const proposalId = requireText(input.proposalId, 'proposal ID');
|
|
66
|
+
const proposalState = await inspectEvolutionProposal(projectRoot, proposalId);
|
|
67
|
+
if (proposalState.proposal.targetKind !== 'skill' || proposalState.status !== 'candidate')
|
|
68
|
+
throw new Error(`skill materialization requires a candidate Skill proposal: ${proposalId}`);
|
|
69
|
+
const artifact = proposalState.acceptedArtifact;
|
|
70
|
+
if (!artifact || artifact.kind !== 'skill' || artifact.proposalId !== proposalId)
|
|
71
|
+
throw new Error(`skill materialization requires an accepted Skill candidate: ${proposalId}`);
|
|
72
|
+
const title = requireText(input.title, 'skill title');
|
|
73
|
+
const procedure = requireProcedure(input.procedure);
|
|
74
|
+
const basis = { proposalId, title, procedure, applicability: artifact.applicability, lineage: artifact.lineage, proposalContentHash: artifact.contentHash };
|
|
75
|
+
const expected = {
|
|
76
|
+
skillId: artifact.artifactId,
|
|
77
|
+
schemaVersion: 1,
|
|
78
|
+
proposalId,
|
|
79
|
+
title,
|
|
80
|
+
procedure,
|
|
81
|
+
applicability: artifact.applicability,
|
|
82
|
+
status: 'candidate',
|
|
83
|
+
contentHash: hash(basis),
|
|
84
|
+
lineage: artifact.lineage,
|
|
85
|
+
};
|
|
86
|
+
const existing = (await readJournal(projectRoot)).find((event) => event.type === 'skill.materialized' && eventRecord(event)?.skillId === expected.skillId);
|
|
87
|
+
if (existing) {
|
|
88
|
+
const record = eventRecord(existing);
|
|
89
|
+
if (!matchingRecord(record, expected, ['createdAt']))
|
|
90
|
+
throw new Error(`Skill already materialized with conflicting content: ${expected.skillId}`);
|
|
91
|
+
return { materialized: false, skillId: expected.skillId, eventId: existing.eventId };
|
|
92
|
+
}
|
|
93
|
+
const skill = { ...expected, createdAt: new Date().toISOString() };
|
|
94
|
+
const git = await readGitWorktreeState(projectRoot);
|
|
95
|
+
const result = await appendEvent(projectRoot, {
|
|
96
|
+
type: 'skill.materialized', source: { kind: 'cli' }, idempotencyKey: `skill-materialized:${skill.skillId}:${skill.contentHash}`,
|
|
97
|
+
git: anchor(git), payload: { record: skill },
|
|
98
|
+
});
|
|
99
|
+
if (result.appended)
|
|
100
|
+
await rebuildProjections(projectRoot);
|
|
101
|
+
return { materialized: result.appended, skillId: skill.skillId, eventId: result.event.eventId };
|
|
102
|
+
}
|
|
103
|
+
export async function transitionSkill(startPath, input) {
|
|
104
|
+
const projectRoot = await enabledProject(startPath);
|
|
105
|
+
const skill = await inspectSkill(projectRoot, input.skillId);
|
|
106
|
+
const rationale = requireText(input.rationale, 'skill rationale');
|
|
107
|
+
if (input.to === 'superseded' && !input.supersedes)
|
|
108
|
+
throw new Error('superseding a Skill requires lineage');
|
|
109
|
+
const request = { entity: 'skill', entityId: skill.skill.skillId, to: input.to, reason: rationale, ...(input.supersedes ? { supersedes: input.supersedes } : {}) };
|
|
110
|
+
const existing = (await readJournal(projectRoot)).find((event) => {
|
|
111
|
+
if (!event.type.startsWith('evolution.'))
|
|
112
|
+
return false;
|
|
113
|
+
const transition = event.payload.transition;
|
|
114
|
+
return isRecord(transition) && matchingRecord(transition, request, ['from']);
|
|
115
|
+
});
|
|
116
|
+
if (existing)
|
|
117
|
+
return { transitioned: false, eventId: existing.eventId };
|
|
118
|
+
if (!((skill.status === 'candidate' && (input.to === 'verified' || input.to === 'deprecated'))
|
|
119
|
+
|| (skill.status === 'verified' && (input.to === 'published' || input.to === 'deprecated' || input.to === 'superseded'))
|
|
120
|
+
|| (skill.status === 'published' && (input.to === 'deprecated' || input.to === 'superseded')))) {
|
|
121
|
+
throw new Error(`invalid Skill transition: ${skill.status} to ${input.to}`);
|
|
122
|
+
}
|
|
123
|
+
const git = await readGitWorktreeState(projectRoot);
|
|
124
|
+
const result = await appendEvent(projectRoot, {
|
|
125
|
+
type: `evolution.${input.to}`,
|
|
126
|
+
source: { kind: 'cli' },
|
|
127
|
+
idempotencyKey: `skill-transition:${skill.skill.skillId}:${input.to}:${hash({ rationale, supersedes: input.supersedes }).slice(0, 16)}`,
|
|
128
|
+
git: anchor(git),
|
|
129
|
+
payload: { transition: { ...request, from: skill.status } },
|
|
130
|
+
});
|
|
131
|
+
if (result.appended)
|
|
132
|
+
await rebuildProjections(projectRoot);
|
|
133
|
+
return { transitioned: result.appended, eventId: result.event.eventId };
|
|
134
|
+
}
|
|
135
|
+
export async function recordSkillInvocation(startPath, input) {
|
|
136
|
+
const projectRoot = await enabledProject(startPath);
|
|
137
|
+
const skill = await inspectSkill(projectRoot, input.skillId);
|
|
138
|
+
const invocation = {
|
|
139
|
+
invocationId: requireText(input.invocationId, 'invocation ID'),
|
|
140
|
+
skillId: skill.skill.skillId,
|
|
141
|
+
callerEventId: requireText(input.callerEventId, 'caller event ID'),
|
|
142
|
+
inputHash: requireText(input.inputHash, 'skill input hash'),
|
|
143
|
+
};
|
|
144
|
+
const existing = (await readJournal(projectRoot)).find((event) => event.type === 'skill.invoked' && eventRecord(event)?.invocationId === invocation.invocationId);
|
|
145
|
+
if (existing) {
|
|
146
|
+
if (!matchingRecord(eventRecord(existing), invocation, ['occurredAt']))
|
|
147
|
+
throw new Error(`Skill invocation ID already exists: ${invocation.invocationId}`);
|
|
148
|
+
return { recorded: false, eventId: existing.eventId };
|
|
149
|
+
}
|
|
150
|
+
if (skill.status !== 'published')
|
|
151
|
+
throw new Error(`Skill invocation requires a published Skill: ${skill.skill.skillId}`);
|
|
152
|
+
const git = await readGitWorktreeState(projectRoot);
|
|
153
|
+
const result = await appendEvent(projectRoot, { type: 'skill.invoked', source: { kind: 'cli' }, idempotencyKey: `skill-invocation:${invocation.invocationId}`, git: anchor(git), payload: { record: { ...invocation, occurredAt: new Date().toISOString() } } });
|
|
154
|
+
if (result.appended)
|
|
155
|
+
await rebuildProjections(projectRoot);
|
|
156
|
+
return { recorded: result.appended, eventId: result.event.eventId };
|
|
157
|
+
}
|
|
158
|
+
export async function recordSkillOutcome(startPath, input) {
|
|
159
|
+
const projectRoot = await enabledProject(startPath);
|
|
160
|
+
const skill = await inspectSkill(projectRoot, input.skillId);
|
|
161
|
+
const invocationId = requireText(input.invocationId, 'invocation ID');
|
|
162
|
+
const outcome = {
|
|
163
|
+
outcomeId: requireText(input.outcomeId, 'outcome ID'),
|
|
164
|
+
invocationId,
|
|
165
|
+
callerEventId: requireText(input.callerEventId, 'caller event ID'),
|
|
166
|
+
outcome: input.outcome,
|
|
167
|
+
summary: requireText(input.summary, 'skill outcome summary'),
|
|
168
|
+
};
|
|
169
|
+
const existing = (await readJournal(projectRoot)).find((event) => event.type === 'skill.outcome.recorded' && eventRecord(event)?.outcomeId === outcome.outcomeId);
|
|
170
|
+
if (existing) {
|
|
171
|
+
if (!matchingRecord(eventRecord(existing), outcome, ['occurredAt']))
|
|
172
|
+
throw new Error(`Skill outcome ID already exists: ${outcome.outcomeId}`);
|
|
173
|
+
return { recorded: false, eventId: existing.eventId };
|
|
174
|
+
}
|
|
175
|
+
if (!skill.invocations.some((item) => item.invocationId === invocationId))
|
|
176
|
+
throw new Error(`Skill invocation not found: ${invocationId}`);
|
|
177
|
+
if (skill.outcomes.some((item) => item.invocationId === invocationId))
|
|
178
|
+
throw new Error(`Skill outcome already recorded: ${invocationId}`);
|
|
179
|
+
const git = await readGitWorktreeState(projectRoot);
|
|
180
|
+
const result = await appendEvent(projectRoot, { type: 'skill.outcome.recorded', source: { kind: 'cli' }, idempotencyKey: `skill-outcome:${outcome.outcomeId}`, git: anchor(git), payload: { record: { ...outcome, occurredAt: new Date().toISOString() } } });
|
|
181
|
+
if (result.appended)
|
|
182
|
+
await rebuildProjections(projectRoot);
|
|
183
|
+
return { recorded: result.appended, eventId: result.event.eventId };
|
|
184
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export declare const COGNITION_LAYOUT_VERSION: "event-journal-v2";
|
|
2
|
-
export type CognitionEventSource = 'cli' | 'claude-hook' | 'mcp' | 'git-observer' | 'fs-observer' | 'migration';
|
|
3
|
-
export type CognitionEventType = 'runtime.enabled' | 'runtime.started' | 'runtime.stopped' | 'host.session.started' | 'host.prompt.submitted' | 'git.state.observed' | 'git.branch.changed' | 'git.head.changed' | 'workspace.paths.changed' | 'context.checkpoint.imported' | 'context.state.recorded' | 'note.recorded' | 'note.resolved' | 'note.absorbed' | 'knowledge.proposed' | 'knowledge.verified' | 'knowledge.deprecated' | 'knowledge.rejected' | 'deadend.proposed' | 'deadend.verified' | 'deadend.obsoleted' | 'deadend.rejected' | 'record.used' | 'projection.advanced' | 'projection.failed';
|
|
2
|
+
export type CognitionEventSource = 'cli' | 'claude-hook' | 'mcp' | 'agent' | 'git-observer' | 'fs-observer' | 'migration';
|
|
3
|
+
export type CognitionEventType = 'runtime.enabled' | 'runtime.started' | 'runtime.stopped' | 'host.session.started' | 'host.prompt.submitted' | 'git.state.observed' | 'git.branch.changed' | 'git.head.changed' | 'workspace.paths.changed' | 'context.checkpoint.imported' | 'context.state.recorded' | 'note.recorded' | 'note.resolved' | 'note.absorbed' | 'knowledge.proposed' | 'knowledge.verified' | 'knowledge.deprecated' | 'knowledge.rejected' | 'deadend.proposed' | 'deadend.verified' | 'deadend.obsoleted' | 'deadend.rejected' | 'experience.recorded' | 'evolution.proposed' | 'evolution.candidate.accepted' | 'evolution.verified' | 'evolution.published' | 'evolution.rejected' | 'evolution.deprecated' | 'evolution.superseded' | 'skill.materialized' | 'recipe.materialized' | 'skill.invoked' | 'skill.outcome.recorded' | 'record.used' | 'usage.operation.completed' | 'projection.advanced' | 'projection.failed';
|
|
4
4
|
export interface CognitionGitAnchor {
|
|
5
5
|
worktreeId: string | null;
|
|
6
6
|
branch: string | null;
|
|
@@ -61,12 +61,201 @@ export interface CognitionLifecycleTransition {
|
|
|
61
61
|
to: 'verified' | 'deprecated' | 'rejected' | 'obsolete';
|
|
62
62
|
reason?: string;
|
|
63
63
|
}
|
|
64
|
+
export type EvolutionArtifactKind = 'knowledge' | 'deadend' | 'skill' | 'compiler-recipe';
|
|
65
|
+
export type EvolutionArtifactStatus = 'candidate' | 'verified' | 'published' | 'rejected' | 'deprecated' | 'superseded' | 'obsolete';
|
|
66
|
+
export type EvolutionLineageRelation = 'derived_from' | 'supports' | 'contradicts' | 'supersedes' | 'validated_by' | 'used_for';
|
|
67
|
+
export interface EvolutionEvidenceReference {
|
|
68
|
+
eventId: string;
|
|
69
|
+
relation: Extract<EvolutionLineageRelation, 'derived_from' | 'supports' | 'contradicts' | 'validated_by'>;
|
|
70
|
+
}
|
|
71
|
+
export interface EvolutionLineageReference {
|
|
72
|
+
entity: EvolutionArtifactKind | 'experience' | 'proposal';
|
|
73
|
+
entityId: string;
|
|
74
|
+
relation: Extract<EvolutionLineageRelation, 'supports' | 'contradicts' | 'supersedes' | 'used_for'>;
|
|
75
|
+
}
|
|
76
|
+
export interface Experience {
|
|
77
|
+
experienceId: string;
|
|
78
|
+
schemaVersion: 1;
|
|
79
|
+
sourceEventIds: string[];
|
|
80
|
+
sourceContextIds: string[];
|
|
81
|
+
sourceSnapshotIds: string[];
|
|
82
|
+
paths: string[];
|
|
83
|
+
git: CognitionGitAnchor;
|
|
84
|
+
privacy: {
|
|
85
|
+
classification: 'metadata' | 'redacted-text';
|
|
86
|
+
redactionCount: number;
|
|
87
|
+
};
|
|
88
|
+
extractor: {
|
|
89
|
+
id: string;
|
|
90
|
+
version: string;
|
|
91
|
+
};
|
|
92
|
+
contentHash: string;
|
|
93
|
+
summary: string;
|
|
94
|
+
createdAt: string;
|
|
95
|
+
}
|
|
96
|
+
export interface EvolutionProposal {
|
|
97
|
+
proposalId: string;
|
|
98
|
+
schemaVersion: 1;
|
|
99
|
+
targetKind: EvolutionArtifactKind;
|
|
100
|
+
targetId: string;
|
|
101
|
+
status: 'candidate';
|
|
102
|
+
sourceExperienceIds: string[];
|
|
103
|
+
evidence: EvolutionEvidenceReference[];
|
|
104
|
+
lineage: EvolutionLineageReference[];
|
|
105
|
+
applicability: Applicability;
|
|
106
|
+
contentHash: string;
|
|
107
|
+
summary: string;
|
|
108
|
+
extractor: {
|
|
109
|
+
id: string;
|
|
110
|
+
version: string;
|
|
111
|
+
};
|
|
112
|
+
createdAt: string;
|
|
113
|
+
}
|
|
114
|
+
export interface EvolutionCandidateArtifact {
|
|
115
|
+
artifactId: string;
|
|
116
|
+
schemaVersion: 1;
|
|
117
|
+
proposalId: string;
|
|
118
|
+
kind: Extract<EvolutionArtifactKind, 'skill' | 'compiler-recipe'>;
|
|
119
|
+
status: 'candidate';
|
|
120
|
+
applicability: Applicability;
|
|
121
|
+
contentHash: string;
|
|
122
|
+
lineage: EvolutionLineageReference[];
|
|
123
|
+
rationale: string;
|
|
124
|
+
createdAt: string;
|
|
125
|
+
}
|
|
126
|
+
export interface EvolutionProposalLifecycleEntry {
|
|
127
|
+
eventId: string;
|
|
128
|
+
occurredAt: string;
|
|
129
|
+
from: EvolutionArtifactStatus;
|
|
130
|
+
to: EvolutionArtifactStatus;
|
|
131
|
+
reason: string;
|
|
132
|
+
supersedes?: EvolutionLineageReference;
|
|
133
|
+
}
|
|
134
|
+
export interface EvolutionProposalState {
|
|
135
|
+
proposal: EvolutionProposal;
|
|
136
|
+
status: EvolutionArtifactStatus;
|
|
137
|
+
lifecycle: EvolutionProposalLifecycleEntry[];
|
|
138
|
+
acceptedArtifact: EvolutionCandidateArtifact | null;
|
|
139
|
+
}
|
|
140
|
+
export interface Skill {
|
|
141
|
+
skillId: string;
|
|
142
|
+
schemaVersion: 1;
|
|
143
|
+
proposalId: string;
|
|
144
|
+
title: string;
|
|
145
|
+
procedure: string[];
|
|
146
|
+
applicability: Applicability;
|
|
147
|
+
status: 'candidate';
|
|
148
|
+
contentHash: string;
|
|
149
|
+
lineage: EvolutionLineageReference[];
|
|
150
|
+
createdAt: string;
|
|
151
|
+
}
|
|
152
|
+
export interface SkillLifecycleEntry {
|
|
153
|
+
eventId: string;
|
|
154
|
+
occurredAt: string;
|
|
155
|
+
from: EvolutionArtifactStatus;
|
|
156
|
+
to: EvolutionArtifactStatus;
|
|
157
|
+
reason: string;
|
|
158
|
+
supersedes?: EvolutionLineageReference;
|
|
159
|
+
}
|
|
160
|
+
export interface SkillState {
|
|
161
|
+
skill: Skill;
|
|
162
|
+
status: EvolutionArtifactStatus;
|
|
163
|
+
lifecycle: SkillLifecycleEntry[];
|
|
164
|
+
invocations: SkillInvocation[];
|
|
165
|
+
outcomes: SkillOutcome[];
|
|
166
|
+
}
|
|
167
|
+
export type CompilerRecipeSource = 'context' | 'knowledge' | 'deadend' | 'note';
|
|
168
|
+
export type CompilerRecipeSection = 'summary' | 'pending' | 'decision' | 'risk' | 'note' | 'knowledge' | 'deadend' | 'activity';
|
|
169
|
+
export interface CompilerRecipe {
|
|
170
|
+
recipeId: string;
|
|
171
|
+
schemaVersion: 1;
|
|
172
|
+
proposalId: string;
|
|
173
|
+
title: string;
|
|
174
|
+
applicability: Applicability;
|
|
175
|
+
preferredSources?: CompilerRecipeSource[];
|
|
176
|
+
preferredSections?: CompilerRecipeSection[];
|
|
177
|
+
sectionBudgets?: Partial<Record<CompilerRecipeSection, number>>;
|
|
178
|
+
status: 'candidate';
|
|
179
|
+
contentHash: string;
|
|
180
|
+
lineage: EvolutionLineageReference[];
|
|
181
|
+
createdAt: string;
|
|
182
|
+
}
|
|
183
|
+
export interface CompilerRecipeLifecycleEntry {
|
|
184
|
+
eventId: string;
|
|
185
|
+
occurredAt: string;
|
|
186
|
+
from: EvolutionArtifactStatus;
|
|
187
|
+
to: EvolutionArtifactStatus;
|
|
188
|
+
reason: string;
|
|
189
|
+
supersedes?: EvolutionLineageReference;
|
|
190
|
+
}
|
|
191
|
+
export interface CompilerRecipeState {
|
|
192
|
+
recipe: CompilerRecipe;
|
|
193
|
+
status: EvolutionArtifactStatus;
|
|
194
|
+
lifecycle: CompilerRecipeLifecycleEntry[];
|
|
195
|
+
}
|
|
196
|
+
export interface EvolutionLifecycleTransition {
|
|
197
|
+
entity: EvolutionArtifactKind | 'proposal';
|
|
198
|
+
entityId: string;
|
|
199
|
+
from: EvolutionArtifactStatus;
|
|
200
|
+
to: EvolutionArtifactStatus;
|
|
201
|
+
reason: string;
|
|
202
|
+
supersedes?: EvolutionLineageReference;
|
|
203
|
+
}
|
|
204
|
+
export interface SkillInvocation {
|
|
205
|
+
invocationId: string;
|
|
206
|
+
skillId: string;
|
|
207
|
+
callerEventId: string;
|
|
208
|
+
inputHash: string;
|
|
209
|
+
occurredAt: string;
|
|
210
|
+
}
|
|
211
|
+
export interface SkillOutcome {
|
|
212
|
+
outcomeId: string;
|
|
213
|
+
invocationId: string;
|
|
214
|
+
callerEventId: string;
|
|
215
|
+
outcome: 'success' | 'failure' | 'partial' | 'irrelevant' | 'unknown';
|
|
216
|
+
summary: string;
|
|
217
|
+
occurredAt: string;
|
|
218
|
+
}
|
|
219
|
+
export interface EvidenceGraphNode {
|
|
220
|
+
nodeId: string;
|
|
221
|
+
kind: 'context' | 'snapshot' | 'experience' | 'proposal' | 'knowledge' | 'deadend' | 'skill' | 'recipe' | 'path' | 'test' | 'commit' | 'branch-worktree';
|
|
222
|
+
}
|
|
223
|
+
export interface EvidenceGraphEdge {
|
|
224
|
+
edgeId: string;
|
|
225
|
+
from: string;
|
|
226
|
+
to: string;
|
|
227
|
+
type: 'derived_from' | 'supports' | 'contradicts' | 'supersedes' | 'used_for' | 'observed_in' | 'changed_in' | 'co_changed_with' | 'tests';
|
|
228
|
+
sourceEventIds: string[];
|
|
229
|
+
derivationVersion: string;
|
|
230
|
+
occurredAt: string;
|
|
231
|
+
confidence?: number;
|
|
232
|
+
}
|
|
233
|
+
export interface EvidenceGraphProjection {
|
|
234
|
+
schemaVersion: 1;
|
|
235
|
+
derivationVersion: 'evidence-graph-v1';
|
|
236
|
+
nodes: EvidenceGraphNode[];
|
|
237
|
+
edges: EvidenceGraphEdge[];
|
|
238
|
+
}
|
|
64
239
|
export interface CognitionUseFeedback {
|
|
65
240
|
feedbackId: string;
|
|
66
241
|
entity: 'knowledge' | 'deadend';
|
|
67
242
|
entityId: string;
|
|
68
243
|
outcome: 'used';
|
|
69
244
|
}
|
|
245
|
+
export interface UsageOperationCompletedPayload {
|
|
246
|
+
operation: 'orient' | 'compile' | 'expand' | 'resume';
|
|
247
|
+
interface: 'cli' | 'mcp' | 'agent' | 'hook';
|
|
248
|
+
success: boolean;
|
|
249
|
+
durationMs: number;
|
|
250
|
+
inputChars: number;
|
|
251
|
+
outputChars: number;
|
|
252
|
+
inputBytes: number;
|
|
253
|
+
outputBytes: number;
|
|
254
|
+
estimatedInputTokens: number;
|
|
255
|
+
estimatedOutputTokens: number;
|
|
256
|
+
estimatedMethod: 'chars-div-four';
|
|
257
|
+
errorClass?: string;
|
|
258
|
+
}
|
|
70
259
|
export interface CognitionRetrievalEntry {
|
|
71
260
|
entity: 'knowledge' | 'deadend';
|
|
72
261
|
entityId: string;
|
|
@@ -163,6 +352,11 @@ export interface CognitionProjection {
|
|
|
163
352
|
notes: Record<string, CognitionRecordPayload>;
|
|
164
353
|
knowledge: Record<string, CognitionRecordPayload>;
|
|
165
354
|
deadends: Record<string, CognitionRecordPayload>;
|
|
355
|
+
experiences: Record<string, Experience>;
|
|
356
|
+
proposals: Record<string, EvolutionProposalState>;
|
|
357
|
+
skills: Record<string, SkillState>;
|
|
358
|
+
recipes: Record<string, CompilerRecipeState>;
|
|
359
|
+
evidenceGraph: EvidenceGraphProjection;
|
|
166
360
|
workspaces: Record<string, BranchWorkspace>;
|
|
167
361
|
}
|
|
168
362
|
export interface RuntimeLease {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export type UsageReportFormat = 'json' | 'ascii';
|
|
2
|
+
export type UsageGranularity = 'day' | 'week' | 'month';
|
|
3
|
+
export interface UsageReportOptions {
|
|
4
|
+
since?: string;
|
|
5
|
+
until?: string;
|
|
6
|
+
days?: number;
|
|
7
|
+
month?: string;
|
|
8
|
+
granularity?: UsageGranularity;
|
|
9
|
+
baselineTokens?: number;
|
|
10
|
+
baselineSource?: 'manual' | 'recorded-control';
|
|
11
|
+
}
|
|
12
|
+
export interface UsageMetric<T> {
|
|
13
|
+
value: T | null;
|
|
14
|
+
quality: 'measured' | 'estimated' | 'derived' | 'unavailable';
|
|
15
|
+
unit: string;
|
|
16
|
+
method?: string;
|
|
17
|
+
}
|
|
18
|
+
interface Window {
|
|
19
|
+
from: string;
|
|
20
|
+
to: string;
|
|
21
|
+
kind: 'rolling' | 'calendar-month' | 'explicit';
|
|
22
|
+
granularity: UsageGranularity;
|
|
23
|
+
}
|
|
24
|
+
export interface UsageReport {
|
|
25
|
+
schemaVersion: 1;
|
|
26
|
+
generatedAt: string;
|
|
27
|
+
projectRoot: string;
|
|
28
|
+
window: Window;
|
|
29
|
+
dataQuality: Record<string, unknown>;
|
|
30
|
+
assets: Record<string, unknown>;
|
|
31
|
+
reuse: Record<string, unknown>;
|
|
32
|
+
investment: Record<string, unknown>;
|
|
33
|
+
payback: Record<string, unknown>;
|
|
34
|
+
operations: Record<string, unknown>;
|
|
35
|
+
trends: Array<Record<string, unknown>>;
|
|
36
|
+
warnings: string[];
|
|
37
|
+
}
|
|
38
|
+
export declare function buildUsageReport(startPath: string | undefined, options?: UsageReportOptions): Promise<UsageReport>;
|
|
39
|
+
export declare function formatUsageReport(report: Record<string, any>): string;
|
|
40
|
+
export {};
|