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.
Files changed (36) hide show
  1. package/README.md +115 -36
  2. package/dist/src/agent/api.d.ts +49 -4
  3. package/dist/src/agent/api.js +183 -131
  4. package/dist/src/agent/index.d.ts +7 -1
  5. package/dist/src/agent/index.js +1 -0
  6. package/dist/src/cli/main.js +301 -15
  7. package/dist/src/cognition/evidence-graph.d.ts +4 -0
  8. package/dist/src/cognition/evidence-graph.js +187 -0
  9. package/dist/src/cognition/evolution.d.ts +36 -0
  10. package/dist/src/cognition/evolution.js +221 -0
  11. package/dist/src/cognition/journal.js +143 -2
  12. package/dist/src/cognition/projections.js +124 -2
  13. package/dist/src/cognition/recipes.d.ts +25 -0
  14. package/dist/src/cognition/recipes.js +149 -0
  15. package/dist/src/cognition/skills.d.ts +45 -0
  16. package/dist/src/cognition/skills.js +184 -0
  17. package/dist/src/cognition/types.d.ts +196 -2
  18. package/dist/src/cognition/usage-report.d.ts +40 -0
  19. package/dist/src/cognition/usage-report.js +232 -0
  20. package/dist/src/cognition/usage.d.ts +5 -0
  21. package/dist/src/cognition/usage.js +48 -0
  22. package/dist/src/compiler/compile.js +73 -4
  23. package/dist/src/compiler/graph.d.ts +7 -0
  24. package/dist/src/compiler/graph.js +117 -0
  25. package/dist/src/compiler/identity.d.ts +1 -1
  26. package/dist/src/compiler/identity.js +3 -1
  27. package/dist/src/compiler/sources.d.ts +3 -1
  28. package/dist/src/compiler/sources.js +11 -2
  29. package/dist/src/compiler/types.d.ts +23 -2
  30. package/dist/src/mcp/server.js +228 -6
  31. package/dist/src/storage/layout.d.ts +5 -0
  32. package/dist/src/storage/layout.js +15 -0
  33. package/dist/src/version.d.ts +1 -1
  34. package/dist/src/version.js +1 -1
  35. package/package.json +1 -1
  36. package/skills/fluffy-context/SKILL.md +53 -5
@@ -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;
@@ -201,7 +220,7 @@ export interface ContextExpandResult {
201
220
  }
