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 +32 -1
- package/CLAUDE.md +10 -10
- package/README.md +2 -2
- package/dist/src/faf-core/commands/bi-sync.js +3 -2
- package/dist/src/faf-core/inject.d.ts +19 -0
- package/dist/src/faf-core/inject.js +57 -0
- package/dist/src/faf-core/parsers/agents-parser.js +3 -2
- package/dist/src/faf-core/parsers/cursorrules-parser.js +6 -2
- package/dist/src/faf-core/parsers/gemini-parser.js +3 -2
- package/dist/src/fafa/a2a-card.d.ts +54 -0
- package/dist/src/fafa/a2a-card.js +54 -0
- package/dist/src/fafm/faf-memory.d.ts +72 -0
- package/dist/src/fafm/faf-memory.js +229 -0
- package/dist/src/handlers/fileHandler.js +29 -22
- package/dist/src/handlers/tools.d.ts +2 -0
- package/dist/src/handlers/tools.js +343 -130
- package/dist/src/server.js +1 -1
- package/dist/src/utils/memory-parser.js +3 -1
- package/dist/src/utils/safe-path.d.ts +66 -0
- package/dist/src/utils/safe-path.js +203 -0
- package/manifest.json +138 -35
- package/package.json +2 -2
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.
|
|
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 |
|
|
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
|
-
|
|
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:**
|
|
17
|
+
- **Build:** tsc
|
|
18
18
|
- **Cicd:** GitHub Actions
|
|
19
19
|
|
|
20
20
|
## Context
|
|
21
21
|
|
|
22
|
-
- **Who:** Claude Desktop
|
|
23
|
-
- **What:**
|
|
24
|
-
- **Why:**
|
|
25
|
-
- **Where:** npm, Claude
|
|
26
|
-
- **When:**
|
|
27
|
-
- **How:**
|
|
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-
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
+
}
|