fluffy-context 0.6.0 → 0.7.2

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 +99 -8
  2. package/dist/src/agent/index.d.ts +2 -0
  3. package/dist/src/agent/index.js +1 -0
  4. package/dist/src/cli/main.js +69 -5
  5. package/dist/src/cognition/filesystem-observer.d.ts +41 -0
  6. package/dist/src/cognition/filesystem-observer.js +164 -0
  7. package/dist/src/cognition/git-observer.d.ts +1 -0
  8. package/dist/src/cognition/git-observer.js +3 -2
  9. package/dist/src/cognition/journal.d.ts +4 -1
  10. package/dist/src/cognition/journal.js +83 -4
  11. package/dist/src/cognition/migration-v1.js +1 -1
  12. package/dist/src/cognition/orient.js +4 -3
  13. package/dist/src/cognition/policy.d.ts +4 -0
  14. package/dist/src/cognition/policy.js +49 -0
  15. package/dist/src/cognition/projections.js +0 -0
  16. package/dist/src/cognition/runtime.d.ts +20 -1
  17. package/dist/src/cognition/runtime.js +229 -53
  18. package/dist/src/cognition/types.d.ts +16 -0
  19. package/dist/src/cognition/v1-bridge.js +3 -2
  20. package/dist/src/compiler/activity.d.ts +3 -0
  21. package/dist/src/compiler/activity.js +79 -0
  22. package/dist/src/compiler/compile.d.ts +2 -0
  23. package/dist/src/compiler/compile.js +330 -0
  24. package/dist/src/compiler/sources.d.ts +19 -0
  25. package/dist/src/compiler/sources.js +130 -0
  26. package/dist/src/compiler/types.d.ts +187 -0
  27. package/dist/src/compiler/types.js +1 -0
  28. package/dist/src/hooks/claude-code.js +7 -15
  29. package/dist/src/mcp/main.js +2 -0
  30. package/dist/src/mcp/server.js +2 -0
  31. package/dist/src/storage/layout.d.ts +1 -0
  32. package/dist/src/storage/layout.js +3 -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 +2 -1
