claude-faf-mcp 5.7.0 → 5.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <!-- faf: claude-faf-mcp | TypeScript | mcp-server | FAF MCP server for Claude Desktop — persistent project context, 32 tools -->
2
- <!-- faf: doc=changelog | latest=v5.7.0 | canonical=project.faf | family=FAF -->
2
+ <!-- faf: doc=changelog | latest=v5.7.2 | canonical=project.faf | family=FAF -->
3
3
 
4
4
  # Changelog
5
5
 
@@ -9,6 +9,37 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
9
9
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
10
10
 
11
11
 
12
+ ## [Unreleased]
13
+
14
+ **Interop now enhances your files.** The same solid, structured `.faf` data is prefixed to the top of your context files for rapid AI consumption upfront — and your Markdown stays in the instruction lane.
15
+
16
+ ### Changed
17
+
18
+ - **Non-destructive interop.** `faf_agents`, `faf_gemini`, `faf_cursor`, and `faf_sync` now inject a structured `.faf` block at the top of AGENTS.md, GEMINI.md, .cursorrules, and CLAUDE.md and preserve everything you've written below. Re-runs update the block in place (idempotent); existing faf-generated files upgrade cleanly in one pass.
19
+
20
+ ## [5.7.2] - 2026-06-11 — The Canonical Edition
21
+
22
+ ### Security
23
+ - **Path confinement on every caller-supplied `path` argument (CWE-22 / CWE-73 / CWE-200).** The shared `getProjectPath()` chokepoint (feeding the `.faf` tools) and the `faf_read` / `faf_write` file tools resolved a caller path straight into a filesystem read/write with no confinement — so an absolute path or `../` traversal could read any file the server process could read (e.g. `/etc/passwd`, `~/.ssh/id_rsa`) or write outside the project. New `safe-path.ts` confines reads to `.faf` / `.fafm` context files and general file ops to the project root (cwd + system temp; override with `FAF_ALLOWED_ROOTS`), canonicalizes through symlinks (closing the symlink bypass), and rejects traversal/absolute escapes; `callTool()` gains a central PATH-DENIED guard. Identified by the maintainers during a sibling-server audit prompted by the coordinated disclosure of the same class of issue in `grok-faf-mcp` by Zhihao Zhang (Worcester Polytechnic Institute). Adds a security regression suite (incl. symlink bypass).
24
+
25
+ ## [5.7.1] - 2026-06-10 — The Canonical Edition
26
+
27
+ Typed structured output and one-click registry install — the reliability layer made machine-readable.
28
+
29
+ ### Added
30
+
31
+ - **MCP `structuredContent` + `outputSchema`** on `faf_score` and `faf_status` — typed structured results (score, tier, populated/empty/ignored/total, next tier; status, filename, path) returned alongside the human-readable text on every path. Spec 2025-06-18; text retained for backward compatibility.
32
+ - **`mcpb` registry package** in `server.json` — one-click Claude Desktop install from the MCP registry, pointing at the released `.mcpb` with its SHA-256.
33
+
34
+ ### Fixed
35
+
36
+ - **README one-click link** pointed at `claude-faf-mcp-5.5.2.mcpb` (404 on the current release) → now the live bundle.
37
+
38
+ ### Changed
39
+
40
+ - **No stale version references** — faf-cli bumped to `^6.8.0` (latest); `project.faf` `faf_version` corrected to `2.5.0` (matches the latest tooling / base tier, not the enterprise 33-slot marker); `docs/index.html` version badge and a hardcoded server-info version string now track the package version.
41
+
42
+
12
43
  ## [5.7.0] - 2026-06-09 — The Canonical Edition
13
44
 
14
45
  The original FAF MCP, made canonical: the registry remote now hits the live
package/CLAUDE.md CHANGED
@@ -1,11 +1,11 @@
1
- <!-- faf: claude-faf-mcp | TypeScript | mcp | Universal FAF MCP Server for Claude Desktop - AI Context Intelligence -->
1
+ <!-- faf: claude-faf-mcp | TypeScript | mcp | Persistent project context for Claude define once, never re-explain -->
2
2
  <!-- faf: claim=project.faf | family=FAF -->
3
3
 
4
4
  # CLAUDE.md — claude-faf-mcp
