fluffy-context 0.6.0 → 0.7.6

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 (44) hide show
  1. package/README.md +494 -115
  2. package/dist/src/agent/api.d.ts +2 -0
  3. package/dist/src/agent/api.js +5 -0
  4. package/dist/src/agent/index.d.ts +3 -0
  5. package/dist/src/agent/index.js +2 -0
  6. package/dist/src/cli/main.js +125 -6
  7. package/dist/src/cognition/filesystem-observer.d.ts +41 -0
  8. package/dist/src/cognition/filesystem-observer.js +164 -0
  9. package/dist/src/cognition/git-observer.d.ts +1 -0
  10. package/dist/src/cognition/git-observer.js +3 -2
  11. package/dist/src/cognition/journal.d.ts +4 -1
  12. package/dist/src/cognition/journal.js +100 -4
  13. package/dist/src/cognition/migration-v1.js +1 -1
  14. package/dist/src/cognition/orient.js +4 -3
  15. package/dist/src/cognition/policy.d.ts +4 -0
  16. package/dist/src/cognition/policy.js +49 -0
  17. package/dist/src/cognition/projections.js +15 -1
  18. package/dist/src/cognition/runtime.d.ts +20 -1
  19. package/dist/src/cognition/runtime.js +229 -53
  20. package/dist/src/cognition/types.d.ts +16 -0
  21. package/dist/src/cognition/v1-bridge.js +3 -2
  22. package/dist/src/compiler/activity.d.ts +3 -0
  23. package/dist/src/compiler/activity.js +79 -0
  24. package/dist/src/compiler/compile.d.ts +2 -0
  25. package/dist/src/compiler/compile.js +353 -0
  26. package/dist/src/compiler/expand.d.ts +2 -0
  27. package/dist/src/compiler/expand.js +187 -0
  28. package/dist/src/compiler/identity.d.ts +6 -0
  29. package/dist/src/compiler/identity.js +36 -0
  30. package/dist/src/compiler/sources.d.ts +19 -0
  31. package/dist/src/compiler/sources.js +126 -0
  32. package/dist/src/compiler/types.d.ts +233 -0
  33. package/dist/src/compiler/types.js +1 -0
  34. package/dist/src/hooks/claude-code.js +7 -15
  35. package/dist/src/mcp/main.js +2 -0
  36. package/dist/src/mcp/server.js +167 -11
  37. package/dist/src/runtime/runtime.js +10 -0
  38. package/dist/src/runtime/types.d.ts +1 -1
  39. package/dist/src/storage/layout.d.ts +1 -0
  40. package/dist/src/storage/layout.js +3 -0
  41. package/dist/src/version.d.ts +1 -1
  42. package/dist/src/version.js +1 -1
  43. package/package.json +1 -1
  44. package/skills/fluffy-context/SKILL.md +10 -3
@@ -1,6 +1,9 @@
1
1
  import { McpServer } from '@modelcontextprotocol/server';
2
2
  import { z } from 'zod';
3
- import { contextOrient } from '../agent/api.js';
3
+ import { compileContext } from '../compiler/compile.js';
4
+ import { addNote, listNotes } from '../runtime/notes.js';
5
+ import { contextExpand, contextOrient, loadContext, saveContext } from '../agent/api.js';
6
+ import { ensureRuntimeForSession } from '../cognition/runtime.js';
4
7
  import { VERSION } from '../version.js';