@@ -0,0 +1,187 @@
1
+ import type { CognitionGitAnchor, WorkspacePathChange } from '../cognition/types.js';
2
+ import type { ContextOrientResult } from '../agent/types.js';
3
+ import type { Deadend, Knowledge, Note, ResumeSummary, StructuredContext } from '../runtime/types.js';
4
+ export type ContextScenario = 'resume' | 'implementation' | 'debugging' | 'verification' | 'handoff' | 'exploration' | 'unknown';
5
+ export type ContextSourceKind = 'current-context' | 'checkpoint' | 'note' | 'journal' | 'knowledge' | 'deadend';
6
+ export type ContextCandidateSection = 'summary' | 'pending' | 'decision' | 'risk' | 'note' | 'knowledge' | 'deadend' | 'activity';
7
+ export type ContextSignalOrigin = 'explicit' | 'deterministic';
8
+ export interface ContextAnchor {
9
+ projectRoot: string;
10
+ worktreeId: string | null;
11
+ branch: string | null;
12
+ ref: string | null;
13
+ head: string | null;
14
+ }
15
+ export interface ContextIntentSignal {
16
+ source: 'explicit-query' | 'explicit-scenario' | 'context-state' | 'host-prompt' | 'path' | 'git';
17
+ value: string;
18
+ origin: ContextSignalOrigin;
19
+ confidence: 'high' | 'medium' | 'low';
20
+ }
21
+ export interface ContextIntent {
22
+ schemaVersion: 1;
23
+ trigger: 'session-start' | 'prompt' | 'manual' | 'tool-call';
24
+ query: string | null;
25
+ normalizedTerms: string[];
26
+ scenario: ContextScenario;
27
+ scope: string | null;
28
+ paths: string[];
29
+ contextId: string | null;
30
+ anchor: ContextAnchor;
31
+ signals: ContextIntentSignal[];
32
+ requestedSections: ContextCandidateSection[];
33
+ budget: ContextBudget;
34
+ adapter: string;
35
+ sessionId: string | null;
36
+ eventId: string | null;
37
+ }
38
+ export interface ContextBudget {
39
+ unit: 'chars';
40
+ total: number;
41
+ reservedForRenderer: number;
42
+ sections?: Partial<Record<ContextCandidateSection, number>>;
43
+ }
44
+ export interface ContextSourceApplicability {
45
+ project: true;
46
+ branchMode: 'global' | 'exact' | 'unsupported';
47
+ branches: string[];
48
+ paths: string[];
49
+ worktreeId: string | null;
50
+ ref: string | null;
51
+ }
52
+ export interface ContextProvenance {
53
+ origin: 'v1-file' | 'v2-journal' | 'mixed' | 'live';
54
+ eventId: string | null;
55
+ eventType: string | null;
56
+ occurredAt: string | null;
57
+ sourcePath: string | null;
58
+ sourceHash: string | null;
59
+ anchorProvenance: 'snapshot' | 'live' | 'global' | null;
60
+ }
61
+ export interface ContextSourceDescriptor {
62
+ sourceId: string;
63
+ kind: ContextSourceKind;
64
+ entityId: string;
65
+ contextId: string | null;
66
+ snapshotId: string | null;
67
+ status: string | null;
68
+ scope: string | null;
69
+ paths: string[];
70
+ anchor: ContextAnchor;
71
+ applicability: ContextSourceApplicability;
72
+ privacy: {
73
+ classification: 'metadata' | 'redacted-text' | 'unknown';
74
+ redactionCount: number;
75
+ };
76
+ provenance: ContextProvenance;
77
+ }
78
+ export interface ContextActivityRecord {
79
+ eventId: string;
80
+ occurredAt: string;
81
+ anchor: ContextAnchor;
82
+ paths: string[];
83
+ changes: WorkspacePathChange[];
84
+ pathCount: number;
85
+ coalesced: boolean;
86
+ truncated: boolean;
87
+ focusMatch: boolean;
88
+ }
89
+ export interface ContextCandidate<T = unknown> {
90
+ candidateId: string;
91
+ section: ContextCandidateSection;
92
+ source: ContextSourceDescriptor;
93
+ content: T;
94
+ text: string;
95
+ priority: number;
96
+ score: number;
97
+ matchedTerms: string[];
98
+ matchedFields: string[];
99
+ originalChars: number;
100
+ selectedChars: number;
101
+ selected: boolean;
102
+ truncated: boolean;
103
+ }
104
+ export type ContextExclusionReason = 'no_active_context' | 'context_mismatch' | 'status_not_verified' | 'not_open' | 'absorbed' | 'applicability_mismatch' | 'scope_mismatch' | 'path_mismatch' | 'query_no_match' | 'result_limit' | 'budget' | 'unsupported_applicability' | 'v2_unavailable';
105
+ export interface ContextExclusion {
106
+ candidateId: string;
107
+ source: ContextSourceDescriptor | null;
108
+ reason: ContextExclusionReason;
109
+ detail?: string;
110
+ }
111
+ export interface ContextTruncation {
112
+ truncated: boolean;
113
+ summary: boolean;
114
+ pending: boolean;
115
+ decisions: boolean;
116
+ risks: boolean;
117
+ notes: boolean;
118
+ knowledge: boolean;
119
+ deadends: boolean;
120
+ activity: boolean;
121
+ candidatesOmittedByBudget: number;
122
+ candidatesPartiallyTruncated: number;
123
+ }
124
+ export interface ContextBudgetReport {
125
+ requested: ContextBudget;
126
+ usedChars: number;
127
+ remainingChars: number;
128
+ sections: Partial<Record<ContextCandidateSection, {
129
+ allocated: number;
130
+ consumed: number;
131
+ truncated: boolean;
132
+ }>>;
133
+ }
134
+ export interface ContextManifestProvenance {
135
+ journalRead: boolean;
136
+ journalEventCount: number;
137
+ selectedSourceIds: string[];
138
+ selectedEventIds: string[];
139
+ fallbackReason: string | null;
140
+ }
141
+ export interface ContextCompileOptions {
142
+ contextId?: string;
143
+ query?: string;
144
+ scenario?: ContextScenario;
145
+ scope?: string;
146
+ paths?: string[];
147
+ maxChars?: number;
148
+ knowledgeLimit?: number;
149
+ deadendLimit?: number;
150
+ noteLimit?: number;
151
+ activityLimit?: number;
152
+ trigger?: ContextIntent['trigger'];
153
+ adapter?: string;
154
+ sessionId?: string;
155
+ eventId?: string;
156
+ }
157
+ export interface ContextManifest {
158
+ schemaVersion: 1;
159
+ compilerVersion: '0.7.2';
160
+ rankingVersion: 'compiler-ranking-v2';
161
+ status: 'ready' | 'no_context';
162
+ projectRoot: string;
163
+ mode: 'v2' | 'v1-fallback';
164
+ anchor: ContextAnchor;
165
+ intent: ContextIntent;
166
+ context: ContextOrientResult['context'];
167
+ snapshot: ContextOrientResult['snapshot'];
168
+ resumeSummary: ResumeSummary | null;
169
+ sections: {
170
+ summary: ContextCandidate[];
171
+ pending: ContextCandidate[];
172
+ decision: ContextCandidate[];
173
+ risk: ContextCandidate[];
174
+ notes: ContextCandidate[];
175
+ knowledge: ContextCandidate[];
176
+ deadends: ContextCandidate[];
177
+ activity: ContextCandidate<ContextActivityRecord>[];
178
+ };
179
+ selected: ContextCandidate[];
180
+ excluded: ContextExclusion[];
181
+ budget: ContextBudgetReport;
182
+ truncation: ContextTruncation;
183
+ provenance: ContextManifestProvenance;
184
+ text: string;
185
+ }
186
+ export type CompilerRecord = StructuredContext | Note | Knowledge | Deadend;
187
+ export type CompilerGitAnchor = CognitionGitAnchor;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,11 +1,10 @@
1
1
  import { contextOrient } from '../agent/api.js';
