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
@@ -0,0 +1,36 @@
1
+ import crypto from 'node:crypto';
2
+ export function canonicalJson(value) {
3
+ if (value === null || typeof value !== 'object')
4
+ return JSON.stringify(value) ?? 'null';
5
+ if (Array.isArray(value))
6
+ return `[${value.map(canonicalJson).join(',')}]`;
7
+ const record = value;
8
+ return `{${Object.keys(record).filter((key) => record[key] !== undefined).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(',')}}`;
9
+ }
10
+ export function contentHash(value) {
11
+ return crypto.createHash('sha256').update(canonicalJson(value)).digest('hex');
12
+ }
13
+ export function estimateTokens(value) {
14
+ return value.length === 0 ? 0 : Math.ceil(value.length / 4);
15
+ }
16
+ export function rebuildManifestHash(manifest) {
17
+ return contentHash({
18
+ schemaVersion: 1,
19
+ compilerVersion: '0.7.6',
20
+ rankingVersion: 'compiler-ranking-v2',
21
+ status: manifest.status,
22
+ projectRoot: manifest.projectRoot,
23
+ mode: manifest.mode,
24
+ anchor: manifest.anchor,
25
+ intent: manifest.intent,
26
+ selectedItemHashes: manifest.selected.map((item) => item.itemHash),
27
+ budget: { usedChars: manifest.budget.usedChars, maxChars: manifest.budget.requested.total },
28
+ truncation: manifest.truncation,
29
+ });
30
+ }
31
+ export function validateManifestIdentity(manifest) {
32
+ return manifest.identity.manifestHash === rebuildManifestHash(manifest)
33
+ && JSON.stringify(manifest.identity.selectedItemHashes) === JSON.stringify(manifest.selected.map((item) => item.itemHash))
34
+ && manifest.identity.tokenEstimate.input === estimateTokens(manifest.text)
35
+ && manifest.identity.tokenEstimate.output === estimateTokens(manifest.text);
36
+ }
@@ -0,0 +1,19 @@
1
+ import type { BranchWorkspace, CognitionEvent } from '../cognition/types.js';
2
+ import type { ContextOrientResult } from '../agent/types.js';
3
+ import type { ContextAnchor, ContextProvenance, ContextSourceDescriptor, ContextSourceApplicability } from './types.js';
4
+ import type { ContextCompileOptions } from './types.js';
5
+ export interface CompilerSources {
6
+ orientation: ContextOrientResult;
7
+ anchor: ContextAnchor;
8
+ events: CognitionEvent[];
9
+ workspace: BranchWorkspace | null;
10
+ fallbackReason: string | null;
11
+ }
12
+ export declare function sourceApplicability(event: CognitionEvent | null, anchor: ContextAnchor): ContextSourceApplicability;
13
+ export declare function provenanceFor(events: CognitionEvent[], entityId: string, anchor: ContextAnchor): ContextProvenance;
14
+ export declare function sourceDescriptor(projectRoot: string, anchor: ContextAnchor, events: CognitionEvent[], kind: ContextSourceDescriptor['kind'], entityId: string, contextId: string | null, snapshotId: string | null, status: string | null, scope: string | null, paths: string[]): ContextSourceDescriptor;
15
+ export declare function loadCompilerSources(startPath: string | undefined, options: ContextCompileOptions): Promise<CompilerSources>;
16
+ export declare function latestHostIntent(events: CognitionEvent[], anchor: ContextAnchor): {
17
+ value: string;
18
+ eventId: string;
19
+ } | null;
@@ -0,0 +1,126 @@
1
+ import { readGitWorktreeState } from '../git/git-adapter.js';
2
+ import { contextOrient } from '../agent/api.js';
3
+ import { readJournal } from '../cognition/journal.js';
4
+ import { projectEvents, workspaceKey } from '../cognition/projections.js';
5
+ function anchorFromGit(git, projectRoot) {
6
+ return {
7
+ projectRoot,
8
+ worktreeId: git.worktreeId,
9
+ branch: git.branch,
10
+ ref: git.ref,
11
+ head: git.commit,
12
+ };
13
+ }
14
+ function payloadRecord(event) {
15
+ const payload = event.payload;
16
+ return payload.record && typeof payload.record === 'object' && !Array.isArray(payload.record)
17
+ ? payload.record
18
+ : null;
19
+ }
20
+ function matchesRecord(event, entityId) {
21
+ if (event.eventId === entityId)
22
+ return true;
23
+ const record = payloadRecord(event);
24
+ return record?.contextId === entityId
25
+ || record?.knowledgeId === entityId
26
+ || record?.deadendId === entityId
27
+ || record?.noteId === entityId
28
+ || record?.snapshotId === entityId;
29
+ }
30
+ export function sourceApplicability(event, anchor) {
31
+ if (!event || (event.git.branch === null && event.git.ref === null)) {
32
+ return { project: true, branchMode: 'global', branches: [], paths: [], worktreeId: null, ref: null };
33
+ }
34
+ return {
35
+ project: true,
36
+ branchMode: 'exact',
37
+ branches: [event.git.ref ?? event.git.branch ?? 'detached'],
38
+ paths: [],
39
+ worktreeId: event.git.worktreeId,
40
+ ref: event.git.ref,
41
+ };
42
+ }
43
+ export function provenanceFor(events, entityId, anchor) {
44
+ const event = [...events].reverse().find((item) => matchesRecord(item, entityId));
45
+ if (!event) {
46
+ return {
47
+ origin: 'v1-file',
48
+ eventId: null,
49
+ eventType: null,
50
+ occurredAt: null,
51
+ sourcePath: null,
52
+ sourceHash: null,
53
+ anchorProvenance: null,
54
+ };
55
+ }
56
+ const payload = event.payload;
57
+ return {
58
+ origin: 'v2-journal',
59
+ eventId: event.eventId,
60
+ eventType: event.type,
61
+ occurredAt: event.occurredAt,
62
+ sourcePath: typeof payload.sourcePath === 'string' ? payload.sourcePath : null,
63
+ sourceHash: typeof payload.sourceHash === 'string' ? payload.sourceHash : null,
64
+ anchorProvenance: payload.anchorProvenance === 'snapshot' || payload.anchorProvenance === 'live' || payload.anchorProvenance === 'global'
65
+ ? payload.anchorProvenance
66
+ : null,
67
+ };
68
+ }
69
+ export function sourceDescriptor(projectRoot, anchor, events, kind, entityId, contextId, snapshotId, status, scope, paths) {
70
+ const provenance = provenanceFor(events, entityId, anchor);
71
+ const event = provenance.eventId ? events.find((item) => item.eventId === provenance.eventId) ?? null : null;
72
+ return {
73
+ sourceId: `${kind}:${entityId}`,
74
+ kind,
75
+ entityId,
76
+ contextId,
77
+ snapshotId,
78
+ status,
79
+ scope,
80
+ paths: [...new Set(paths)].sort(),
81
+ anchor,
82
+ applicability: sourceApplicability(event, anchor),
83
+ privacy: { classification: provenance.origin === 'v2-journal' ? (event?.privacy.classification ?? 'metadata') : 'metadata', redactionCount: event?.privacy.redactionCount ?? 0 },
84
+ provenance,
85
+ };
86
+ }
87
+ export async function loadCompilerSources(startPath, options) {
88
+ const orientation = await contextOrient(startPath, {
89
+ contextId: options.contextId,
90
+ query: options.query,
91
+ scope: options.scope,
92
+ maxChars: Number.MAX_SAFE_INTEGER,
93
+ knowledgeLimit: options.knowledgeLimit ?? 10,
94
+ deadendLimit: options.deadendLimit ?? 10,
95
+ noteLimit: options.noteLimit ?? 20,
96
+ });
97
+ const projectRoot = orientation.projectRoot;
98
+ const git = await readGitWorktreeState(projectRoot);
99
+ let events = [];
100
+ let fallbackReason = null;
101
+ try {
102
+ events = await readJournal(projectRoot);
103
+ }
104
+ catch (error) {
105
+ fallbackReason = error instanceof Error ? error.message : 'journal-unavailable';
106
+ }
107
+ const anchor = anchorFromGit(git, projectRoot);
108
+ const projection = events.length > 0 ? projectEvents(events) : null;
109
+ const workspace = projection?.workspaces[workspaceKey(anchor)] ?? null;
110
+ return { orientation, anchor, events, workspace, fallbackReason };
111
+ }
112
+ export function latestHostIntent(events, anchor) {
113
+ const event = [...events].reverse().find((candidate) => {
114
+ if (candidate.type !== 'host.prompt.submitted')
115
+ return false;
116
+ if (candidate.git.worktreeId !== null && candidate.git.worktreeId !== anchor.worktreeId)
117
+ return false;
118
+ if (candidate.git.ref !== null && candidate.git.ref !== anchor.ref)
119
+ return false;
120
+ const payload = candidate.payload;
121
+ return typeof payload.intent === 'string' && payload.intent.trim().length > 0;
122
+ });
123
+ if (!event)
124
+ return null;
125
+ return { value: event.payload.intent, eventId: event.eventId };
126
+ }
@@ -0,0 +1,233 @@
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 ContextLevel = 'signal' | 'summary' | 'structured' | 'evidence';
6
+ export type ContextExpandLevel = 'structured' | 'evidence';
7
+ export type ContextSourceKind = 'current-context' | 'checkpoint' | 'note' | 'journal' | 'knowledge' | 'deadend';
8
+ export type ContextCandidateSection = 'summary' | 'pending' | 'decision' | 'risk' | 'note' | 'knowledge' | 'deadend' | 'activity';
9
+ export type ContextSignalOrigin = 'explicit' | 'deterministic';
10
+ export interface ContextAnchor {
11
+ projectRoot: string;
12
+ worktreeId: string | null;
13
+ branch: string | null;
14
+ ref: string | null;
15
+ head: string | null;
16
+ }
17
+ export interface ContextIntentSignal {
18
+ source: 'explicit-query' | 'explicit-scenario' | 'context-state' | 'host-prompt' | 'path' | 'git';
19
+ value: string;
20
+ origin: ContextSignalOrigin;
21
+ confidence: 'high' | 'medium' | 'low';
22
+ }
23
+ export interface ContextIntent {
24
+ schemaVersion: 1;
25
+ trigger: 'session-start' | 'prompt' | 'manual' | 'tool-call';
26
+ query: string | null;
27
+ normalizedTerms: string[];
28
+ scenario: ContextScenario;
29
+ scope: string | null;
30
+ paths: string[];
31
+ contextId: string | null;
32
+ anchor: ContextAnchor;
33
+ signals: ContextIntentSignal[];
34
+ requestedSections: ContextCandidateSection[];
35
+ budget: ContextBudget;
36
+ adapter: string;
37
+ sessionId: string | null;
38
+ eventId: string | null;
39
+ }
40
+ export interface ContextBudget {
41
+ unit: 'chars';
42
+ total: number;
43
+ reservedForRenderer: number;
44
+ sections?: Partial<Record<ContextCandidateSection, number>>;
45
+ }
46
+ export interface ContextTokenEstimate {
47
+ input: number;
48
+ output: number;
49
+ unit: 'estimated-tokens';
50
+ method: 'chars-div-four';
51
+ }
52
+ export interface ContextSourceApplicability {
53
+ project: true;
54
+ branchMode: 'global' | 'exact' | 'unsupported';
55
+ branches: string[];
56
+ paths: string[];
57
+ worktreeId: string | null;
58
+ ref: string | null;
59
+ }
60
+ export interface ContextProvenance {
61
+ origin: 'v1-file' | 'v2-journal' | 'mixed' | 'live';
62
+ eventId: string | null;
63
+ eventType: string | null;
64
+ occurredAt: string | null;
65
+ sourcePath: string | null;
66
+ sourceHash: string | null;
67
+ anchorProvenance: 'snapshot' | 'live' | 'global' | null;
68
+ }
69
+ export interface ContextSourceDescriptor {
70
+ sourceId: string;
71
+ kind: ContextSourceKind;
72
+ entityId: string;
73
+ contextId: string | null;
74
+ snapshotId: string | null;
75
+ status: string | null;
76
+ scope: string | null;
77
+ paths: string[];
78
+ anchor: ContextAnchor;
79
+ applicability: ContextSourceApplicability;
80
+ privacy: {
81
+ classification: 'metadata' | 'redacted-text' | 'unknown';
82
+ redactionCount: number;
83
+ };
84
+ provenance: ContextProvenance;
85
+ }
86
+ export interface ContextActivityRecord {
87
+ eventId: string;
88
+ occurredAt: string;
89
+ anchor: ContextAnchor;
90
+ paths: string[];
91
+ changes: WorkspacePathChange[];
92
+ pathCount: number;
93
+ coalesced: boolean;
94
+ truncated: boolean;
95
+ focusMatch: boolean;
96
+ }
97
+ export interface ContextCandidate<T = unknown> {
98
+ candidateId: string;
99
+ section: ContextCandidateSection;
100
+ source: ContextSourceDescriptor;
101
+ content: T;
102
+ text: string;
103
+ priority: number;
104
+ score: number;
105
+ matchedTerms: string[];
106
+ matchedFields: string[];
107
+ originalChars: number;
108
+ selectedChars: number;
109
+ selected: boolean;
110
+ truncated: boolean;
111
+ level: ContextLevel;
112
+ itemHash: string;
113
+ tokenEstimate: number;
114
+ expandable: boolean;
115
+ }
116
+ 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';
117
+ export interface ContextExclusion {
118
+ candidateId: string;
119
+ source: ContextSourceDescriptor | null;
120
+ reason: ContextExclusionReason;
121
+ detail?: string;
122
+ }
123
+ export interface ContextTruncation {
124
+ truncated: boolean;
125
+ summary: boolean;
126
+ pending: boolean;
127
+ decisions: boolean;
128
+ risks: boolean;
129
+ notes: boolean;
130
+ knowledge: boolean;
131
+ deadends: boolean;
132
+ activity: boolean;
133
+ candidatesOmittedByBudget: number;
134
+ candidatesPartiallyTruncated: number;
135
+ }
136
+ export interface ContextBudgetReport {
137
+ requested: ContextBudget;
138
+ usedChars: number;
139
+ remainingChars: number;
140
+ sections: Partial<Record<ContextCandidateSection, {
141
+ allocated: number;
142
+ consumed: number;
143
+ truncated: boolean;
144
+ }>>;
145
+ }
146
+ export interface ContextManifestProvenance {
147
+ journalRead: boolean;
148
+ journalEventCount: number;
149
+ selectedSourceIds: string[];
150
+ selectedEventIds: string[];
151
+ fallbackReason: string | null;
152
+ }
153
+ export interface ContextManifestIdentity {
154
+ manifestHash: string;
155
+ selectedItemHashes: string[];
156
+ tokenEstimate: ContextTokenEstimate;
157
+ }
158
+ export interface ContextCompileOptions {
159
+ contextId?: string;
160
+ query?: string;
161
+ scenario?: ContextScenario;
162
+ scope?: string;
163
+ paths?: string[];
164
+ maxChars?: number;
165
+ knowledgeLimit?: number;
166
+ deadendLimit?: number;
167
+ noteLimit?: number;
168
+ activityLimit?: number;
169
+ trigger?: ContextIntent['trigger'];
170
+ adapter?: string;
171
+ sessionId?: string;
172
+ eventId?: string;
173
+ }
174
+ export interface ContextExpandInput {
175
+ manifestHash: string;
176
+ candidateId?: string;
177
+ itemHash?: string;
178
+ level: ContextExpandLevel;
179
+ maxChars?: number;
180
+ compile: ContextCompileOptions;
181
+ }
182
+ export interface ContextExpandResult {
183
+ status: 'complete' | 'partial';
184
+ projectRoot: string;
185
+ manifestHash: string;
186
+ candidateId: string;
187
+ itemHash: string;
188
+ previousLevel: ContextLevel;
189
+ requestedLevel: ContextExpandLevel;
190
+ resolvedLevel: ContextExpandLevel;
191
+ source: ContextSourceDescriptor;
192
+ content: unknown | null;
193
+ text: string;
194
+ budget: {
195
+ requestedChars: number;
196
+ usedChars: number;
197
+ remainingChars: number;
198
+ truncated: boolean;
199
+ };
200
+ provenance: ContextManifestProvenance;
201
+ }
202
+ export interface ContextManifest {
203
+ schemaVersion: 1;
204
+ compilerVersion: '0.7.6';
205
+ rankingVersion: 'compiler-ranking-v2';
206
+ status: 'ready' | 'no_context';
207
+ projectRoot: string;
208
+ mode: 'v2' | 'v1-fallback';
209
+ anchor: ContextAnchor;
210
+ intent: ContextIntent;
211
+ context: ContextOrientResult['context'];
212
+ snapshot: ContextOrientResult['snapshot'];
213
+ resumeSummary: ResumeSummary | null;
214
+ sections: {
215
+ summary: ContextCandidate[];
216
+ pending: ContextCandidate[];
217
+ decision: ContextCandidate[];
218
+ risk: ContextCandidate[];
219
+ notes: ContextCandidate[];
220
+ knowledge: ContextCandidate[];
221
+ deadends: ContextCandidate[];
222
+ activity: ContextCandidate<ContextActivityRecord>[];
223
+ };
224
+ selected: ContextCandidate[];
225
+ excluded: ContextExclusion[];
226
+ identity: ContextManifestIdentity;
227
+ budget: ContextBudgetReport;
228
+ truncation: ContextTruncation;
229
+ provenance: ContextManifestProvenance;
230
+ text: string;
231
+ }
232
+ export type CompilerRecord = StructuredContext | Note | Knowledge | Deadend;
233
+ 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
  });