5
5
 
6
6
  ## What This Is
7
7
 
8
- Universal FAF MCP Server for Claude Desktop - AI Context Intelligence
8
+ Persistent project context for Claude define once, never re-explain
9
9
 
10
10
  ## Stack
11
11
 
@@ -14,18 +14,18 @@ Universal FAF MCP Server for Claude Desktop - AI Context Intelligence
14
14
  - **Api Type:** MCP (stdio + Streamable HTTP)
15
15
  - **Runtime:** Node.js
16
16
  - **Hosting:** npm/Claude Desktop
17
- - **Build:** esbuild
17
+ - **Build:** tsc
18
18
  - **Cicd:** GitHub Actions
19
19
 
20
20
  ## Context
21
21
 
22
- - **Who:** Claude Desktop users who want persistent AI project context
23
- - **What:** MCP server — 32 FAF tools for project DNA, scoring, and context sync
24
- - **Why:** Zero project re-explaining across AI sessions — define once, remember forever
25
- - **Where:** npm, Claude Desktop, MCP Registry (io.github.Wolfe-Jam/claude-faf-mcp)
26
- - **When:** v5.7.0 (The Canonical Edition), June 2026
27
- - **How:** npm install -g claude-faf-mcp
22
+ - **Who:** Claude Desktop + Code devs
23
+ - **What:** Persistent project-context MCP server
24
+ - **Why:** Define once, never re-explain
25
+ - **Where:** npm, Claude, MCP registry
26
+ - **When:** Every coding session
27
+ - **How:** One-click .mcpb install
28
28
 
29
29
  ---
30
30
 
31
- *STATUS: BI-SYNC ACTIVE — 2026-06-09T12:52:07.994Z*
31
+ *STATUS: BI-SYNC ACTIVE — 2026-06-10T01:27:09.822Z*
package/README.md CHANGED
@@ -26,7 +26,7 @@
26
26
 
27
27
  > ⚡ **New: `/faf` prompt** — type `/faf` in Claude Desktop. It checks your project, scores it, drives it to 100%, and syncs. Relentlessly. One command.
28
28
 
29
- > 🏆 **v5.7.0 — The Canonical Edition.** The original FAF MCP, made canonical: the registry remote now hits the live Cloudflare edge directly (no Vercel/SSE redirect), the redundant `faf_chat` shim is retired (the host is your chat) consolidated into 32 must-have tools.
29
+ > 🏆 **v5.7.2 — The Canonical Edition.** Security patch caller-supplied `path` arguments are now confined: reads are restricted to `.faf` / `.fafm` context files and file ops to the project root (override with `FAF_ALLOWED_ROOTS`), closing an arbitrary local file read. Built on the Canonical foundation: typed structured output (`structuredContent` + output schemas), edge-direct remote, 32 must-have tools.
30
30
 
31
31
  32 MCP tools. IANA-registered formats (`application/vnd.faf+yaml` · `application/vnd.fafm+yaml`). 2,538 test executions per push.
32
32
 