2
2
  import { appendEvent } from '../cognition/journal.js';
3
3
  import { v2Enabled } from '../cognition/orient.js';
4
- import { startRuntime } from '../cognition/runtime.js';
4
+ import { ensureRuntimeForSession, touchRuntime } from '../cognition/runtime.js';
5
+ import { loadRuntimePolicy } from '../cognition/policy.js';
5
6
  import { readGitWorktreeState } from '../git/git-adapter.js';
6
7
  import { resolveProjectRoot } from '../project/project-resolver.js';
7
- import { cognitionPolicyPath } from '../storage/layout.js';
8
- import { readJson } from '../storage/json-store.js';
9
8
  const HOOK_RESPONSE = '{}\n';
10
9
  function text(value) {
11
10
  return typeof value === 'string' ? value : undefined;
@@ -68,15 +67,12 @@ function redactIntent(value) {
68
67
  .replace(/\b(?:sk|ghp|gho|github_pat)_[A-Za-z0-9_-]+\b/g, () => { redactionCount += 1; return '[REDACTED]'; });
69
68
  return { intent, redactionCount };
70
69
  }
71
- async function within(task, milliseconds) {
72
- return Promise.race([task, new Promise((_, reject) => setTimeout(() => reject(new Error('hook deadline exceeded')), milliseconds))]);
73
- }
74
70
  async function capture(event, input) {
75
71
  const cwd = text(input.cwd);
76
72
  const projectRoot = await resolveProjectRoot(cwd);
77
73
  if (!await v2Enabled(projectRoot))
78
74
  return;
79
- const policy = await readJson(cognitionPolicyPath(projectRoot));
75
+ const policy = await loadRuntimePolicy(projectRoot);
80
76
  const sessionId = stableId(input, 'session');
81
77
  const eventId = stableId(input, 'event');
82
78
  if (!sessionId || !eventId)
@@ -94,12 +90,7 @@ async function capture(event, input) {
94
90
  });
95
91
  }