202
221
  export interface ContextManifest {
203
222
  schemaVersion: 1;
204
- compilerVersion: '0.7.6';
223
+ compilerVersion: '0.7.7';
205
224
  rankingVersion: 'compiler-ranking-v2';
206
225
  status: 'ready' | 'no_context';
207
226
  projectRoot: 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;
@@ -1,8 +1,7 @@
1
1
  import { McpServer } from '@modelcontextprotocol/server';
2
2
  import { z } from 'zod';
3
- import { compileContext } from '../compiler/compile.js';
4
3
  import { addNote, listNotes } from '../runtime/notes.js';
5
- import { contextExpand, contextOrient, 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';
6
5
  import { ensureRuntimeForSession } from '../cognition/runtime.js';
7
6
  import { VERSION } from '../version.js';
8
7
  const orientInput = z.object({
@@ -22,6 +21,8 @@ const noteActor = z.enum(['agent', 'human']);
22
21
  const noteStatus = z.enum(['open', 'resolved']);
23
22
  const compileOptions = z.object({
24
23
  contextId: z.string().optional(),
24
+ recipeId: z.string().optional(),
25
+ graphHops: z.union([z.literal(0), z.literal(1), z.literal(2)]).optional(),
25
26
  query: z.string().optional(),
26
27
  scenario: scenario.optional(),
27
28
  scope: z.string().optional(),
@@ -84,6 +85,35 @@ const checkpointInput = z.object({
84
85
  risks: z.array(z.string()).optional(),
85
86
  relatedFiles: z.array(z.string()).optional(),
86
87
  });
88
+ const usageReportInput = z.object({
89
+ path: z.string().optional(),
90
+ since: z.string().optional(),
91
+ until: z.string().optional(),
92
+ days: z.int().nonnegative().optional(),
93
+ month: z.string().optional(),
94
+ granularity: z.enum(['day', 'week', 'month']).optional(),
95
+ baselineTokens: z.number().nonnegative().optional(),
96
+ baselineSource: z.enum(['manual', 'recorded-control']).optional(),
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() });
87
117
  function errorMessage(error) {
88
118
  return error instanceof Error ? error.message : String(error);
89
119
  }
@@ -108,7 +138,7 @@ export function createMcpServer() {
108
138
  }, async ({ path, ...options }) => {
109
139
  try {
110
140
  await ensureRuntimeForSession(path, { source: 'mcp', timeoutMs: 1_000 }).catch(() => undefined);
111
- return successResponse(await contextOrient(path, options));
141
+ return successResponse(await contextOrient(path, options, 'mcp'));
112
142
  }
113
143
  catch (error) {
114
144
  return failureResponse('context_orient', error);
@@ -121,7 +151,7 @@ export function createMcpServer() {
121
151
  }, async ({ path, ...input }) => {
122
152
  try {
123
153
  await ensureRuntimeForSession(path, { source: 'mcp', timeoutMs: 1_000 }).catch(() => undefined);
124
- return successResponse(await contextExpand(path, input));
154
+ return successResponse(await contextExpand(path, input, 'mcp'));
125
155
  }
126
156
  catch (error) {
127
157
  return failureResponse('context_expand', error);
@@ -134,7 +164,7 @@ export function createMcpServer() {
134
164
  }, async ({ path, ...options }) => {
135
165
  try {
136
166
  await ensureRuntimeForSession(path, { source: 'mcp', timeoutMs: 1_000 }).catch(() => undefined);
137
- return successResponse(await compileContext(path, options));
167
+ return successResponse(await contextCompile(path, options, 'mcp'));
138
168
  }
139
169
  catch (error) {
140
170
  return failureResponse('context_compile', error);
@@ -147,7 +177,7 @@ export function createMcpServer() {
147
177
  }, async ({ path, ...options }) => {
148
178
  try {
149
179
  await ensureRuntimeForSession(path, { source: 'mcp', timeoutMs: 1_000 }).catch(() => undefined);
150
- return successResponse(await loadContext(path, options));
180
+ return successResponse(await loadContext(path, options, 'mcp'));
151
181
  }
152
182
  catch (error) {
153
183
  return failureResponse('context_resume', error);
@@ -179,6 +209,198 @@ export function createMcpServer() {
179
209
  return failureResponse('context_note_add', error);
180
210
  }
181
211
  });
212
+ server.registerTool('context_usage_report', {
213
+ title: 'Context usage report',
214
+ description: 'Build a read-only report of context assets, explicit reuse, estimated token investment, and operation metrics.',
215
+ inputSchema: usageReportInput,
216
+ }, async ({ path, ...options }) => {
217
+ try {
218
+ return successResponse(await contextUsageReport(path, options));
219
+ }
220
+ catch (error) {
221
+ return failureResponse('context_usage_report', error);
222
+ }
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
+ });
182
404
  server.registerTool('context_checkpoint', {
183
405
  title: 'Checkpoint context',
184
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
  }
@@ -1 +1 @@
1
- export declare const VERSION = "0.7.6";
1
+ export declare const VERSION = "0.8.0";
@@ -1 +1 @@
1
- export const VERSION = '0.7.6';
1
+ export const VERSION = '0.8.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fluffy-context",
3
- "version": "0.7.6",
3
+ "version": "0.8.0",
4
4
  "description": "Local context management CLI and MCP tools for AI coding agents",
5
5
  "license": "MIT",
6
6
  "author": "FluffyChi-Xing",
@@ -19,12 +19,19 @@ compatibility: 需要 Node.js >=20.19.0;Git 可选。CLI 通过 npm 全局安
19
19
  - 仅需要完整详情时再运行 `ctx resume --max-chars <预算>`;它会保留既有的 `lastUsedAt` 更新语义。
20
20
  - Agent 集成从 `fluffy-context` 或 `fluffy-context/agent` 导入 `contextOrient`、`compileContext`、`saveContext`、`loadContext` 和 `searchContext`,不应直接读写 `.context` 或导入内部 `dist/...` 路径。
21
21
  - `ctx compile` 的 `activity` section 只表示 Runtime 观察到的近期文件 metadata 变化;它不代表文件被读取或理解,也不替代 checkpoint、Note 或已验证 Knowledge。
22
- - 0.7.6 的 Compiler Manifest 提供稳定 `identity.manifestHash`、候选 `itemHash`、`tokenEstimate` 和 `level` 契约;默认输出 `summary` 层。
22
+ - 只有显式传入已验证或已发布的 `--recipe-id <recipe-id>` 时,Recipe 才能影响一次编译;未指定时保持默认编译。Recipe 是受限声明式数据,不能执行命令、读取文件或替换核心排序。
23
+ - 仅在需要时使用 `--graph-hops 0|1|2`;图只对已经词法命中的合规候选进行有界排序,图不可用时接受明确 fallback 并保持词法结果。
24
+ - `ctx expand` 必须重传原始 `recipeId`、`graphHops` 和 compile budget;身份过期、Recipe 状态/内容/适用性变化时重新 compile,不要绕过 hash 校验。
25
+ - 0.8.0 的 Compiler Manifest 提供稳定 `identity.manifestHash`、候选 `itemHash`、`tokenEstimate` 和 `level` 契约;默认输出 `summary` 层。
23
26
  - 只对 selected candidate 使用 `ctx expand --manifest-hash ... --candidate-id ... --level structured|evidence`。必须带上原始 compile 参数和相同的 compile budget;Manifest 过期时应重新 compile,不要绕过 hash 校验。
24
27
  - `expand` 是无状态、只读的渐进式展开:`complete` 才提供完整 `content`,`partial` 提供有界 `text` 且 `content` 为 null。它不写入 Context/journal、不记录 feedback、不读取项目文件正文、diff、命令输出或敏感数据。Agent API 使用 `contextExpand`,MCP 使用 `context_expand`。
25
28
  - 新任务先发现已验证共识,再开始实现;候选项只在显式审查时使用。
26
29
  - 普通查询只使用已验证 Knowledge 和 Deadend;需要审查候选项时显式使用 `--all`。
27
30
  - CLI 业务命令的标准输出是 JSON;错误写入标准错误并返回非零退出码。解析输出时不要把 `--help` 的纯文本当作 JSON。
31
+ - 每周或每月复盘时使用 `ctx usage report`,不要为每条 prompt 运行;它只读 journal 和 v1 数据,不创建事件或修改项目状态。
32
+ - Report 中的 `chars / 4` 是明确标注的 estimated token,不是真实 Provider token;Provider token 和计费金额当前 unavailable。
33
+ - 只有显式 `record.used` 才算 Knowledge/Deadend reuse;compile 命中或 orient 返回不等于复用。
34
+ - 没有显式 baseline 时,report 不生成 savings、ROI 或 payback 数字;report 不能替代 `doctor`、`journal verify` 或人工治理。
28
35
 
29
36
  ## 快速工作流
30
37
 
@@ -46,6 +53,21 @@ ctx learn ... / ctx deadend ... → 显式 verify
46
53
 
47
54
  `ctx orient` 不带查询时也有效,用于恢复当前任务和 open Note。带查询时只发现已验证的 Knowledge 与 Deadend;不要以候选项驱动普通实现。
48
55
 
56
+ ### Usage report
57
+
58
+ 在阶段性复盘、周报或月报时运行只读观测报告:
59
+
60
+ ```bash
61
+ ctx usage report --days 30
62
+ ctx usage report --month 2026-08 --granularity week
63
+ ctx usage report --since 2026-08-01T00:00:00.000Z --until 2026-09-01T00:00:00.000Z --baseline-tokens 12000 --baseline-source manual
64
+ ctx usage report --format ascii
65
+ ```
66
+
67
+ JSON 是默认输出;`--format ascii` 仅用于终端展示。报告分别统计 v1 文件资产和 v2 journal 事件,展示 Knowledge、Deadend、Note、Context、Snapshot、显式 reuse、操作趋势与 estimated token 投入。`record.used` 是唯一的 reuse 信号,候选项命中、compile 选择和普通检索都不算复用。
68
+
69
+ `--baseline-tokens` 必须是显式对照输入。未提供 baseline 时 savings、ROI 和 payback 为 `null` 或 `no_baseline`;字符换算不代表 Provider 真实 token,也不提供默认单价。报告失败时应使用 `ctx doctor` 与 `ctx journal verify` 进一步诊断,而不是把 report 当作数据修复工具。
70
+
49
71
  ### 初始化
50
72
 
51
73
  ```bash
@@ -101,11 +123,33 @@ ctx resume --path path/to/project --max-chars 2000
101
123
 
102
124
  遇到 `rate_limited` 时不要循环重试;完成更多阶段性工作后,在 `retryAt` 之后再保存。
103
125
 
104
- ## MCP 与 Claude Code 集成
126
+ ### Evolution、Skill 与 Recipe
127
+
128
+ Evolution 必须显式发起,并且只基于已授权的 journal、Git/workspace metadata 与 `record.used` 信号:
129
+
130
+ ```text
131
+ Experience metadata → Evolution Proposal → accept → materialize → governed lifecycle
132
+ ```
133
+
134
+ ```bash
135
+ ctx evolution propose --recent-limit 32 --target-kind skill
136
+ ctx evolution list
137
+ ctx evolution inspect <proposal-id>
138
+ ctx evolution accept <proposal-id> --rationale "人工审查"
139
+ ctx evolution verify <proposal-id> --rationale "确认可物化"
140
+ ctx evolution supersede <proposal-id> --rationale "由新提案替换" --supersedes proposal:<predecessor-id>
141
+ ```
142
+
143
+ - 不自动运行 `evolution propose`;它不读取项目源文件、原始 prompt、Note/Snapshot 内容,不创建 checkpoint,也不自动接受、验证、发布、物化或调用。
144
+ - 接受 Proposal 仅创建候选工件。Skill/Recipe 仍需要显式物化和人工治理;候选从不进入默认 orient/compile/retrieval。
145
+ - Skill 和 Recipe 都追加式经历 `candidate → verified → published → deprecated|superseded`;修改时用带 `supersedes` 谱系的新记录,不删除或覆盖旧记录。
146
+ - 仅 `published` Skill 可以记录 invocation。调用只存输入 hash 与 caller event ID;每个 invocation 仅能写入一个 `success|failure|partial|irrelevant|unknown` 终态 outcome。不要执行 procedure 文本,也不要把原始输入写入 Note、journal 或命令参数。
147
+ - 只有已物化、verified/published 且适用的 Recipe 可以作为 `--recipe-id` 参与 compile;不存在、候选、终态或不适用 Recipe 必须修正治理/选择,而非静默降级。
148
+
105
149
 
106
- `ctx agent serve` 提供 MCP stdio server,注册以下工具:`context_orient`、`context_expand`、`context_compile`、`context_resume`、`context_note_list`、`context_note_add` 和 `context_checkpoint`。标准输入和输出都是 MCP 协议,不能输出提示、日志或交互文本;调用错误是单次工具错误,不应让 server 退出。
150
+ `ctx agent serve` 提供 MCP stdio server,注册 23 个工具:`context_orient`、`context_expand`、`context_compile`、`context_resume`、`context_note_list`、`context_note_add`、`context_usage_report`、`recipe_list`、`recipe_inspect`、`recipe_materialize`、`recipe_transition`、`skill_list`、`skill_inspect`、`skill_materialize`、`skill_transition`、`skill_record_invocation`、`skill_record_outcome`、`evolution_propose`、`evolution_list`、`evolution_inspect`、`evolution_accept`、`evolution_transition` 和 `context_checkpoint`。标准输入和输出都是 MCP 协议,不能输出提示、日志或交互文本;调用错误是单次工具错误,不应让 server 退出。
107
151
 
108
- 读取工作流通常是 `context_orient → context_compile → context_expand`。`context_compile` 与 `context_expand` 继续保持有界、确定性和不读取文件正文的边界;`context_resume` 复用 `loadContext`,可能更新 `lastUsedAt`。`context_note_list` 是只读的,`context_note_add` 和 `context_checkpoint` 是显式写入操作,分别新增 Note 或保存 Snapshot;它们不会自动记录 feedback、Knowledge、Deadend 或重复 checkpoint。Knowledge/Deadend 的完整记录和治理仍使用 CLI/Agent API。
152
+ 读取工作流通常是 `context_orient → context_compile → context_expand`。`context_compile` 与 `context_expand` 保持有界、确定性且不读取文件正文,且可显式传递 `recipeId` 和 `graphHops: 0|1|2`;`context_resume` 复用 `loadContext`,可能更新 `lastUsedAt`。`context_note_list`、usage、Evolution/Skill/Recipe 的 list/inspect 是逻辑只读操作。`context_note_add`、`context_checkpoint`、Evolution 的 propose/accept/transition,以及 Skill/Recipe 的 materialize/transition/invocation/outcome 都是显式写入;不会自动执行 Skill/Recipe 或自动推进生命周期。
109
153
 
110
154
  ```bash
111
155
  ctx agent serve
@@ -341,7 +385,11 @@ ctx learn "订单取消后不能再次进入支付中状态"
341
385
  [ ] .contextignored 排除敏感和越界路径
342
386
  [ ] Knowledge candidate → verified
343
387
  [ ] Deadend candidate → verified
344
- [ ] MCP context_orient/context_expand/context_compile/context_resume/context_note_list/context_note_add/context_checkpoint 可调用,且单次错误不会终止 server
388
+ [ ] Skill/Recipe candidate → verified → published 的显式治理,以及 supersedes 谱系
389
+ [ ] 只调用 published Skill,且 invocation 不保存原始输入、每次仅一个 outcome
390
+ [ ] MCP 23 个工具可调用,read-only 工具不逻辑创建认知记录,单次错误不会终止 server
391
+ [ ] 显式 Recipe 与 graphHops 编译/展开保持 Manifest identity 校验和 lexical fallback
392
+ [ ] ctx usage report 的 JSON/ASCII 输出、baseline 边界和只读性符合预期
345
393
  [ ] MCP 只读工具不写入 Context;Note add 和 checkpoint 仅在显式调用时写入
346
394
  [ ] Claude 集成预览不写文件,--apply 幂等且拒绝冲突
347
395
  [ ] Hook 失败时 fail open,且不会自动 checkpoint