@@ -82,7 +82,7 @@ Same `.faf`, every surface — Claude, Gemini, Grok, Cursor. **[faf-cli on npm
82
82
 
83
83
  **Click** — one-click `.mcpb`
84
84
 
85
- [**⬇ Download `claude-faf-mcp-5.5.2.mcpb`**](https://github.com/Wolfe-Jam/claude-faf-mcp/releases/latest/download/claude-faf-mcp-5.5.2.mcpb)
85
+ [**⬇ Download `claude-faf-mcp-5.7.2.mcpb`**](https://github.com/Wolfe-Jam/claude-faf-mcp/releases/latest/download/claude-faf-mcp-5.7.2.mcpb)
86
86
 
87
87
  Double-click. **No terminal. No JSON config. 32 tools live in 10 seconds.**
88
88
 
@@ -45,6 +45,7 @@ const path = __importStar(require("path"));
45
45
  const fs_1 = require("fs");
46
46
  const file_utils_1 = require("../utils/file-utils");
47
47
  const agents_js_1 = require("./agents.js");
48
+ const inject_1 = require("../inject");
48
49
  const cursor_js_1 = require("./cursor.js");
49
50
  const gemini_js_1 = require("./gemini.js");
50
51
  /**
@@ -134,7 +135,7 @@ async function syncBiDirectional(projectPath, _options = {}) {
134
135
  if (!claudeMdExists) {
135
136
  // Create CLAUDE.md from project.faf
136
137
  const claudeMdContent = fafToClaudeMd(fafContent);
137
- await fs_1.promises.writeFile(claudeMdPath, claudeMdContent, 'utf-8');
138
+ await (0, inject_1.injectFafBlock)(claudeMdPath, claudeMdContent);
138
139
  result.success = true;
139
140
  result.direction = 'faf-to-claude';
140
141
  result.filesChanged.push('CLAUDE.md');
@@ -143,7 +144,7 @@ async function syncBiDirectional(projectPath, _options = {}) {
143
144
  else {
144
145
  // Both files exist - update CLAUDE.md from project.faf
145
146
  const claudeMdContent = fafToClaudeMd(fafContent);
146
- await fs_1.promises.writeFile(claudeMdPath, claudeMdContent, 'utf-8');
147
+ await (0, inject_1.injectFafBlock)(claudeMdPath, claudeMdContent);
147
148
  result.success = true;
148
149
  result.direction = 'faf-to-claude';
149
150
  result.filesChanged.push('CLAUDE.md');
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Block markers for the faf-managed front section. Markdown files use HTML
3
+ * comments; non-markdown files (.cursorrules) pass hash-comment markers.
4
+ */
5
+ export declare const FAF_START = "<!-- faf:start -->";
6
+ export declare const FAF_END = "<!-- faf:end -->";
7
+ /**
8
+ * Non-destructively write a faf-managed block into a file.
9
+ *
10
+ * - no file -> create it with just the block
11
+ * - markers present -> replace ONLY between them (update in place)
12
+ * - legacy faf file (metastamp, no markers) -> reclaim in place (no duplication)
13
+ * - genuine user file -> prefix the block; preserve everything below
14
+ *
15
+ * Idempotent: re-runs update the block, never duplicate or destroy user content.
16
+ * faf owns what's between the markers; the user owns everything else.
17
+ * Enhance, never replace.
18
+ */
19
+ export declare function injectFafBlock(path: string, block: string, start?: string, end?: string): Promise<void>;
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FAF_END = exports.FAF_START = void 0;
4
+ exports.injectFafBlock = injectFafBlock;
5
+ const fs_1 = require("fs");
6
+ /**
7
+ * Block markers for the faf-managed front section. Markdown files use HTML
8
+ * comments; non-markdown files (.cursorrules) pass hash-comment markers.
9
+ */
10
+ exports.FAF_START = '<!-- faf:start -->';
11
+ exports.FAF_END = '<!-- faf:end -->';
12
+ /**
13
+ * faf's own metastamp fingerprint. Every faf-generated file begins with it and a
14
+ * user never hand-writes it — so a markerless file led by it is legacy faf output
15
+ * we can safely reclaim, never genuine user content.
16
+ */
17
+ const FAF_METASTAMP = '<!-- faf:';
18
+ /**
19
+ * Non-destructively write a faf-managed block into a file.
20
+ *
21
+ * - no file -> create it with just the block
22
+ * - markers present -> replace ONLY between them (update in place)
23
+ * - legacy faf file (metastamp, no markers) -> reclaim in place (no duplication)
24
+ * - genuine user file -> prefix the block; preserve everything below
25
+ *
26
+ * Idempotent: re-runs update the block, never duplicate or destroy user content.
27
+ * faf owns what's between the markers; the user owns everything else.
28
+ * Enhance, never replace.
29
+ */
30
+ async function injectFafBlock(path, block, start = exports.FAF_START, end = exports.FAF_END) {
31
+ const wrapped = `${start}\n${block.trim()}\n${end}`;
32
+ let existing = null;
33
+ try {
34
+ existing = await fs_1.promises.readFile(path, 'utf-8');
35
+ }
36
+ catch {
37
+ existing = null; // file does not exist yet
38
+ }
39
+ if (existing === null) {
40
+ await fs_1.promises.writeFile(path, `${wrapped}\n`, 'utf-8');
41
+ return;
42
+ }
43
+ const s = existing.indexOf(start);
44
+ const e = existing.indexOf(end);
45
+ if (s !== -1 && e !== -1 && e > s) {
46
+ await fs_1.promises.writeFile(path, existing.slice(0, s) + wrapped + existing.slice(e + end.length), 'utf-8');
47
+ return;
48
+ }
49
+ if (existing.trimStart().startsWith(FAF_METASTAMP)) {
50
+ // Legacy faf output — reclaim in place, no duplication.
51
+ await fs_1.promises.writeFile(path, `${wrapped}\n`, 'utf-8');
52
+ return;
53
+ }
54
+ // Genuine user file — prefix the block, preserve everything.
55
+ await fs_1.promises.writeFile(path, `${wrapped}\n\n${existing}`, 'utf-8');
56
+ }
57
+ //# sourceMappingURL=inject.js.map
@@ -23,6 +23,7 @@ exports.detectAgentsMd = detectAgentsMd;
23
23
  exports.detectGlobalAgentsMd = detectGlobalAgentsMd;
24
24
  const fs_1 = require("fs");
25
25
  const path_1 = __importDefault(require("path"));
26
+ const inject_1 = require("../inject");
26
27
  // ============================================================================
27
28
  // Parsing
28
29
  // ============================================================================
@@ -278,9 +279,9 @@ async function agentsExport(fafContent, outputPath) {
278
279
  lines.push('---');
279
280
  lines.push(`*Generated from project.faf by claude-faf-mcp — ${new Date().toISOString().split('T')[0]}*`);
280
281
  lines.push('');
281
- // Write file
282
+ // Write file — non-destructive: inject/update the faf block, preserve the rest.
282
283
  const content = lines.join('\n');
283
- await fs_1.promises.writeFile(outputPath, content);
284
+ await (0, inject_1.injectFafBlock)(outputPath, content);
284
285
  return {
285
286
  success: true,
286
287
  filePath: outputPath,
@@ -25,6 +25,7 @@ exports.cursorExport = cursorExport;
25
25
  exports.detectCursorRules = detectCursorRules;
26
26
  const fs_1 = require("fs");
27
27
  const path_1 = __importDefault(require("path"));
28
+ const inject_1 = require("../inject");
28
29
  // ============================================================================
29
30
  // Parsing
30
31
  // ============================================================================
@@ -41,6 +42,9 @@ function parseCursorRules(content) {
41
42
  const rawLines = [];
42
43
  for (const line of lines) {
43
44
  rawLines.push(line);
45
+ // Skip faf's own block markers — they are not headings or content.
46
+ if (line.trim() === '# faf:start' || line.trim() === '# faf:end')
47
+ continue;
44
48
  // H1 = Project name
45
49
  const h1Match = line.match(/^#\s+(?:Project:\s*)?(.+)$/);
46
50
  if (h1Match) {
@@ -286,9 +290,9 @@ async function cursorExport(fafContent, outputPath) {
286
290
  lines.push('---');
287
291
  lines.push(`Generated from project.faf by claude-faf-mcp — ${new Date().toISOString().split('T')[0]}`);
288
292
  lines.push('');
289
- // Write file
293
+ // Write file — non-destructive: inject/update the faf block (hash-comment markers), preserve the rest.
290
294
  const content = lines.join('\n');
291
- await fs_1.promises.writeFile(outputPath, content);
295
+ await (0, inject_1.injectFafBlock)(outputPath, content, '# faf:start', '# faf:end');
292
296
  return {
293
297
  success: true,
294
298
  filePath: outputPath,
@@ -23,6 +23,7 @@ exports.detectGeminiMd = detectGeminiMd;
23
23
  exports.detectGlobalGeminiMd = detectGlobalGeminiMd;
24
24
  const fs_1 = require("fs");
25
25
  const path_1 = __importDefault(require("path"));
26
+ const inject_1 = require("../inject");
26
27
  // ============================================================================
27
28
  // Parsing
28
29
  // ============================================================================
@@ -218,9 +219,9 @@ async function geminiExport(fafContent, outputPath) {
218
219
  }
219
220
  lines.push('');
220
221
  }
221
- // Write file
222
+ // Write file — non-destructive: inject/update the faf block, preserve the rest.
222
223
  const content = lines.join('\n');
223
- await fs_1.promises.writeFile(outputPath, content);
224
+ await (0, inject_1.injectFafBlock)(outputPath, content);
224
225
  return {
225
226
  success: true,
226
227
  filePath: outputPath,
@@ -0,0 +1,54 @@
1
+ /**
2
+ * `.fafa` → A2A AgentCard mapper.
3
+ *
4
+ * Maps CFM's FAF Agent Card (`.fafa`) to a conformant **A2A AgentCard**
5
+ * (A2A protocol v1.0, served at `/.well-known/a2a-agent-card`).
6
+ *
7
+ * Posture: `.fafa` is a PEER to the A2A AgentCard — **compatible + enhancing**.
8
+ * Every A2A field maps from `.fafa`; the enhancement is the **FAF context block**,
9
+ * which rides as an A2A extension (`one.faf/context`) — byte-equivalent to the MCP
10
+ * server-card `_meta["one.faf/context"]`. One context, every door.
11
+ *
12
+ * Pure mapper (testable). The live A2A *invocation* endpoint (JSON-RPC `message/send`)
13
+ * is served at the edge — this module produces the discovery card that points at it.
14
+ */
15
+ export declare const A2A_PROTOCOL_VERSION = "1.0";
16
+ export declare const A2A_WELL_KNOWN_PATH = "/.well-known/a2a-agent-card";
17
+ export interface A2AAgentSkill {
18
+ id: string;
19
+ name: string;
20
+ description?: string;
21
+ tags: string[];
22
+ examples?: string[];
23
+ inputModes?: string[];
24
+ outputModes?: string[];
25
+ }
26
+ export interface A2AAgentCard {
27
+ protocolVersion: string;
28
+ name: string;
29
+ description?: string;
30
+ url: string;
31
+ version?: string;
32
+ provider?: {
33
+ organization?: string;
34
+ url?: string;
35
+ };
36
+ capabilities: {
37
+ streaming: boolean;
38
+ pushNotifications: boolean;
39
+ extendedAgentCard: boolean;
40
+ };
41
+ defaultInputModes: string[];
42
+ defaultOutputModes: string[];
43
+ skills: A2AAgentSkill[];
44
+ extensions?: {
45
+ uri: string;
46
+ description?: string;
47
+ required: boolean;
48
+ params?: Record<string, unknown>;
49
+ }[];
50
+ }
51
+ /** Map a parsed `.fafa` document to a conformant A2A AgentCard. */
52
+ export declare function fafaToA2ACard(fafa: any, opts: {
53
+ url: string;
54
+ }): A2AAgentCard;
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ /**
3
+ * `.fafa` → A2A AgentCard mapper.
4
+ *
5
+ * Maps CFM's FAF Agent Card (`.fafa`) to a conformant **A2A AgentCard**
6
+ * (A2A protocol v1.0, served at `/.well-known/a2a-agent-card`).
7
+ *
8
+ * Posture: `.fafa` is a PEER to the A2A AgentCard — **compatible + enhancing**.
9
+ * Every A2A field maps from `.fafa`; the enhancement is the **FAF context block**,
10
+ * which rides as an A2A extension (`one.faf/context`) — byte-equivalent to the MCP
11
+ * server-card `_meta["one.faf/context"]`. One context, every door.
12
+ *
13
+ * Pure mapper (testable). The live A2A *invocation* endpoint (JSON-RPC `message/send`)
14
+ * is served at the edge — this module produces the discovery card that points at it.
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.A2A_WELL_KNOWN_PATH = exports.A2A_PROTOCOL_VERSION = void 0;
18
+ exports.fafaToA2ACard = fafaToA2ACard;
19
+ exports.A2A_PROTOCOL_VERSION = '1.0';
20
+ exports.A2A_WELL_KNOWN_PATH = '/.well-known/a2a-agent-card';
21
+ /** Map a parsed `.fafa` document to a conformant A2A AgentCard. */
22
+ function fafaToA2ACard(fafa, opts) {
23
+ const agent = fafa?.agent ?? {};
24
+ const card = {
25
+ protocolVersion: exports.A2A_PROTOCOL_VERSION,
26
+ name: agent.name,
27
+ description: agent.description,
28
+ url: opts.url, // A2A service endpoint (JSON-RPC) — served at the edge
29
+ version: agent.version,
30
+ provider: { organization: agent.vendor, url: agent.homepage },
31
+ // CFM's tools are synchronous request/response; no streaming/push yet.
32
+ capabilities: { streaming: false, pushNotifications: false, extendedAgentCard: false },
33
+ defaultInputModes: ['text/plain', 'application/json'],
34
+ defaultOutputModes: ['text/plain', 'application/json'],
35
+ // A2A skill ≡ FAF capability ≡ MCP tool (per AGENT-FORMAT §3).
36
+ skills: (fafa?.capabilities ?? []).map((c) => ({
37
+ id: c.name,
38
+ name: c.name,
39
+ description: c.description,
40
+ tags: Array.isArray(c.tags) ? c.tags : [],
41
+ })),
42
+ };
43
+ // The enhancement: the FAF context block as an A2A extension (the durable middle).
44
+ if (fafa?.provenance) {
45
+ card.extensions = [{
46
+ uri: 'https://one.faf/context',
47
+ description: 'FAF context provenance — the durable context block (one context, every door).',
48
+ required: false,
49
+ params: fafa.provenance,
50
+ }];
51
+ }
52
+ return card;
53
+ }
54
+ //# sourceMappingURL=a2a-card.js.map
@@ -0,0 +1,72 @@
1
+ export type Priority = 'ephemeral' | 'standard' | 'high' | 'critical';
2
+ /** Normalise a priority string: map legacy aliases, default unknowns to `standard`. */
3
+ export declare function canonicalPriority(p?: string | null): Priority;
4
+ export interface Fact {
5
+ text: string;
6
+ id?: string;
7
+ type?: string;
8
+ priority: Priority;
9
+ tags: string[];
10
+ links: string[];
11
+ timestamp?: string;
12
+ source?: string;
13
+ }
14
+ export interface EtchInput {
15
+ text: string;
16
+ id?: string;
17
+ type?: string;
18
+ priority?: string;
19
+ tags?: string[];
20
+ links?: string[];
21
+ source?: string;
22
+ }
23
+ export interface RecallOpts {
24
+ query?: string;
25
+ tags?: string[];
26
+ type?: string;
27
+ minPriority?: string;
28
+ limit?: number;
29
+ }
30
+ /** A loaded `.fafm` knowledge soul + its basic offline memory ops. */
31
+ export declare class Soul {
32
+ namepoint: string;
33
+ profile: string;
34
+ retention: string;
35
+ created: string;
36
+ last_etched: string;
37
+ private _facts;
38
+ private _byId;
39
+ constructor(namepoint: string, opts?: {
40
+ profile?: string;
41
+ facts?: Fact[];
42
+ retention?: string;
43
+ created?: string;
44
+ });
45
+ get facts(): Fact[];
46
+ static fromObj(o: any): Fact;
47
+ /** Load a `.fafm` soul from disk; namepoint defaults to the filename stem. */
48
+ static load(soulPath: string): Soul;
49
+ /** Load if the soul exists, else a fresh empty soul (offline-first, no account). */
50
+ static open(soulPath: string, namepoint?: string): Soul;
51
+ private factToObj;
52
+ toDoc(): Record<string, any>;
53
+ toYaml(): string;
54
+ save(soulPath: string): string;
55
+ /** Insert or update a Fact by id (O(1) dedup); else append. The merge primitive. */
56
+ add(fact: Fact): Fact;
57
+ /** Write a fact. If `id` matches an existing fact it's updated in place; else appended. */
58
+ etch(input: EtchInput): Fact;
59
+ /**
60
+ * Deterministic recall: case-insensitive substring on `text`, tag intersection,
61
+ * type equality, priority floor — ranked by priority, then recency, then
62
+ * insertion index (so the most-recently-etched fact always wins ties).
63
+ */
64
+ recall(opts?: RecallOpts): Fact[];
65
+ getFact(id: string): Fact | undefined;
66
+ /** The lean index — one-liners for always-loaded recall (`id — text`). */
67
+ index(): {
68
+ id: string;
69
+ text: string;
70
+ priority: Priority;
71
+ }[];
72
+ }