96
92
  async function maybeStartRuntime(input) {
97
- const projectRoot = await resolveProjectRoot(text(input.cwd));
98
- if (!await v2Enabled(projectRoot))
99
- return;
100
- const policy = await readJson(cognitionPolicyPath(projectRoot));
101
- if (policy.allowHookStartup)
102
- await within(startRuntime(projectRoot), 1_000);
93
+ await ensureRuntimeForSession(text(input.cwd), { source: 'claude-hook', timeoutMs: 1_000 });
103
94
  }
104
95
  async function parseInput(raw) {
105
96
  try {
@@ -115,9 +106,10 @@ export async function runClaudeCodeHook(event, raw) {
115
106
  if (!input)
116
107
  return HOOK_RESPONSE;
117
108
  try {
118
- if (event === 'session-start')
119
- await maybeStartRuntime(input).catch(() => undefined);
109
+ await maybeStartRuntime(input).catch(() => undefined);
110
+ await touchRuntime(text(input.cwd)).catch(() => false);
120
111
  await capture(event, input).catch(() => undefined);
112
+ await touchRuntime(text(input.cwd)).catch(() => false);
121
113
  const cwd = text(input.cwd);
122
114
  const result = event === 'session-start'
123
115
  ? await contextOrient(cwd, { maxChars: 1200, noteLimit: 8 })
@@ -1,5 +1,7 @@
1
1
  import { serveStdio } from '@modelcontextprotocol/server/stdio';
2
+ import { ensureRuntimeForSession } from '../cognition/runtime.js';
2
3
  import { createMcpServer } from './server.js';
4
+ await ensureRuntimeForSession(process.cwd(), { source: 'mcp', timeoutMs: 1_000 }).catch(() => undefined);
3
5
  serveStdio(createMcpServer, {
4
6
  onerror: (error) => process.stderr.write(`MCP server error: ${error.message}\n`),
5
7
  });
@@ -1,6 +1,7 @@
1
1
  import { McpServer } from '@modelcontextprotocol/server';
2
2
  import { z } from 'zod';
3
3
  import { contextOrient } from '../agent/api.js';
4
+ import { ensureRuntimeForSession } from '../cognition/runtime.js';
4
5
  import { VERSION } from '../version.js';
5
6
  const orientInput = z.object({
6
7
  path: z.string().optional(),
@@ -23,6 +24,7 @@ export function createMcpServer() {
23
24
  inputSchema: orientInput,
24
25
  }, async ({ path, ...options }) => {
25
26
  try {
27
+ await ensureRuntimeForSession(path, { source: 'mcp', timeoutMs: 1_000 }).catch(() => undefined);
26
28
  const result = await contextOrient(path, options);
27
29
  return {
28
30
  content: [{ type: 'text', text: JSON.stringify(result) }],
@@ -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.2";
@@ -1 +1 @@
1
- export const VERSION = '0.6.0';
1
+ export const VERSION = '0.7.2';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fluffy-context",
3
- "version": "0.6.0",
3
+ "version": "0.7.2",
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,8 @@ 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。
21
22
  - 新任务先发现已验证共识,再开始实现;候选项只在显式审查时使用。
22
23
  - 普通查询只使用已验证 Knowledge 和 Deadend;需要审查候选项时显式使用 `--all`。
23
24
  - CLI 业务命令的标准输出是 JSON;错误写入标准错误并返回非零退出码。解析输出时不要把 `--help` 的纯文本当作 JSON。