5
8
  const orientInput = z.object({
6
9
  path: z.string().optional(),
@@ -12,28 +15,181 @@ const orientInput = z.object({
12
15
  deadendLimit: z.int().nonnegative().optional(),
13
16
  noteLimit: z.int().nonnegative().optional(),
14
17
  });
18
+ const scenario = z.enum(['resume', 'implementation', 'debugging', 'verification', 'handoff', 'exploration', 'unknown']);
19
+ const trigger = z.enum(['session-start', 'prompt', 'manual', 'tool-call']);
20
+ const noteKind = z.enum(['problem', 'action', 'observation', 'decision']);
21
+ const noteActor = z.enum(['agent', 'human']);
22
+ const noteStatus = z.enum(['open', 'resolved']);
23
+ const compileOptions = z.object({
24
+ contextId: z.string().optional(),
25
+ query: z.string().optional(),
26
+ scenario: scenario.optional(),
27
+ scope: z.string().optional(),
28
+ paths: z.array(z.string()).optional(),
29
+ maxChars: z.int().nonnegative().optional(),
30
+ knowledgeLimit: z.int().nonnegative().optional(),
31
+ deadendLimit: z.int().nonnegative().optional(),
32
+ noteLimit: z.int().nonnegative().optional(),
33
+ activityLimit: z.int().nonnegative().optional(),
34
+ trigger: trigger.optional(),
35
+ adapter: z.string().optional(),
36
+ sessionId: z.string().optional(),
37
+ eventId: z.string().optional(),
38
+ });
39
+ const expandInput = z.object({
40
+ path: z.string().optional(),
41
+ manifestHash: z.string(),
42
+ candidateId: z.string().optional(),
43
+ itemHash: z.string().optional(),
44
+ level: z.enum(['structured', 'evidence']),
45
+ maxChars: z.int().nonnegative().optional(),
46
+ compile: compileOptions,
47
+ });
48
+ const compileInput = z.object({ path: z.string().optional(), ...compileOptions.shape });
49
+ const resumeInput = z.object({
50
+ path: z.string().optional(),
51
+ contextId: z.string().optional(),
52
+ maxChars: z.int().nonnegative().optional(),
53
+ includeDetails: z.boolean().optional(),
54
+ query: z.string().optional(),
55
+ scope: z.string().optional(),
56
+ });
57
+ const noteListInput = z.object({
58
+ path: z.string().optional(),
59
+ contextId: z.string().optional(),
60
+ openOnly: z.boolean().optional(),
61
+ limit: z.int().nonnegative().optional(),
62
+ maxChars: z.int().nonnegative().optional(),
63
+ });
64
+ const noteAddInput = z.object({
65
+ path: z.string().optional(),
66
+ message: z.string(),
67
+ kind: noteKind.optional(),
68
+ actor: noteActor.optional(),
69
+ contextId: z.string().optional(),
70
+ snapshotId: z.string().optional(),
71
+ relatedFiles: z.array(z.string()).optional(),
72
+ status: noteStatus.optional(),
73
+ });
74
+ const checkpointInput = z.object({
75
+ path: z.string().optional(),
76
+ contextId: z.string().optional(),
77
+ absorbNotes: z.boolean().optional(),
78
+ title: z.string().optional(),
79
+ progressSummary: z.string().optional(),
80
+ lastError: z.string().nullable().optional(),
81
+ completed: z.array(z.string()).optional(),
82
+ pendingTasks: z.array(z.string()).optional(),
83
+ decisions: z.array(z.string()).optional(),
84
+ risks: z.array(z.string()).optional(),
85
+ relatedFiles: z.array(z.string()).optional(),
86
+ });
15
87
  function errorMessage(error) {
16
88
  return error instanceof Error ? error.message : String(error);
17
89
  }
90
+ function successResponse(result) {
91
+ return {
92
+ content: [{ type: 'text', text: JSON.stringify(result) }],
93
+ structuredContent: result,
94
+ };
95
+ }
96
+ function failureResponse(tool, error) {
97
+ return {
98
+ isError: true,
99
+ content: [{ type: 'text', text: `${tool} failed: ${errorMessage(error)}` }],
100
+ };
101
+ }
18
102
  export function createMcpServer() {
19
103
  const server = new McpServer({ name: 'fluffy-context', version: VERSION });
20
104
  server.registerTool('context_orient', {
21
105
  title: 'Orient context',
22
- description: 'Load bounded, read-only task context with verified consensus and open notes.',
106
+ description: 'Load bounded, read-only task context with verified consensus and open notes. Compiler manifests expose deterministic levels, hashes, and estimated tokens through the Agent API and CLI.',
23
107
  inputSchema: orientInput,
24
108
  }, async ({ path, ...options }) => {
25
109
  try {
26
- const result = await contextOrient(path, options);
27
- return {
28
- content: [{ type: 'text', text: JSON.stringify(result) }],
29
- structuredContent: result,
30
- };
110
+ await ensureRuntimeForSession(path, { source: 'mcp', timeoutMs: 1_000 }).catch(() => undefined);
111
+ return successResponse(await contextOrient(path, options));
112
+ }
113
+ catch (error) {
114
+ return failureResponse('context_orient', error);
115
+ }
116
+ });
117
+ server.registerTool('context_expand', {
118
+ title: 'Expand context candidate',
119
+ description: 'Read a selected compiler candidate at structured or evidence level without writing project state or reading file contents.',
120
+ inputSchema: expandInput,
121
+ }, async ({ path, ...input }) => {
122
+ try {
123
+ await ensureRuntimeForSession(path, { source: 'mcp', timeoutMs: 1_000 }).catch(() => undefined);
124
+ return successResponse(await contextExpand(path, input));
125
+ }
126
+ catch (error) {
127
+ return failureResponse('context_expand', error);
128
+ }
129
+ });
130
+ server.registerTool('context_compile', {
131
+ title: 'Compile context manifest',
132
+ description: 'Compile a bounded, deterministic, read-only context manifest without writing project state or reading file contents.',
133
+ inputSchema: compileInput,
134
+ }, async ({ path, ...options }) => {
135
+ try {
136
+ await ensureRuntimeForSession(path, { source: 'mcp', timeoutMs: 1_000 }).catch(() => undefined);
137
+ return successResponse(await compileContext(path, options));
138
+ }
139
+ catch (error) {
140
+ return failureResponse('context_compile', error);
141
+ }
142
+ });
143
+ server.registerTool('context_resume', {
144
+ title: 'Resume context',
145
+ description: 'Resume the selected active context with a bounded summary and optional details. This may update the existing lastUsedAt field.',
146
+ inputSchema: resumeInput,
147
+ }, async ({ path, ...options }) => {
148
+ try {
149
+ await ensureRuntimeForSession(path, { source: 'mcp', timeoutMs: 1_000 }).catch(() => undefined);
150
+ return successResponse(await loadContext(path, options));
151
+ }
152
+ catch (error) {
153
+ return failureResponse('context_resume', error);
154
+ }
155
+ });
156
+ server.registerTool('context_note_list', {
157
+ title: 'List context notes',
158
+ description: 'List bounded notes without modifying project state or absorbing or resolving notes.',
159
+ inputSchema: noteListInput,
160
+ }, async ({ path, ...query }) => {
161
+ try {
162
+ await ensureRuntimeForSession(path, { source: 'mcp', timeoutMs: 1_000 }).catch(() => undefined);
163
+ return successResponse({ notes: await listNotes(path, query) });
164
+ }
165
+ catch (error) {
166
+ return failureResponse('context_note_list', error);
167
+ }
168
+ });
169
+ server.registerTool('context_note_add', {
170
+ title: 'Add context note',
171
+ description: 'Append one explicit note to project state. This writes the local context store but does not create a Snapshot.',
172
+ inputSchema: noteAddInput,
173
+ }, async ({ path, message, ...input }) => {
174
+ try {
175
+ await ensureRuntimeForSession(path, { source: 'mcp', timeoutMs: 1_000 }).catch(() => undefined);
176
+ return successResponse(await addNote(path, message, input));
177
+ }
178
+ catch (error) {
179
+ return failureResponse('context_note_add', error);
180
+ }
181
+ });
182
+ server.registerTool('context_checkpoint', {
183
+ title: 'Checkpoint context',
184
+ description: 'Explicitly save context progress and create a Snapshot when changes are present; runtime rate limits remain enforced.',
185
+ inputSchema: checkpointInput,
186
+ }, async ({ path, ...input }) => {
187
+ try {
188
+ await ensureRuntimeForSession(path, { source: 'mcp', timeoutMs: 1_000 }).catch(() => undefined);
189
+ return successResponse(await saveContext(path, input));
31
190
  }
32
191
  catch (error) {
33
- return {
34
- isError: true,
35
- content: [{ type: 'text', text: `context_orient failed: ${errorMessage(error)}` }],
36
- };
192
+ return failureResponse('context_checkpoint', error);
37
193
  }
38
194
  });
39
195
  return server;
@@ -253,6 +253,16 @@ export async function checkpoint(startPath, input, options = {}) {
253
253
  }
254
254
  const content = await contextFromInput(projectRoot, input, previous);
255
255
  const patch = diffContext(previous, content);
256
+ const hasContent = content.progressSummary.length > 0
257
+ || content.lastError !== null
258
+ || content.completed.length > 0
259
+ || content.pendingTasks.length > 0
260
+ || content.decisions.length > 0
261
+ || content.risks.length > 0
262
+ || content.relatedFiles.length > 0;
263
+ if (!hasContent && !previousSnapshot) {
264
+ return { status: 'no_change', context, snapshot: null };
265
+ }
256
266
  if (previousSnapshot && Object.keys(patch).length === 0)
257
267
  return { status: 'no_change', context, snapshot: previousSnapshot };
258
268
  const minSaveIntervalMs = options.minSaveIntervalMs ?? 10_000;
@@ -304,7 +304,7 @@ export type CheckpointResult = {
304
304
  } | {
305
305
  status: 'no_change';
306
306
  context: ContextMetadata;
307
- snapshot: Snapshot;
307
+ snapshot: Snapshot | null;
308
308
  } | {
309
309
  status: 'rate_limited';
310
310
  context: ContextMetadata;
@@ -28,5 +28,6 @@ export declare function cognitionV1ImportPath(projectRoot: string): string;
28
28
  export declare function cognitionRuntimeDirectory(projectRoot: string): string;
29
29
  export declare function cognitionRuntimeLeasePath(projectRoot: string): string;
30
30
  export declare function cognitionRuntimeSecretPath(projectRoot: string): string;
31
+ export declare function cognitionRuntimeStartupPath(projectRoot: string): string;
31
32
  export declare function cognitionV1SyncPath(projectRoot: string): string;
32
33
  export declare function cognitionQuarantineDirectory(projectRoot: string): string;
@@ -89,6 +89,9 @@ export function cognitionRuntimeLeasePath(projectRoot) {
89
89
  export function cognitionRuntimeSecretPath(projectRoot) {
90
90
  return path.join(cognitionRuntimeDirectory(projectRoot), 'secret');
91
91
  }
92
+ export function cognitionRuntimeStartupPath(projectRoot) {
93
+ return path.join(cognitionRuntimeDirectory(projectRoot), 'startup.json');
94
+ }
92
95
  export function cognitionV1SyncPath(projectRoot) {
93
96
  return path.join(cognitionRuntimeDirectory(projectRoot), 'v1-sync.json');
94
97
  }
@@ -1 +1 @@
1
- export declare const VERSION = "0.6.0";
1
+ export declare const VERSION = "0.7.6";
@@ -1 +1 @@
1
- export const VERSION = '0.6.0';
1
+ export const VERSION = '0.7.6';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fluffy-context",
3
- "version": "0.6.0",
3
+ "version": "0.7.6",
4
4
  "description": "Local context management CLI and MCP tools for AI coding agents",
5
5
  "license": "MIT",
6
6
  "author": "FluffyChi-Xing",
@@ -17,7 +17,11 @@ compatibility: 需要 Node.js >=20.19.0;Git 可选。CLI 通过 npm 全局安
17
17
  - 人类需要了解 Agent 发生了什么时使用 `ctx activity`;它是只读时间线,不要求人类参与 Agent 的高频记录操作。
18
18
  - 新会话与新任务优先运行 `ctx orient [query] --max-chars <预算>`;它只读地返回有界摘要、匹配的已验证共识和当前 Context 的 open Note,不会创建 Snapshot 或更新 `lastUsedAt`。
19
19
  - 仅需要完整详情时再运行 `ctx resume --max-chars <预算>`;它会保留既有的 `lastUsedAt` 更新语义。
20
- - Agent 集成从 `fluffy-context` 或 `fluffy-context/agent` 导入 `contextOrient`、`saveContext`、`loadContext` 和 `searchContext`,不应直接读写 `.context` 或导入内部 `dist/...` 路径。
20
+ - Agent 集成从 `fluffy-context` 或 `fluffy-context/agent` 导入 `contextOrient`、`compileContext`、`saveContext`、`loadContext` 和 `searchContext`,不应直接读写 `.context` 或导入内部 `dist/...` 路径。
21
+ - `ctx compile` 的 `activity` section 只表示 Runtime 观察到的近期文件 metadata 变化;它不代表文件被读取或理解,也不替代 checkpoint、Note 或已验证 Knowledge。
22
+ - 0.7.6 的 Compiler Manifest 提供稳定 `identity.manifestHash`、候选 `itemHash`、`tokenEstimate` 和 `level` 契约;默认输出 `summary` 层。
23
+ - 只对 selected candidate 使用 `ctx expand --manifest-hash ... --candidate-id ... --level structured|evidence`。必须带上原始 compile 参数和相同的 compile budget;Manifest 过期时应重新 compile,不要绕过 hash 校验。
24
+ - `expand` 是无状态、只读的渐进式展开:`complete` 才提供完整 `content`,`partial` 提供有界 `text` 且 `content` 为 null。它不写入 Context/journal、不记录 feedback、不读取项目文件正文、diff、命令输出或敏感数据。Agent API 使用 `contextExpand`,MCP 使用 `context_expand`。
21
25
  - 新任务先发现已验证共识,再开始实现;候选项只在显式审查时使用。
22
26
  - 普通查询只使用已验证 Knowledge 和 Deadend;需要审查候选项时显式使用 `--all`。
23
27
  - CLI 业务命令的标准输出是 JSON;错误写入标准错误并返回非零退出码。解析输出时不要把 `--help` 的纯文本当作 JSON。
@@ -99,7 +103,9 @@ ctx resume --path path/to/project --max-chars 2000
99
103
 
100
104
  ## MCP 与 Claude Code 集成
101
105
 
102
- `ctx agent serve` 提供 MCP stdio server,注册 `context_orient` 工具。标准输入和输出都是 MCP 协议,不能输出提示、日志或交互文本;调用错误是单次工具错误,不应让 server 退出。
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 退出。
107
+
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。
103
109
 
104
110
  ```bash
105
111
  ctx agent serve
@@ -335,7 +341,8 @@ ctx learn "订单取消后不能再次进入支付中状态"
335
341
  [ ] .contextignored 排除敏感和越界路径
336
342
  [ ] Knowledge candidate → verified
337
343
  [ ] Deadend candidate → verified
338
- [ ] MCP context_orient 的 no_context 和单次错误不会终止 server
344
+ [ ] MCP context_orient/context_expand/context_compile/context_resume/context_note_list/context_note_add/context_checkpoint 可调用,且单次错误不会终止 server
345
+ [ ] MCP 只读工具不写入 Context;Note add 和 checkpoint 仅在显式调用时写入
339
346
  [ ] Claude 集成预览不写文件,--apply 幂等且拒绝冲突
340
347
  [ ] Hook 失败时 fail open,且不会自动 checkpoint
341
348
  [ ] npm pack 的安装包可运行 ctx 并导入 contextOrient