faf-mcp 3.0.0 → 3.0.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 +37 -2
- package/CLAUDE.md +6 -2
- package/README.md +48 -28
- package/dist/src/cli.js +2 -1
- package/dist/src/cli.js.map +1 -1
- package/dist/src/faf-core/commands/agents.d.ts +2 -2
- package/dist/src/faf-core/commands/agents.js +21 -7
- package/dist/src/faf-core/commands/agents.js.map +1 -1
- package/dist/src/faf-core/commands/claude.d.ts +30 -0
- package/dist/src/faf-core/commands/{bi-sync.js → claude.js} +19 -15
- package/dist/src/faf-core/commands/claude.js.map +1 -0
- package/dist/src/faf-core/commands/conductor.d.ts +1 -1
- package/dist/src/faf-core/commands/conductor.js +36 -11
- package/dist/src/faf-core/commands/conductor.js.map +1 -1
- package/dist/src/faf-core/commands/cursor.d.ts +2 -2
- package/dist/src/faf-core/commands/cursor.js +21 -7
- package/dist/src/faf-core/commands/cursor.js.map +1 -1
- package/dist/src/faf-core/commands/gemini.d.ts +2 -2
- package/dist/src/faf-core/commands/gemini.js +21 -7
- package/dist/src/faf-core/commands/gemini.js.map +1 -1
- package/dist/src/faf-core/commands/git-context.d.ts +13 -4
- package/dist/src/faf-core/commands/git-context.js +30 -8
- package/dist/src/faf-core/commands/git-context.js.map +1 -1
- package/dist/src/faf-core/commands/sync.d.ts +8 -0
- package/dist/src/faf-core/commands/sync.js +20 -23
- package/dist/src/faf-core/commands/sync.js.map +1 -1
- package/dist/src/faf-core/fix-once/yaml.js +12 -13
- package/dist/src/faf-core/fix-once/yaml.js.map +1 -1
- package/dist/src/faf-core/parsers/agents-parser.d.ts +4 -3
- package/dist/src/faf-core/parsers/agents-parser.js +4 -3
- package/dist/src/faf-core/parsers/agents-parser.js.map +1 -1
- package/dist/src/faf-core/parsers/conductor-parser.d.ts +1 -1
- package/dist/src/faf-core/parsers/conductor-parser.js +1 -1
- package/dist/src/faf-core/parsers/cursorrules-parser.d.ts +4 -3
- package/dist/src/faf-core/parsers/cursorrules-parser.js +4 -3
- package/dist/src/faf-core/parsers/cursorrules-parser.js.map +1 -1
- package/dist/src/faf-core/parsers/faf-git-generator.d.ts +1 -2
- package/dist/src/faf-core/parsers/faf-git-generator.js +19 -39
- package/dist/src/faf-core/parsers/faf-git-generator.js.map +1 -1
- package/dist/src/faf-core/parsers/gemini-parser.d.ts +4 -3
- package/dist/src/faf-core/parsers/gemini-parser.js +4 -3
- package/dist/src/faf-core/parsers/gemini-parser.js.map +1 -1
- package/dist/src/faf-core/parsers/github-extractor.d.ts +1 -1
- package/dist/src/faf-core/parsers/github-extractor.js +1 -1
- package/dist/src/faf-core/parsers/slot-counter.d.ts +1 -1
- package/dist/src/faf-core/parsers/slot-counter.js +1 -1
- package/dist/src/handlers/engine-adapter.js +10 -7
- package/dist/src/handlers/engine-adapter.js.map +1 -1
- package/dist/src/handlers/tools.d.ts +2 -2
- package/dist/src/handlers/tools.js +191 -206
- package/dist/src/handlers/tools.js.map +1 -1
- package/dist/src/server.js +1 -1
- package/dist/src/server.js.map +1 -1
- package/dist/src/utils/faf-cli-bridge.d.ts +12 -8
- package/dist/src/utils/faf-cli-bridge.js +12 -8
- package/dist/src/utils/faf-cli-bridge.js.map +1 -1
- package/package.json +5 -5
- package/project.faf +5 -5
- package/scripts/sync-version.js +2 -1
- package/skills/faf-ide/SKILL.md +5 -5
- package/dist/src/faf-core/commands/bi-sync.d.ts +0 -29
- package/dist/src/faf-core/commands/bi-sync.js.map +0 -1
|
@@ -38,7 +38,6 @@ const fileHandler_1 = require("./fileHandler");
|
|
|
38
38
|
const fs = __importStar(require("fs"));
|
|
39
39
|
const os = __importStar(require("os"));
|
|
40
40
|
const pathModule = __importStar(require("path"));
|
|
41
|
-
const fuzzy_detector_1 = require("../utils/fuzzy-detector");
|
|
42
41
|
const faf_file_finder_js_1 = require("../utils/faf-file-finder.js");
|
|
43
42
|
const safe_path_1 = require("../utils/safe-path");
|
|
44
43
|
const version_1 = require("../version");
|
|
@@ -49,12 +48,12 @@ const path_resolver_1 = require("../utils/path-resolver");
|
|
|
49
48
|
// scoring or the tier ladder here — that's exactly the drift v2.1.0 set out
|
|
50
49
|
// to kill across the surface; v2.1.1 closes the loop on the active handler.
|
|
51
50
|
//
|
|
52
|
-
// Imported via ../utils/faf-cli-bridge.js — a
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
//
|
|
51
|
+
// Imported via ../utils/faf-cli-bridge.js — a one-file re-export that pins
|
|
52
|
+
// the relative dist path. It began as a workaround for faf-cli 6.7.1's `bun`
|
|
53
|
+
// exports condition (pointed at a non-shipped src/); 6.8.0 dropped it and
|
|
54
|
+
// 7.12.0 exports only `types`/`default`. Node and Bun load the same compiled
|
|
55
|
+
// module through the bridge, which stays for the CJS-to-ESM load and
|
|
56
|
+
// bundledFafCliVersion()'s path-walk. See the bridge header for the rationale.
|
|
58
57
|
const faf_cli_bridge_js_1 = require("../utils/faf-cli-bridge.js");
|
|
59
58
|
const turbocat_bridge_js_1 = require("../faf-core/extract/turbocat-bridge.js");
|
|
60
59
|
/**
|
|
@@ -66,8 +65,8 @@ const turbocat_bridge_js_1 = require("../faf-core/extract/turbocat-bridge.js");
|
|
|
66
65
|
* the 40%-MIN rule means one weak tool on the surface caps the whole server.
|
|
67
66
|
*
|
|
68
67
|
* Spine mirrors claude-faf-mcp's proven-A Core; the interop quad
|
|
69
|
-
* (
|
|
70
|
-
* the Cursor / IDE multi-format
|
|
68
|
+
* (faf_claude + faf_agents/faf_cursor/faf_gemini) is this edition's identity —
|
|
69
|
+
* the Cursor / IDE multi-format write — where CFM carries memory tools instead.
|
|
71
70
|
* See PLANET-FAF/strategy/claude-faf-mcp-core-tier-glama-a-2026-06-17.md.
|
|
72
71
|
*/
|
|
73
72
|
const CORE_TOOLS = new Set([
|
|
@@ -75,7 +74,7 @@ const CORE_TOOLS = new Set([
|
|
|
75
74
|
'faf_init', 'faf_auto', 'faf_go', 'faf_score', 'faf_doctor',
|
|
76
75
|
'faf_sync', 'faf_context', 'faf_trust', 'faf_about',
|
|
77
76
|
// IDE-edition interop — the differentiator
|
|
78
|
-
'
|
|
77
|
+
'faf_claude', 'faf_agents', 'faf_cursor', 'faf_gemini',
|
|
79
78
|
// Craft-grade utility
|
|
80
79
|
'faf_check', 'faf_git',
|
|
81
80
|
]);
|
|
@@ -192,7 +191,7 @@ class FafToolHandler {
|
|
|
192
191
|
},
|
|
193
192
|
{
|
|
194
193
|
name: 'faf_trust',
|
|
195
|
-
description: 'Validate a project.faf
|
|
194
|
+
description: 'Validate a project.faf\'s required fields (faf_version, project.name) and about.* block with faf-cli\'s validator, and report its real score.',
|
|
196
195
|
annotations: {
|
|
197
196
|
title: 'Trust Score',
|
|
198
197
|
readOnlyHint: true,
|
|
@@ -206,7 +205,7 @@ class FafToolHandler {
|
|
|
206
205
|
},
|
|
207
206
|
{
|
|
208
207
|
name: 'faf_sync',
|
|
209
|
-
description: 'Reconcile project.faf with what the repo manifests say now (package.json name, description, framework dependencies). Dry-run by default: reports the fields that would change. Pass apply:true to write them into project.faf. Does not touch CLAUDE.md — use
|
|
208
|
+
description: 'Reconcile project.faf with what the repo manifests say now (package.json name, description, framework dependencies). Dry-run by default: reports the fields that would change. Pass apply:true to write them into project.faf. Does not touch CLAUDE.md — use faf_claude for CLAUDE.md, AGENTS.md, .cursorrules and GEMINI.md.',
|
|
210
209
|
annotations: {
|
|
211
210
|
title: 'Reconcile .faf with repo manifests',
|
|
212
211
|
readOnlyHint: false,
|
|
@@ -223,10 +222,10 @@ class FafToolHandler {
|
|
|
223
222
|
}
|
|
224
223
|
},
|
|
225
224
|
{
|
|
226
|
-
name: '
|
|
227
|
-
description: 'Write CLAUDE.md from project.faf (one direction: .faf → CLAUDE.md) and, with the format flags or all, also AGENTS.md, .cursorrules and GEMINI.md. Content outside the faf-managed block is preserved. Returns the files written. Use this to keep every AI tool\'s context file current from one source.',
|
|
225
|
+
name: 'faf_claude',
|
|
226
|
+
description: 'Write CLAUDE.md from project.faf (one direction: .faf → CLAUDE.md) and, with the format flags or all, also AGENTS.md, .cursorrules and GEMINI.md. Content outside the faf-managed block is preserved. Returns the files written. Use this to keep every AI tool\'s context file current from one source. (Named faf_bi_sync before 3.0.1; that name still works.)',
|
|
228
227
|
annotations: {
|
|
229
|
-
title: '
|
|
228
|
+
title: 'Write CLAUDE.md and IDE formats from .faf',
|
|
230
229
|
readOnlyHint: false,
|
|
231
230
|
destructiveHint: false,
|
|
232
231
|
openWorldHint: false
|
|
@@ -234,10 +233,10 @@ class FafToolHandler {
|
|
|
234
233
|
inputSchema: {
|
|
235
234
|
type: 'object',
|
|
236
235
|
properties: {
|
|
237
|
-
agents: { type: 'boolean', description: 'Also
|
|
238
|
-
cursor: { type: 'boolean', description: 'Also
|
|
239
|
-
gemini: { type: 'boolean', description: 'Also
|
|
240
|
-
all: { type: 'boolean', description: '
|
|
236
|
+
agents: { type: 'boolean', description: 'Also write AGENTS.md (OpenAI/Codex format)' },
|
|
237
|
+
cursor: { type: 'boolean', description: 'Also write .cursorrules (Cursor IDE format)' },
|
|
238
|
+
gemini: { type: 'boolean', description: 'Also write GEMINI.md (Google Gemini format)' },
|
|
239
|
+
all: { type: 'boolean', description: 'Write ALL formats: CLAUDE.md + AGENTS.md + .cursorrules + GEMINI.md' },
|
|
241
240
|
path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
|
|
242
241
|
},
|
|
243
242
|
additionalProperties: false
|
|
@@ -245,7 +244,7 @@ class FafToolHandler {
|
|
|
245
244
|
},
|
|
246
245
|
{
|
|
247
246
|
name: 'faf_clear',
|
|
248
|
-
description: 'Remove
|
|
247
|
+
description: 'Remove ~/.faf-cli-cache/technical-credit.json, the cache file left by faf-mcp versions before 3.0. Other files in ~/.faf-cli-cache are left in place.',
|
|
249
248
|
annotations: {
|
|
250
249
|
title: 'Clear .faf Data',
|
|
251
250
|
readOnlyHint: false,
|
|
@@ -255,8 +254,8 @@ class FafToolHandler {
|
|
|
255
254
|
inputSchema: {
|
|
256
255
|
type: 'object',
|
|
257
256
|
properties: {
|
|
258
|
-
cache: { type: 'boolean', description: 'Remove ~/.faf-cli-cache (written by faf-mcp < 3.0)' },
|
|
259
|
-
all: { type: 'boolean', description: 'Remove everything older versions
|
|
257
|
+
cache: { type: 'boolean', description: 'Remove ~/.faf-cli-cache/technical-credit.json (written by faf-mcp < 3.0)' },
|
|
258
|
+
all: { type: 'boolean', description: 'Remove everything older versions left behind (default; today that is technical-credit.json)' }
|
|
260
259
|
},
|
|
261
260
|
additionalProperties: false
|
|
262
261
|
}
|
|
@@ -277,9 +276,9 @@ class FafToolHandler {
|
|
|
277
276
|
},
|
|
278
277
|
{
|
|
279
278
|
name: 'faf_read',
|
|
280
|
-
description: 'Read a file within the
|
|
279
|
+
description: 'Read a file within the allowed roots (cwd, the OS temp dir, or FAF_ALLOWED_ROOTS). Paths outside them are refused.',
|
|
281
280
|
annotations: {
|
|
282
|
-
title: 'Read
|
|
281
|
+
title: 'Read Project File',
|
|
283
282
|
readOnlyHint: true,
|
|
284
283
|
openWorldHint: false
|
|
285
284
|
},
|
|
@@ -297,11 +296,11 @@ class FafToolHandler {
|
|
|
297
296
|
},
|
|
298
297
|
{
|
|
299
298
|
name: 'faf_write',
|
|
300
|
-
description: 'Write a file within the
|
|
299
|
+
description: 'Write a file within the allowed roots (cwd, the OS temp dir, or FAF_ALLOWED_ROOTS). Paths outside them are refused.',
|
|
301
300
|
annotations: {
|
|
302
|
-
title: 'Write
|
|
301
|
+
title: 'Write Project File',
|
|
303
302
|
readOnlyHint: false,
|
|
304
|
-
destructiveHint:
|
|
303
|
+
destructiveHint: true,
|
|
305
304
|
openWorldHint: false
|
|
306
305
|
},
|
|
307
306
|
inputSchema: {
|
|
@@ -324,7 +323,7 @@ class FafToolHandler {
|
|
|
324
323
|
name: 'faf_list',
|
|
325
324
|
description: 'List directories and discover projects with project.faf files - Essential for FAF discovery workflow',
|
|
326
325
|
annotations: {
|
|
327
|
-
title: 'List
|
|
326
|
+
title: 'List Directories',
|
|
328
327
|
readOnlyHint: true,
|
|
329
328
|
openWorldHint: false
|
|
330
329
|
},
|
|
@@ -415,7 +414,7 @@ class FafToolHandler {
|
|
|
415
414
|
},
|
|
416
415
|
{
|
|
417
416
|
name: 'faf_check',
|
|
418
|
-
description: 'Inspect the human_context fields and rate each empty/generic/good
|
|
417
|
+
description: 'Inspect the human_context fields and rate each empty/generic/good. Returns the ratings; with protect it records the good fields in _protected_fields as an advisory list (other tools do not enforce it), with unlock it clears that list. Use this to gauge context quality.',
|
|
419
418
|
annotations: {
|
|
420
419
|
title: 'Check .faf Health',
|
|
421
420
|
readOnlyHint: false,
|
|
@@ -425,8 +424,8 @@ class FafToolHandler {
|
|
|
425
424
|
inputSchema: {
|
|
426
425
|
type: 'object',
|
|
427
426
|
properties: {
|
|
428
|
-
protect: { type: 'boolean', description: '
|
|
429
|
-
unlock: { type: 'boolean', description: '
|
|
427
|
+
protect: { type: 'boolean', description: 'Record good fields in _protected_fields (advisory; other tools do not enforce it)' },
|
|
428
|
+
unlock: { type: 'boolean', description: 'Clear the _protected_fields list' },
|
|
430
429
|
path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
|
|
431
430
|
},
|
|
432
431
|
additionalProperties: false
|
|
@@ -434,7 +433,7 @@ class FafToolHandler {
|
|
|
434
433
|
},
|
|
435
434
|
{
|
|
436
435
|
name: 'faf_context',
|
|
437
|
-
description: 'Set or show the active project path that later faf_
|
|
436
|
+
description: 'Set or show the active project path that later faf_ project tools resolve against (faf_read and faf_write resolve relative paths against the server\'s working directory instead). Returns the current context path. Call this once at the start of a session so the project tools target the right project.',
|
|
438
437
|
annotations: {
|
|
439
438
|
title: 'View Context',
|
|
440
439
|
readOnlyHint: true,
|
|
@@ -472,7 +471,7 @@ class FafToolHandler {
|
|
|
472
471
|
},
|
|
473
472
|
{
|
|
474
473
|
name: 'faf_auto',
|
|
475
|
-
description: 'Run the
|
|
474
|
+
description: 'Run the setup pipeline in one call — init or merge, stack detection, CLAUDE.md, and score — taking a project from no context to a scored project.faf plus CLAUDE.md; faf_go closes the human slots. Returns the final AI-readiness score and what was created. Use this as the fast path on a fresh project; use the individual tools when you need finer control.',
|
|
476
475
|
annotations: {
|
|
477
476
|
title: 'Auto-detect Context',
|
|
478
477
|
readOnlyHint: false,
|
|
@@ -492,10 +491,11 @@ class FafToolHandler {
|
|
|
492
491
|
},
|
|
493
492
|
{
|
|
494
493
|
name: 'faf_dna',
|
|
495
|
-
description: 'Show your FAF DNA journey
|
|
494
|
+
description: 'Show and record your FAF DNA journey — the score evolution of this project.faf from birth to today (e.g. 22% → 85% → 100%). Creates .faf-dna on the first call and appends a milestone when the score changes.',
|
|
496
495
|
annotations: {
|
|
497
496
|
title: 'View Project DNA',
|
|
498
|
-
readOnlyHint:
|
|
497
|
+
readOnlyHint: false,
|
|
498
|
+
destructiveHint: false,
|
|
499
499
|
openWorldHint: false
|
|
500
500
|
},
|
|
501
501
|
inputSchema: {
|
|
@@ -508,7 +508,7 @@ class FafToolHandler {
|
|
|
508
508
|
},
|
|
509
509
|
{
|
|
510
510
|
name: 'faf_formats',
|
|
511
|
-
description: 'Scan the project for tech-stack signals across 150+ manifest types (package.json, Cargo.toml, pyproject.toml, go.mod, and more)
|
|
511
|
+
description: 'Scan the project for tech-stack signals across 150+ manifest types (package.json, Cargo.toml, pyproject.toml, go.mod, and more). Returns the detected formats, the stack signature and faf-cli\'s slot-fill hints. Use this to see what stack detection finds.',
|
|
512
512
|
annotations: {
|
|
513
513
|
title: 'List Formats',
|
|
514
514
|
readOnlyHint: true,
|
|
@@ -545,7 +545,7 @@ class FafToolHandler {
|
|
|
545
545
|
},
|
|
546
546
|
{
|
|
547
547
|
name: 'faf_doctor',
|
|
548
|
-
description: 'Diagnose a project.faf setup — report missing files,
|
|
548
|
+
description: 'Diagnose a project.faf setup — report missing files, the real score with populated/active slot counts, and common configuration issues, each with a fix. Returns a checklist. Use this when your score is low and you need to know why.',
|
|
549
549
|
annotations: {
|
|
550
550
|
title: 'Diagnose Issues',
|
|
551
551
|
readOnlyHint: true,
|
|
@@ -560,11 +560,11 @@ class FafToolHandler {
|
|
|
560
560
|
}
|
|
561
561
|
},
|
|
562
562
|
// ============================================================================
|
|
563
|
-
//
|
|
563
|
+
// INTEROP TOOLS — landed in faf-mcp 2.0.0 (The Interop MCP for Context); ported from faf-cli 4.5.0 via claude-faf-mcp 4.5.0
|
|
564
564
|
// ============================================================================
|
|
565
565
|
{
|
|
566
566
|
name: 'faf_agents',
|
|
567
|
-
description: 'Import
|
|
567
|
+
description: 'Import AGENTS.md (the OpenAI/Codex convention) into project.faf, or write it from project.faf. Returns the merged or written result for the chosen action. Use this to keep a Codex/OpenAI-style AGENTS.md and your .faf in agreement from one source.',
|
|
568
568
|
annotations: {
|
|
569
569
|
title: 'Sync AGENTS.md',
|
|
570
570
|
readOnlyHint: false,
|
|
@@ -574,9 +574,9 @@ class FafToolHandler {
|
|
|
574
574
|
inputSchema: {
|
|
575
575
|
type: 'object',
|
|
576
576
|
properties: {
|
|
577
|
-
action: { type: 'string', enum: ['import', 'export', 'sync'], description: 'Action: import (AGENTS.md -> .faf), export (.faf -> AGENTS.md), sync (
|
|
578
|
-
force: { type: 'boolean', description: '
|
|
579
|
-
merge: { type: 'boolean', description: '
|
|
577
|
+
action: { type: 'string', enum: ['import', 'export', 'sync'], description: 'Action: import (AGENTS.md -> .faf; merge:true writes it into project.faf), export (.faf -> AGENTS.md), sync (re-writes AGENTS.md from .faf; export with force)' },
|
|
578
|
+
force: { type: 'boolean', description: 'Update the faf-managed block in an existing file (content outside it is preserved)' },
|
|
579
|
+
merge: { type: 'boolean', description: 'Write the imported data into project.faf (without it, import only reports the section count and writes nothing)' },
|
|
580
580
|
path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
|
|
581
581
|
},
|
|
582
582
|
required: ['action'],
|
|
@@ -585,7 +585,7 @@ class FafToolHandler {
|
|
|
585
585
|
},
|
|
586
586
|
{
|
|
587
587
|
name: 'faf_cursor',
|
|
588
|
-
description: 'Import
|
|
588
|
+
description: 'Import .cursorrules (the Cursor IDE convention) into project.faf, or write it from project.faf. Returns the merged or written result for the chosen action. Use this to keep Cursor rules and your .faf in agreement from one source.',
|
|
589
589
|
annotations: {
|
|
590
590
|
title: 'Sync .cursorrules',
|
|
591
591
|
readOnlyHint: false,
|
|
@@ -595,9 +595,9 @@ class FafToolHandler {
|
|
|
595
595
|
inputSchema: {
|
|
596
596
|
type: 'object',
|
|
597
597
|
properties: {
|
|
598
|
-
action: { type: 'string', enum: ['import', 'export', 'sync'], description: 'Action: import (.cursorrules -> .faf), export (.faf -> .cursorrules), sync (
|
|
599
|
-
force: { type: 'boolean', description: '
|
|
600
|
-
merge: { type: 'boolean', description: '
|
|
598
|
+
action: { type: 'string', enum: ['import', 'export', 'sync'], description: 'Action: import (.cursorrules -> .faf; merge:true writes it into project.faf), export (.faf -> .cursorrules), sync (re-writes .cursorrules from .faf; export with force)' },
|
|
599
|
+
force: { type: 'boolean', description: 'Update the faf-managed block in an existing file (content outside it is preserved)' },
|
|
600
|
+
merge: { type: 'boolean', description: 'Write the imported data into project.faf (without it, import only reports the section count and writes nothing)' },
|
|
601
601
|
path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
|
|
602
602
|
},
|
|
603
603
|
required: ['action'],
|
|
@@ -606,7 +606,7 @@ class FafToolHandler {
|
|
|
606
606
|
},
|
|
607
607
|
{
|
|
608
608
|
name: 'faf_gemini',
|
|
609
|
-
description: 'Import
|
|
609
|
+
description: 'Import GEMINI.md (the Google Gemini CLI convention) into project.faf, or write it from project.faf. Returns the merged or written result for the chosen action. Use this to keep a Gemini context file and your .faf in agreement from one source.',
|
|
610
610
|
annotations: {
|
|
611
611
|
title: 'Sync GEMINI.md',
|
|
612
612
|
readOnlyHint: false,
|
|
@@ -616,9 +616,9 @@ class FafToolHandler {
|
|
|
616
616
|
inputSchema: {
|
|
617
617
|
type: 'object',
|
|
618
618
|
properties: {
|
|
619
|
-
action: { type: 'string', enum: ['import', 'export', 'sync'], description: 'Action: import (GEMINI.md -> .faf), export (.faf -> GEMINI.md), sync (
|
|
620
|
-
force: { type: 'boolean', description: '
|
|
621
|
-
merge: { type: 'boolean', description: '
|
|
619
|
+
action: { type: 'string', enum: ['import', 'export', 'sync'], description: 'Action: import (GEMINI.md -> .faf; merge:true writes it into project.faf), export (.faf -> GEMINI.md), sync (re-writes GEMINI.md from .faf; export with force)' },
|
|
620
|
+
force: { type: 'boolean', description: 'Update the faf-managed block in an existing file (content outside it is preserved)' },
|
|
621
|
+
merge: { type: 'boolean', description: 'Write the imported data into project.faf (without it, import only reports the section count and writes nothing)' },
|
|
622
622
|
path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
|
|
623
623
|
},
|
|
624
624
|
required: ['action'],
|
|
@@ -631,7 +631,7 @@ class FafToolHandler {
|
|
|
631
631
|
annotations: {
|
|
632
632
|
title: 'Sync Conductor',
|
|
633
633
|
readOnlyHint: false,
|
|
634
|
-
destructiveHint:
|
|
634
|
+
destructiveHint: true,
|
|
635
635
|
openWorldHint: false
|
|
636
636
|
},
|
|
637
637
|
inputSchema: {
|
|
@@ -639,7 +639,7 @@ class FafToolHandler {
|
|
|
639
639
|
properties: {
|
|
640
640
|
action: { type: 'string', enum: ['import', 'export'], description: 'Action: import (conductor/ -> .faf), export (.faf -> conductor/)' },
|
|
641
641
|
force: { type: 'boolean', description: 'Force overwrite existing files' },
|
|
642
|
-
merge: { type: 'boolean', description: '
|
|
642
|
+
merge: { type: 'boolean', description: 'Write the imported data into project.faf (without it, import only reports the section count and writes nothing)' },
|
|
643
643
|
path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
|
|
644
644
|
},
|
|
645
645
|
required: ['action'],
|
|
@@ -648,18 +648,19 @@ class FafToolHandler {
|
|
|
648
648
|
},
|
|
649
649
|
{
|
|
650
650
|
name: 'faf_git',
|
|
651
|
-
description: '
|
|
651
|
+
description: 'Author a project.faf from any public GitHub repository URL — fetches the repo and extracts its stack and context. Returns the authored .faf. Use this to bootstrap context for a project you have the URL for but not cloned locally.',
|
|
652
652
|
annotations: {
|
|
653
653
|
title: 'Extract from GitHub',
|
|
654
654
|
readOnlyHint: false,
|
|
655
|
-
destructiveHint:
|
|
655
|
+
destructiveHint: true,
|
|
656
656
|
openWorldHint: true
|
|
657
657
|
},
|
|
658
658
|
inputSchema: {
|
|
659
659
|
type: 'object',
|
|
660
660
|
properties: {
|
|
661
661
|
url: { type: 'string', description: 'GitHub repository URL (e.g., https://github.com/owner/repo or owner/repo)' },
|
|
662
|
-
path: { type: 'string', description: 'Output directory for
|
|
662
|
+
path: { type: 'string', description: 'Output directory for the authored project.faf. If omitted, returns content without writing.' },
|
|
663
|
+
force: { type: 'boolean', description: 'Overwrite an existing project.faf at path (without it, faf_git refuses)' }
|
|
663
664
|
},
|
|
664
665
|
required: ['url'],
|
|
665
666
|
additionalProperties: false
|
|
@@ -689,8 +690,9 @@ class FafToolHandler {
|
|
|
689
690
|
return await this.handleFafTrust(args);
|
|
690
691
|
case 'faf_sync':
|
|
691
692
|
return await this.handleFafSync(args);
|
|
692
|
-
case 'faf_bi_sync':
|
|
693
|
-
|
|
693
|
+
case 'faf_bi_sync': // pre-3.0.1 name: still callable, never listed
|
|
694
|
+
case 'faf_claude':
|
|
695
|
+
return await this.handleFafClaude(args);
|
|
694
696
|
case 'faf_clear':
|
|
695
697
|
return await this.handleFafClear(args);
|
|
696
698
|
case 'faf_debug':
|
|
@@ -736,7 +738,7 @@ class FafToolHandler {
|
|
|
736
738
|
return await this.handleFafQuick(args);
|
|
737
739
|
case 'faf_doctor':
|
|
738
740
|
return await this.handleFafDoctor(args);
|
|
739
|
-
//
|
|
741
|
+
// Interop tools (faf-mcp 2.0.0; ported from faf-cli 4.5.0 via claude-faf-mcp 4.5.0)
|
|
740
742
|
case 'faf_agents':
|
|
741
743
|
return await this.handleFafAgents(args);
|
|
742
744
|
case 'faf_cursor':
|
|
@@ -769,7 +771,7 @@ class FafToolHandler {
|
|
|
769
771
|
return {
|
|
770
772
|
content: [{
|
|
771
773
|
type: 'text',
|
|
772
|
-
text: `🤖
|
|
774
|
+
text: `🤖 FAF Project Status:\n\n❌ No FAF file found in ${cwd}\n💡 Run faf_init to create project.faf`
|
|
773
775
|
}]
|
|
774
776
|
};
|
|
775
777
|
}
|
|
@@ -778,7 +780,7 @@ class FafToolHandler {
|
|
|
778
780
|
return {
|
|
779
781
|
content: [{
|
|
780
782
|
type: 'text',
|
|
781
|
-
text: `🤖
|
|
783
|
+
text: `🤖 FAF Project Status:\n\n✅ ${fafResult.filename} found in ${cwd}\n\nContent preview:\n${lines.join('\n')}`
|
|
782
784
|
}]
|
|
783
785
|
};
|
|
784
786
|
}
|
|
@@ -786,7 +788,7 @@ class FafToolHandler {
|
|
|
786
788
|
return {
|
|
787
789
|
content: [{
|
|
788
790
|
type: 'text',
|
|
789
|
-
text: `🤖
|
|
791
|
+
text: `🤖 FAF Project Status:\n\n❌ Error: ${error.message}`
|
|
790
792
|
}],
|
|
791
793
|
isError: true
|
|
792
794
|
};
|
|
@@ -817,7 +819,7 @@ class FafToolHandler {
|
|
|
817
819
|
}
|
|
818
820
|
// Strip ANSI from tier indicator (faf-cli emits colored glyphs).
|
|
819
821
|
// eslint-disable-next-line no-control-regex
|
|
820
|
-
const strip = (s) => s.replace(/\[[0-9;]*m/g, '').trim();
|
|
822
|
+
const strip = (s) => s.replace(/\x1b\[[0-9;]*m/g, '').trim();
|
|
821
823
|
let raw;
|
|
822
824
|
try {
|
|
823
825
|
raw = readFafRaw(fafPath);
|
|
@@ -849,7 +851,7 @@ class FafToolHandler {
|
|
|
849
851
|
text: `FAF SCORE: 0/100 (0%) ○ INVALID\n\n` +
|
|
850
852
|
`\`${fafPath}\` couldn't be parsed as a valid .faf YAML:\n` +
|
|
851
853
|
` ${error?.message ?? String(error)}\n\n` +
|
|
852
|
-
`Re-run \`faf_init\` to
|
|
854
|
+
`Re-run \`faf_init\` with force: true to write a fresh, valid file.`,
|
|
853
855
|
},
|
|
854
856
|
],
|
|
855
857
|
isError: true,
|
|
@@ -885,7 +887,7 @@ class FafToolHandler {
|
|
|
885
887
|
output += `Empty (${emptySlots.length}): ${emptySlots.join(', ') || '(none)'}\n`;
|
|
886
888
|
output += `Ignored (${ignoredSlots.length}): ${ignoredSlots.join(', ') || '(none)'}`;
|
|
887
889
|
if (score < 100 && emptySlots.length > 0) {
|
|
888
|
-
output += `\n\nTip: fill empty slots or mark them \`slotignored\` to climb tiers
|
|
890
|
+
output += `\n\nTip: fill empty slots or mark them \`slotignored\` to climb tiers.`;
|
|
889
891
|
}
|
|
890
892
|
}
|
|
891
893
|
return {
|
|
@@ -898,13 +900,19 @@ class FafToolHandler {
|
|
|
898
900
|
};
|
|
899
901
|
}
|
|
900
902
|
async handleFafInit(args) {
|
|
901
|
-
//
|
|
903
|
+
// faf_init is `faf init`: the same exported faf-cli steps the CLI command
|
|
904
|
+
// runs (assembleFreshFaf detects the folder → writeFaf → readFafRaw →
|
|
905
|
+
// scoreFafYaml), on the folder the path resolves to. Until 3.0.1 this
|
|
906
|
+
// wrote its own legacy template (no format version, `project:` as a
|
|
907
|
+
// plain string) that scored 0% whatever the folder held, failed faf_trust and
|
|
908
|
+
// crashed faf_go. Two steps of `faf init` are not exported by faf-cli
|
|
909
|
+
// 7.12 and are not repeated here: the home/root refusal and the
|
|
910
|
+
// .faf-dna birth record (faf_dna writes that on its first call).
|
|
902
911
|
try {
|
|
903
|
-
//
|
|
912
|
+
// Path resolution unchanged (supports "my-app", "~/Projects/my-app", "/full/path")
|
|
904
913
|
const userInput = args?.path;
|
|
905
914
|
const resolution = (0, path_resolver_1.resolveProjectPath)(userInput);
|
|
906
915
|
const targetDir = resolution.projectPath;
|
|
907
|
-
const projectName = resolution.projectName;
|
|
908
916
|
const fafPath = resolution.fafFilePath;
|
|
909
917
|
// Ensure project directory exists
|
|
910
918
|
if (!fs.existsSync(targetDir)) {
|
|
@@ -916,57 +924,16 @@ class FafToolHandler {
|
|
|
916
924
|
return {
|
|
917
925
|
content: [{
|
|
918
926
|
type: 'text',
|
|
919
|
-
text: `🚀
|
|
927
|
+
text: `🚀 FAF Initialization:\n\n⚠️ ${existingFaf.filename} already exists in ${targetDir}\n💡 Use force: true to overwrite`
|
|
920
928
|
}]
|
|
921
929
|
};
|
|
922
930
|
}
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
let projectData = {
|
|
930
|
-
project: projectName,
|
|
931
|
-
project_type: projectType,
|
|
932
|
-
description: projectDescription,
|
|
933
|
-
generated: new Date().toISOString(),
|
|
934
|
-
version: version_1.VERSION
|
|
935
|
-
};
|
|
936
|
-
// Apply Intel-Friday: Auto-fill Chrome Extension slots for 90%+ score!
|
|
937
|
-
if (chromeDetection.detected) {
|
|
938
|
-
projectData = (0, fuzzy_detector_1.applyIntelFriday)(projectData);
|
|
939
|
-
}
|
|
940
|
-
// Create enhanced .faf content
|
|
941
|
-
const fafContent = `# FAF - Foundational AI Context
|
|
942
|
-
project: ${projectData.project}
|
|
943
|
-
type: ${projectData.project_type}${chromeDetection.detected ? ' 🎯' : ''}
|
|
944
|
-
context: I⚡🍊
|
|
945
|
-
generated: ${projectData.generated}
|
|
946
|
-
version: ${projectData.version}
|
|
947
|
-
${chromeDetection.corrected ? `# Auto-corrected: "${args?.description}" → "${chromeDetection.corrected}"` : ''}
|
|
948
|
-
|
|
949
|
-
# The Formula
|
|
950
|
-
human_input: Your project files
|
|
951
|
-
multiplier: FAF Context
|
|
952
|
-
output: Championship Performance
|
|
953
|
-
|
|
954
|
-
# Quick Context
|
|
955
|
-
working_directory: ${targetDir}
|
|
956
|
-
initialized_by: faf-mcp${projectData._friday_feature ? `\nfriday_feature: ${projectData._friday_feature}` : ''}
|
|
957
|
-
vitamin_context: true
|
|
958
|
-
faffless: true
|
|
959
|
-
|
|
960
|
-
${chromeDetection.detected ? `# Chrome Extension Auto-Fill (90%+ Score!)
|
|
961
|
-
runtime: ${projectData.runtime}
|
|
962
|
-
hosting: ${projectData.hosting}
|
|
963
|
-
api_type: ${projectData.api_type}
|
|
964
|
-
backend: ${projectData.backend}
|
|
965
|
-
database: ${projectData.database}
|
|
966
|
-
build: ${projectData.build}
|
|
967
|
-
package_manager: ${projectData.package_manager}` : ''}
|
|
968
|
-
`;
|
|
969
|
-
fs.writeFileSync(fafPath, fafContent);
|
|
931
|
+
const { assembleFreshFaf, writeFaf, readFafRaw, scoreFafYaml } = await faf_cli_bridge_js_1.fafCli;
|
|
932
|
+
writeFaf(fafPath, assembleFreshFaf(targetDir));
|
|
933
|
+
const score = scoreFafYaml(readFafRaw(fafPath));
|
|
934
|
+
// Like `cd` + `faf init`: the new project becomes the current one, so the
|
|
935
|
+
// next steps printed below (faf_score, faf_claude, faf_go) act on it.
|
|
936
|
+
this.engineAdapter.setWorkingDirectory(targetDir);
|
|
970
937
|
// Pomelli-style success confirmation with path resolution info
|
|
971
938
|
const pathConfirmation = (0, path_resolver_1.formatPathConfirmation)(resolution);
|
|
972
939
|
const sourceExplanation = resolution.source === 'user-name'
|
|
@@ -975,7 +942,7 @@ package_manager: ${projectData.package_manager}` : ''}
|
|
|
975
942
|
return {
|
|
976
943
|
content: [{
|
|
977
944
|
type: 'text',
|
|
978
|
-
text: `🚀
|
|
945
|
+
text: `🚀 FAF Initialization:\n\n✅ Created project.faf\n📊 ${score.score}/100 (${score.populated}/${score.active} slots populated) — ${score.tier.name}\n\n${pathConfirmation}${sourceExplanation}\n\n🍊 Vitamin Context activated!\n⚡ FAFFLESS AI ready!\n\n🏁 Next steps:\n • Run faf_score for AI-readiness score\n • Run faf_claude to write CLAUDE.md\n • Run faf_go to fill the gaps`
|
|
979
946
|
}]
|
|
980
947
|
};
|
|
981
948
|
}
|
|
@@ -983,7 +950,7 @@ package_manager: ${projectData.package_manager}` : ''}
|
|
|
983
950
|
return {
|
|
984
951
|
content: [{
|
|
985
952
|
type: 'text',
|
|
986
|
-
text: `🚀
|
|
953
|
+
text: `🚀 FAF Initialization:\n\n❌ Error: ${error.message}`
|
|
987
954
|
}],
|
|
988
955
|
isError: true
|
|
989
956
|
};
|
|
@@ -1011,7 +978,7 @@ package_manager: ${projectData.package_manager}` : ''}
|
|
|
1011
978
|
return {
|
|
1012
979
|
content: [{
|
|
1013
980
|
type: 'text',
|
|
1014
|
-
text: `🔒
|
|
981
|
+
text: `🔒 FAF Trust Validation:\n\n❌ No .faf found in ${cwd}\n💡 Run faf_init to create one — then faf_trust reports real trust metrics.`
|
|
1015
982
|
}]
|
|
1016
983
|
};
|
|
1017
984
|
}
|
|
@@ -1025,14 +992,14 @@ package_manager: ${projectData.package_manager}` : ''}
|
|
|
1025
992
|
return {
|
|
1026
993
|
content: [{
|
|
1027
994
|
type: 'text',
|
|
1028
|
-
text: `🔒
|
|
995
|
+
text: `🔒 FAF Trust Validation:\n\n❌ Could not read ${fafPath}: ${error?.message ?? String(error)}`
|
|
1029
996
|
}],
|
|
1030
997
|
isError: true
|
|
1031
998
|
};
|
|
1032
999
|
}
|
|
1033
1000
|
const validation = validateFaf(data);
|
|
1034
1001
|
let output = validation.valid
|
|
1035
|
-
? `✅
|
|
1002
|
+
? `✅ Required fields present (faf_version, project.name) — no validation errors.`
|
|
1036
1003
|
: `❌ ${validation.errors.length} issue${validation.errors.length === 1 ? '' : 's'} found:\n` +
|
|
1037
1004
|
validation.errors.map((e) => ` • ${e}`).join('\n');
|
|
1038
1005
|
try {
|
|
@@ -1045,7 +1012,7 @@ package_manager: ${projectData.package_manager}` : ''}
|
|
|
1045
1012
|
return {
|
|
1046
1013
|
content: [{
|
|
1047
1014
|
type: 'text',
|
|
1048
|
-
text: `🔒
|
|
1015
|
+
text: `🔒 FAF Trust Validation:\n\n${output}`
|
|
1049
1016
|
}],
|
|
1050
1017
|
isError: !validation.valid
|
|
1051
1018
|
};
|
|
@@ -1062,61 +1029,72 @@ package_manager: ${projectData.package_manager}` : ''}
|
|
|
1062
1029
|
return {
|
|
1063
1030
|
content: [{
|
|
1064
1031
|
type: 'text',
|
|
1065
|
-
text: `🔄
|
|
1032
|
+
text: `🔄 FAF Sync:\n\nFailed to sync: ${result.error}`
|
|
1066
1033
|
}],
|
|
1067
1034
|
isError: true
|
|
1068
1035
|
};
|
|
1069
1036
|
}
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1037
|
+
// A dry run carries the fields it would change: list them by name, so the
|
|
1038
|
+
// agent sees what `apply: true` would write instead of a bare count.
|
|
1039
|
+
const changes = result.data?.changes;
|
|
1040
|
+
const shown = (v) => v === undefined || v === null || v === '' ? '(empty)' : typeof v === 'string' ? v : JSON.stringify(v);
|
|
1041
|
+
const output = Array.isArray(changes) && changes.length > 0
|
|
1042
|
+
? `${result.data.message}\n` +
|
|
1043
|
+
changes.map((c) => `• ${c.path}: ${shown(c.oldValue)} → ${shown(c.newValue)}`).join('\n')
|
|
1044
|
+
: typeof result.data === 'string'
|
|
1045
|
+
? result.data
|
|
1046
|
+
: result.data?.output || JSON.stringify(result.data, null, 2);
|
|
1073
1047
|
return {
|
|
1074
1048
|
content: [{
|
|
1075
1049
|
type: 'text',
|
|
1076
|
-
text: `🔄
|
|
1050
|
+
text: `🔄 FAF Sync:\n\n${output}`
|
|
1077
1051
|
}]
|
|
1078
1052
|
};
|
|
1079
1053
|
}
|
|
1080
|
-
async
|
|
1054
|
+
async handleFafClaude(args) {
|
|
1081
1055
|
// Set project context if path provided
|
|
1082
1056
|
if (args?.path) {
|
|
1083
1057
|
this.getProjectPath(args.path);
|
|
1084
1058
|
}
|
|
1085
1059
|
// auto / watch / force used to be declared here too; none of them was
|
|
1086
|
-
// ever read by
|
|
1087
|
-
const
|
|
1060
|
+
// ever read by the command, so the schema no longer promises them.
|
|
1061
|
+
const claudeArgs = [];
|
|
1088
1062
|
// v3.0 fix: the tool's own schema advertises agents/cursor/gemini/all,
|
|
1089
|
-
// but they were never forwarded to engine
|
|
1090
|
-
//
|
|
1063
|
+
// but before 3.0 they were never forwarded to the engine adapter —
|
|
1064
|
+
// the tool silently ignored them and only ever wrote CLAUDE.md.
|
|
1091
1065
|
if (args?.agents) {
|
|
1092
|
-
|
|
1066
|
+
claudeArgs.push('--agents');
|
|
1093
1067
|
}
|
|
1094
1068
|
if (args?.cursor) {
|
|
1095
|
-
|
|
1069
|
+
claudeArgs.push('--cursor');
|
|
1096
1070
|
}
|
|
1097
1071
|
if (args?.gemini) {
|
|
1098
|
-
|
|
1072
|
+
claudeArgs.push('--gemini');
|
|
1099
1073
|
}
|
|
1100
1074
|
if (args?.all) {
|
|
1101
|
-
|
|
1075
|
+
claudeArgs.push('--all');
|
|
1102
1076
|
}
|
|
1103
|
-
const result = await this.engineAdapter.callEngine('
|
|
1077
|
+
const result = await this.engineAdapter.callEngine('claude', claudeArgs);
|
|
1104
1078
|
if (!result.success) {
|
|
1105
1079
|
return {
|
|
1106
1080
|
content: [{
|
|
1107
1081
|
type: 'text',
|
|
1108
|
-
text: `🔗
|
|
1082
|
+
text: `🔗 FAF → CLAUDE.md:\n\nCould not write CLAUDE.md: ${result.error}`
|
|
1109
1083
|
}],
|
|
1110
1084
|
isError: true
|
|
1111
1085
|
};
|
|
1112
1086
|
}
|
|
1087
|
+
// The command's message (it carries "FAF Score: N%") plus the files it
|
|
1088
|
+
// wrote, one per line — not the raw result object.
|
|
1089
|
+
const filesChanged = Array.isArray(result.data?.filesChanged) ? result.data.filesChanged : [];
|
|
1113
1090
|
const output = typeof result.data === 'string'
|
|
1114
1091
|
? result.data
|
|
1115
|
-
: result.data?.
|
|
1092
|
+
: `${result.data?.message ?? ''}` +
|
|
1093
|
+
(filesChanged.length > 0 ? `\n\nFiles written:\n${filesChanged.map((f) => `• ${f}`).join('\n')}` : '');
|
|
1116
1094
|
return {
|
|
1117
1095
|
content: [{
|
|
1118
1096
|
type: 'text',
|
|
1119
|
-
text: `🔗
|
|
1097
|
+
text: `🔗 FAF → CLAUDE.md:\n\n${output}`
|
|
1120
1098
|
}]
|
|
1121
1099
|
};
|
|
1122
1100
|
}
|
|
@@ -1159,7 +1137,7 @@ package_manager: ${projectData.package_manager}` : ''}
|
|
|
1159
1137
|
return {
|
|
1160
1138
|
content: [{
|
|
1161
1139
|
type: 'text',
|
|
1162
|
-
text: `🧹
|
|
1140
|
+
text: `🧹 FAF Clear:\n\n${lines.join('\n')}`
|
|
1163
1141
|
}]
|
|
1164
1142
|
};
|
|
1165
1143
|
}
|
|
@@ -1188,21 +1166,21 @@ WHAT IS .FAF?
|
|
|
1188
1166
|
• The dot (.) means it's a file format!
|
|
1189
1167
|
|
|
1190
1168
|
🧡 Trust: Context verified
|
|
1191
|
-
⚡️ Speed:
|
|
1169
|
+
⚡️ Speed: Authored in <29ms
|
|
1192
1170
|
SPEEDY AI you can TRUST!
|
|
1193
1171
|
|
|
1194
1172
|
Version ${packageInfo.version}
|
|
1195
1173
|
|
|
1196
|
-
Just like JPEG makes images
|
|
1174
|
+
Just like JPEG makes images portable,
|
|
1197
1175
|
.faf makes projects AI-readable.
|
|
1198
1176
|
|
|
1199
1177
|
HOW IT WORKS:
|
|
1200
1178
|
1. Drop a file or paste the path
|
|
1201
1179
|
2. Create .faf (Foundational AI-context Format)
|
|
1202
|
-
3.
|
|
1180
|
+
3. Ask your AI to run faf_claude
|
|
1203
1181
|
4. You're done⚡
|
|
1204
1182
|
|
|
1205
|
-
🩵 You just made
|
|
1183
|
+
🩵 You just made your AI happy
|
|
1206
1184
|
🧡⚡️ SPEEDY AI you can TRUST!`;
|
|
1207
1185
|
return {
|
|
1208
1186
|
content: [{
|
|
@@ -1220,8 +1198,8 @@ WHAT: .faf = Foundational AI-context Format
|
|
|
1220
1198
|
WHY: Just like JPEG makes images viewable everywhere,
|
|
1221
1199
|
.faf makes projects understandable by AI.
|
|
1222
1200
|
|
|
1223
|
-
HOW: Run
|
|
1224
|
-
Run 'faf_score' to check AI-readiness (target:
|
|
1201
|
+
HOW: Run faf_init or faf_auto on any project to create one.
|
|
1202
|
+
Run 'faf_score' to check AI-readiness (target: 100%).
|
|
1225
1203
|
|
|
1226
1204
|
REMEMBER: Always use ".faf" with the dot - it's a FORMAT!
|
|
1227
1205
|
|
|
@@ -1236,7 +1214,6 @@ REMEMBER: Always use ".faf" with the dot - it's a FORMAT!
|
|
|
1236
1214
|
async handleFafDebug(_args) {
|
|
1237
1215
|
try {
|
|
1238
1216
|
const fs = await import('fs');
|
|
1239
|
-
const path = await import('path');
|
|
1240
1217
|
const cwd = this.engineAdapter.getWorkingDirectory();
|
|
1241
1218
|
const debugInfo = {
|
|
1242
1219
|
workingDirectory: cwd,
|
|
@@ -1247,11 +1224,10 @@ REMEMBER: Always use ".faf" with the dot - it's a FORMAT!
|
|
|
1247
1224
|
permissions: {},
|
|
1248
1225
|
enginePath: this.engineAdapter.getEnginePath(),
|
|
1249
1226
|
};
|
|
1250
|
-
// Check write permissions
|
|
1227
|
+
// Check write permissions — an access check, never a probe file in the
|
|
1228
|
+
// user's workspace.
|
|
1251
1229
|
try {
|
|
1252
|
-
|
|
1253
|
-
fs.writeFileSync(testFile, 'test');
|
|
1254
|
-
fs.unlinkSync(testFile);
|
|
1230
|
+
fs.accessSync(cwd, fs.constants.W_OK);
|
|
1255
1231
|
debugInfo.canWrite = true;
|
|
1256
1232
|
}
|
|
1257
1233
|
catch (error) {
|
|
@@ -1263,7 +1239,7 @@ REMEMBER: Always use ".faf" with the dot - it's a FORMAT!
|
|
|
1263
1239
|
const debugOutput = `🔍 faf-mcp Debug Information:
|
|
1264
1240
|
|
|
1265
1241
|
📂 Working Directory: ${debugInfo.workingDirectory}
|
|
1266
|
-
✏️ Write Permissions: ${debugInfo.canWrite ? '✅
|
|
1242
|
+
✏️ Write Permissions: ${debugInfo.canWrite ? '✅ writable' : '❌ not writable'}
|
|
1267
1243
|
${debugInfo.permissions.writeError ? ` Error: ${debugInfo.permissions.writeError}\n` : ''}🤖 FAF Engine Path: ${debugInfo.enginePath}
|
|
1268
1244
|
📋 faf-cli (bundled): ${debugInfo.fafVersion ? `v${debugInfo.fafVersion}` : '❌ not found in node_modules — reinstall faf-mcp'}
|
|
1269
1245
|
📄 FAF File: ${hasFaf ? `✅ ${fafResult.filename} exists` : '❌ Not found (run faf_init)'}
|
|
@@ -1283,7 +1259,7 @@ ${debugInfo.permissions.writeError ? ` Error: ${debugInfo.permissions.writeErr
|
|
|
1283
1259
|
return {
|
|
1284
1260
|
content: [{
|
|
1285
1261
|
type: 'text',
|
|
1286
|
-
text: `🔍
|
|
1262
|
+
text: `🔍 faf-mcp Debug Failed: ${error instanceof Error ? error.message : String(error)}`
|
|
1287
1263
|
}],
|
|
1288
1264
|
isError: true
|
|
1289
1265
|
};
|
|
@@ -1319,23 +1295,18 @@ ${debugInfo.permissions.writeError ? ` Error: ${debugInfo.permissions.writeErr
|
|
|
1319
1295
|
- Confirm: "Creating at ~/Projects/heritage-club-dubai/"
|
|
1320
1296
|
|
|
1321
1297
|
## Real Filesystem Only
|
|
1322
|
-
- ✅
|
|
1298
|
+
- ✅ \`~/Projects/my-app/\`
|
|
1323
1299
|
- ❌ \`/mnt/user-data/\` (container paths)
|
|
1324
1300
|
- ❌ \`/home/claude/\` (container paths)
|
|
1325
1301
|
|
|
1326
1302
|
## Commands
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
- \`
|
|
1331
|
-
- \`
|
|
1332
|
-
- \`faf
|
|
1333
|
-
- \`faf
|
|
1334
|
-
|
|
1335
|
-
**Extensions:**
|
|
1336
|
-
- \`new\` - force overwrite existing
|
|
1337
|
-
- \`full\` - detailed output
|
|
1338
|
-
- \`bi\` - bi-directional sync
|
|
1303
|
+
Call these MCP tools:
|
|
1304
|
+
- \`faf_init\` (path) - create project.faf (infer path from context)
|
|
1305
|
+
- \`faf_auto\` - init or merge, stack detection, CLAUDE.md, and score in one call
|
|
1306
|
+
- \`faf_score\` - show AI-readiness
|
|
1307
|
+
- \`faf_go\` - guided questions for the human slots, to 100%
|
|
1308
|
+
- \`faf_sync\` - reconcile project.faf with package.json (dry-run; \`apply: true\` writes)
|
|
1309
|
+
- \`faf_claude\` - write CLAUDE.md from project.faf (\`all: true\` also writes AGENTS.md, .cursorrules, GEMINI.md)
|
|
1339
1310
|
|
|
1340
1311
|
## UX Rules
|
|
1341
1312
|
1. **Don't offer option menus** - just solve it
|
|
@@ -1495,7 +1466,7 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
|
|
|
1495
1466
|
}
|
|
1496
1467
|
}
|
|
1497
1468
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
1498
|
-
//
|
|
1469
|
+
// Human Context Tools (parity with faf-cli v3.2.0)
|
|
1499
1470
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
1500
1471
|
async handleFafReadme(args) {
|
|
1501
1472
|
try {
|
|
@@ -1682,7 +1653,7 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
|
|
|
1682
1653
|
return {
|
|
1683
1654
|
content: [{
|
|
1684
1655
|
type: 'text',
|
|
1685
|
-
text: `🔓 FAF Check:\n\n✅
|
|
1656
|
+
text: `🔓 FAF Check:\n\n✅ Cleared _protected_fields\n📁 Updated: ${fafResult.filename}`
|
|
1686
1657
|
}]
|
|
1687
1658
|
};
|
|
1688
1659
|
}
|
|
@@ -1700,14 +1671,15 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
|
|
|
1700
1671
|
for (const field of fields) {
|
|
1701
1672
|
qualities[field] = assessField(humanContext[field]);
|
|
1702
1673
|
}
|
|
1703
|
-
// Handle --protect
|
|
1674
|
+
// Handle --protect. _protected_fields is an advisory list: it is
|
|
1675
|
+
// recorded here and nothing else in faf-mcp reads it.
|
|
1704
1676
|
if (args?.protect) {
|
|
1705
|
-
const toProtect = fields.filter(f => qualities[f] === 'good'
|
|
1677
|
+
const toProtect = fields.filter(f => qualities[f] === 'good');
|
|
1706
1678
|
if (toProtect.length === 0) {
|
|
1707
1679
|
return {
|
|
1708
1680
|
content: [{
|
|
1709
1681
|
type: 'text',
|
|
1710
|
-
text: `🔒 FAF Check:\n\n⚠️ No fields
|
|
1682
|
+
text: `🔒 FAF Check:\n\n⚠️ No fields rated good — nothing recorded in _protected_fields`
|
|
1711
1683
|
}]
|
|
1712
1684
|
};
|
|
1713
1685
|
}
|
|
@@ -1716,13 +1688,13 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
|
|
|
1716
1688
|
return {
|
|
1717
1689
|
content: [{
|
|
1718
1690
|
type: 'text',
|
|
1719
|
-
text: `🔒 FAF Check:\n\n✅
|
|
1691
|
+
text: `🔒 FAF Check:\n\n✅ Recorded ${toProtect.length} field(s) in _protected_fields: ${toProtect.join(', ')}\nℹ️ Advisory only — other tools do not enforce it.\n📁 Updated: ${fafResult.filename}`
|
|
1720
1692
|
}]
|
|
1721
1693
|
};
|
|
1722
1694
|
}
|
|
1723
1695
|
// Default: show quality report
|
|
1724
1696
|
const icons = {
|
|
1725
|
-
empty: '⬜', generic: '
|
|
1697
|
+
empty: '⬜', generic: '◻️', good: '◼️'
|
|
1726
1698
|
};
|
|
1727
1699
|
let output = `🔍 FAF Human Context Quality\n\n`;
|
|
1728
1700
|
for (const field of fields) {
|
|
@@ -1732,11 +1704,11 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
|
|
|
1732
1704
|
const displayValue = value.length > 40 ? value.substring(0, 37) + '...' : value;
|
|
1733
1705
|
output += `${icons[q]} ${locked} ${field.toUpperCase().padEnd(6)} ${displayValue}\n`;
|
|
1734
1706
|
}
|
|
1735
|
-
const goodCount = fields.filter(f => qualities[f] === 'good'
|
|
1707
|
+
const goodCount = fields.filter(f => qualities[f] === 'good').length;
|
|
1736
1708
|
const emptyCount = fields.filter(f => qualities[f] === 'empty').length;
|
|
1737
1709
|
output += `\n📊 Quality: ${Math.round((goodCount / fields.length) * 100)}%\n`;
|
|
1738
1710
|
if (protectedFields.length > 0) {
|
|
1739
|
-
output += `🔒
|
|
1711
|
+
output += `🔒 _protected_fields (advisory, not enforced): ${protectedFields.join(', ')}\n`;
|
|
1740
1712
|
}
|
|
1741
1713
|
if (emptyCount > 0) {
|
|
1742
1714
|
output += `\n💡 Use faf_readme or faf_human_add to fill empty slots`;
|
|
@@ -1759,7 +1731,7 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
|
|
|
1759
1731
|
return {
|
|
1760
1732
|
content: [{
|
|
1761
1733
|
type: 'text',
|
|
1762
|
-
text: `📂 FAF Context Set:\n\n✅ Active project: ${newPath}\n${fafResult ? `✅ project.faf found: ${fafResult.filename}` : '⚠️ No project.faf in this directory'}\n\n💡
|
|
1734
|
+
text: `📂 FAF Context Set:\n\n✅ Active project: ${newPath}\n${fafResult ? `✅ project.faf found: ${fafResult.filename}` : '⚠️ No project.faf in this directory'}\n\n💡 Later faf_ project tools use this context (faf_read / faf_write resolve relative paths against the server's working directory)`
|
|
1763
1735
|
}]
|
|
1764
1736
|
};
|
|
1765
1737
|
}
|
|
@@ -1909,7 +1881,7 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
|
|
|
1909
1881
|
text: JSON.stringify({
|
|
1910
1882
|
complete: true,
|
|
1911
1883
|
score: 100,
|
|
1912
|
-
message: '
|
|
1884
|
+
message: '✪ GOLD CODE ACHIEVED! Your project has 100% AI-Readiness.',
|
|
1913
1885
|
context: 'faf_go'
|
|
1914
1886
|
}, null, 2)
|
|
1915
1887
|
}]
|
|
@@ -1961,7 +1933,7 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
|
|
|
1961
1933
|
targetScore: 100,
|
|
1962
1934
|
questionsRemaining: questions.length,
|
|
1963
1935
|
questions,
|
|
1964
|
-
instructions: '
|
|
1936
|
+
instructions: 'Ask the user these questions, then call faf_go again with the answers parameter.'
|
|
1965
1937
|
}, null, 2)
|
|
1966
1938
|
}]
|
|
1967
1939
|
};
|
|
@@ -2026,38 +1998,44 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
|
|
|
2026
1998
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
2027
1999
|
// Format output
|
|
2028
2000
|
const deltaDisplay = scoreDelta > 0 ? `(+${scoreDelta}%)` : scoreDelta < 0 ? `(${scoreDelta}%)` : '(no change)';
|
|
2029
|
-
let output =
|
|
2001
|
+
let output = `⚡️ FAF AUTO\n`;
|
|
2030
2002
|
output += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n`;
|
|
2031
2003
|
output += steps.join('\n') + '\n\n';
|
|
2032
2004
|
output += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
|
|
2033
2005
|
output += `⏱️ Completed in ${elapsed}s\n`;
|
|
2034
2006
|
output += `📊 Before: ${beforeScore}% | After: ${newScore}% ${deltaDisplay}\n`;
|
|
2035
2007
|
output += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n`;
|
|
2036
|
-
if (newScore >=
|
|
2037
|
-
output +=
|
|
2008
|
+
if (newScore >= 100) {
|
|
2009
|
+
output += `✪ TROPHY — 100% AI-Readiness. Your AI has complete context.\n`;
|
|
2010
|
+
}
|
|
2011
|
+
else if (newScore >= 99) {
|
|
2012
|
+
output += `★ Gold — 1% to Trophy. Run faf_go to finish.\n`;
|
|
2013
|
+
}
|
|
2014
|
+
else if (newScore >= 95) {
|
|
2015
|
+
output += `◆ Silver — ${100 - newScore}% to Trophy.\n`;
|
|
2038
2016
|
}
|
|
2039
2017
|
else if (newScore >= 85) {
|
|
2040
|
-
output +=
|
|
2018
|
+
output += `◇ Bronze — production ready. ${100 - newScore}% to Trophy.\n`;
|
|
2041
2019
|
}
|
|
2042
2020
|
else if (newScore >= 70) {
|
|
2043
|
-
output +=
|
|
2021
|
+
output += `● Green — solid foundation. Run faf_go to climb tiers.\n`;
|
|
2044
2022
|
}
|
|
2045
2023
|
else {
|
|
2046
2024
|
output += `🚀 Good start! Run faf_go for guided improvement.\n`;
|
|
2047
2025
|
}
|
|
2048
|
-
output += `\n💡 Next: faf_score | faf_go |
|
|
2026
|
+
output += `\n💡 Next: faf_score | faf_go | faf_claude`;
|
|
2049
2027
|
return { content: [{ type: 'text', text: output }] };
|
|
2050
2028
|
}
|
|
2051
2029
|
catch (error) {
|
|
2052
2030
|
return {
|
|
2053
|
-
content: [{ type: 'text', text:
|
|
2031
|
+
content: [{ type: 'text', text: `⚡️ FAF Auto:\n\n❌ Error: ${error.message}` }],
|
|
2054
2032
|
isError: true
|
|
2055
2033
|
};
|
|
2056
2034
|
}
|
|
2057
2035
|
}
|
|
2058
2036
|
/**
|
|
2059
2037
|
* faf_dna - Show your FAF DNA journey
|
|
2060
|
-
* Displays evolution from birth to
|
|
2038
|
+
* Displays the score evolution from birth to today (e.g. 22% → 85% → 100%)
|
|
2061
2039
|
*/
|
|
2062
2040
|
async handleFafDna(args) {
|
|
2063
2041
|
const cwd = this.getProjectPath(args?.path);
|
|
@@ -2171,7 +2149,7 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
|
|
|
2171
2149
|
output += `\n🧬 MILESTONES\n`;
|
|
2172
2150
|
const milestoneIcons = {
|
|
2173
2151
|
birth: '🐣', first_save: '💾', doubled: '2️⃣', sync: '🔄',
|
|
2174
|
-
championship: '
|
|
2152
|
+
championship: '✪', elite: '⭐', peak: '🏔️', perfect: '💎'
|
|
2175
2153
|
};
|
|
2176
2154
|
for (const m of milestones) {
|
|
2177
2155
|
const icon = milestoneIcons[m.type] || '📍';
|
|
@@ -2212,15 +2190,18 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
|
|
|
2212
2190
|
const analysis = await (0, turbocat_bridge_js_1.turboCatDisplay)(cwd);
|
|
2213
2191
|
const elapsed = Date.now() - startTime;
|
|
2214
2192
|
if (args?.json) {
|
|
2193
|
+
// The sum of format priorities is not a FAF score; name it for what
|
|
2194
|
+
// it is so no second "score" reaches clients next to faf_score's.
|
|
2195
|
+
const { totalIntelligenceScore, ...rest } = analysis;
|
|
2215
2196
|
return {
|
|
2216
2197
|
content: [{
|
|
2217
2198
|
type: 'text',
|
|
2218
|
-
text: JSON.stringify(
|
|
2199
|
+
text: JSON.stringify({ ...rest, formatPriorityTotal: totalIntelligenceScore }, null, 2)
|
|
2219
2200
|
}]
|
|
2220
2201
|
};
|
|
2221
2202
|
}
|
|
2222
2203
|
// Format human-readable output
|
|
2223
|
-
let output = `😽 TURBO-CAT™ Format Discovery
|
|
2204
|
+
let output = `😽 TURBO-CAT™ Format Discovery\n`;
|
|
2224
2205
|
output += `═══════════════════════════════════════════════════\n\n`;
|
|
2225
2206
|
output += `✅ Found ${analysis.discoveredFormats.length} formats in ${elapsed}ms!\n\n`;
|
|
2226
2207
|
output += `📋 Discovered Formats (A-Z):\n`;
|
|
@@ -2228,8 +2209,7 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
|
|
|
2228
2209
|
for (const format of sorted) {
|
|
2229
2210
|
output += ` ✅ ${format.fileName}\n`;
|
|
2230
2211
|
}
|
|
2231
|
-
output += `\n💡 Stack Signature: ${analysis.stackSignature}\n`;
|
|
2232
|
-
output += `🏆 Intelligence Score: ${analysis.totalIntelligenceScore}\n\n`;
|
|
2212
|
+
output += `\n💡 Stack Signature: ${analysis.stackSignature}\n\n`;
|
|
2233
2213
|
if (Object.keys(analysis.slotFillRecommendations).length > 0) {
|
|
2234
2214
|
output += `📊 Recommended Slot Fills:\n`;
|
|
2235
2215
|
for (const [key, value] of Object.entries(analysis.slotFillRecommendations)) {
|
|
@@ -2274,7 +2254,7 @@ Examples:
|
|
|
2274
2254
|
"api-service, REST API for mobile app, python, fastapi, aws"
|
|
2275
2255
|
"cli-tool, developer productivity tool, go"
|
|
2276
2256
|
|
|
2277
|
-
Minimum: name and description. Rest is
|
|
2257
|
+
Minimum: name and description. Rest is optional.`
|
|
2278
2258
|
}]
|
|
2279
2259
|
};
|
|
2280
2260
|
}
|
|
@@ -2295,7 +2275,8 @@ Example: "my-app, e-commerce platform"`
|
|
|
2295
2275
|
}
|
|
2296
2276
|
const projectName = parts[0] || 'my-project';
|
|
2297
2277
|
const projectGoal = parts[1] || 'Build amazing software';
|
|
2298
|
-
|
|
2278
|
+
// Only what the user gave: no language is written unless they named one.
|
|
2279
|
+
const mainLanguage = parts[2] || undefined;
|
|
2299
2280
|
const framework = parts[3] || 'none';
|
|
2300
2281
|
const hosting = parts[4] || 'cloud';
|
|
2301
2282
|
// Check if .faf exists
|
|
@@ -2313,16 +2294,16 @@ Use force: true to overwrite, or use faf_go / faf_human_add to modify.`
|
|
|
2313
2294
|
};
|
|
2314
2295
|
}
|
|
2315
2296
|
// Detect project type from inputs
|
|
2316
|
-
const projectType = this.detectProjectTypeFromQuick(projectGoal, framework, mainLanguage);
|
|
2297
|
+
const projectType = this.detectProjectTypeFromQuick(projectGoal, framework, mainLanguage ?? '');
|
|
2317
2298
|
// Build .faf content
|
|
2318
2299
|
const fafData = {
|
|
2300
|
+
faf_version: '3.0',
|
|
2319
2301
|
project: {
|
|
2320
2302
|
name: projectName,
|
|
2321
2303
|
goal: projectGoal,
|
|
2322
|
-
main_language: mainLanguage
|
|
2304
|
+
...(mainLanguage ? { main_language: mainLanguage } : {})
|
|
2323
2305
|
},
|
|
2324
2306
|
type: projectType,
|
|
2325
|
-
generated: new Date().toISOString(),
|
|
2326
2307
|
version: version_1.VERSION,
|
|
2327
2308
|
initialized_by: 'faf-mcp-quick'
|
|
2328
2309
|
};
|
|
@@ -2340,7 +2321,8 @@ Use force: true to overwrite, or use faf_go / faf_human_add to modify.`
|
|
|
2340
2321
|
let output = `⚡ FAF Quick - Created in ${elapsed}ms!\n\n`;
|
|
2341
2322
|
output += `📦 Project: ${projectName}\n`;
|
|
2342
2323
|
output += `🎯 Purpose: ${projectGoal}\n`;
|
|
2343
|
-
|
|
2324
|
+
const stackShown = [mainLanguage, framework !== 'none' ? framework : undefined].filter(Boolean).join(' + ');
|
|
2325
|
+
output += `💻 Stack: ${stackShown || '(not given)'}\n`;
|
|
2344
2326
|
output += `📍 Type: ${projectType}\n\n`;
|
|
2345
2327
|
output += `✅ Created: ${fafPath}\n\n`;
|
|
2346
2328
|
output += `Next steps:\n`;
|
|
@@ -2438,7 +2420,7 @@ Use force: true to overwrite, or use faf_go / faf_human_add to modify.`
|
|
|
2438
2420
|
results.push({
|
|
2439
2421
|
status: 'error',
|
|
2440
2422
|
message: '.faf file is empty',
|
|
2441
|
-
fix: 'Run: faf_init with force option to
|
|
2423
|
+
fix: 'Run: faf_init with force option to rewrite it'
|
|
2442
2424
|
});
|
|
2443
2425
|
}
|
|
2444
2426
|
else {
|
|
@@ -2479,7 +2461,7 @@ Use force: true to overwrite, or use faf_go / faf_human_add to modify.`
|
|
|
2479
2461
|
results.push({
|
|
2480
2462
|
status: 'warning',
|
|
2481
2463
|
message: `Score could be better: ${score}% (${slotSummary})`,
|
|
2482
|
-
fix: '
|
|
2464
|
+
fix: 'Run faf_go — the target is 100%'
|
|
2483
2465
|
});
|
|
2484
2466
|
}
|
|
2485
2467
|
else {
|
|
@@ -2494,7 +2476,7 @@ Use force: true to overwrite, or use faf_go / faf_human_add to modify.`
|
|
|
2494
2476
|
results.push({
|
|
2495
2477
|
status: 'error',
|
|
2496
2478
|
message: '.faf file is corrupted or invalid YAML',
|
|
2497
|
-
fix: 'Run: faf_init with force option to
|
|
2479
|
+
fix: 'Run: faf_init with force option to rewrite it'
|
|
2498
2480
|
});
|
|
2499
2481
|
}
|
|
2500
2482
|
}
|
|
@@ -2504,13 +2486,13 @@ Use force: true to overwrite, or use faf_go / faf_human_add to modify.`
|
|
|
2504
2486
|
results.push({
|
|
2505
2487
|
status: 'warning',
|
|
2506
2488
|
message: 'No CLAUDE.md file',
|
|
2507
|
-
fix: 'Run: faf_auto or
|
|
2489
|
+
fix: 'Run: faf_auto or faf_claude to write CLAUDE.md'
|
|
2508
2490
|
});
|
|
2509
2491
|
}
|
|
2510
2492
|
else {
|
|
2511
2493
|
results.push({
|
|
2512
2494
|
status: 'ok',
|
|
2513
|
-
message: 'CLAUDE.md found
|
|
2495
|
+
message: 'CLAUDE.md found'
|
|
2514
2496
|
});
|
|
2515
2497
|
}
|
|
2516
2498
|
// Check 6: Project detection
|
|
@@ -2568,7 +2550,7 @@ Use force: true to overwrite, or use faf_go / faf_human_add to modify.`
|
|
|
2568
2550
|
}
|
|
2569
2551
|
output += `\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n`;
|
|
2570
2552
|
if (!hasErrors && !hasWarnings) {
|
|
2571
|
-
output +=
|
|
2553
|
+
output += `✅ Perfect health — no issues found.`;
|
|
2572
2554
|
}
|
|
2573
2555
|
else if (!hasErrors) {
|
|
2574
2556
|
output += `🎯 Good health with minor improvements suggested.`;
|
|
@@ -2586,7 +2568,7 @@ Use force: true to overwrite, or use faf_go / faf_human_add to modify.`
|
|
|
2586
2568
|
}
|
|
2587
2569
|
}
|
|
2588
2570
|
// ============================================================================
|
|
2589
|
-
//
|
|
2571
|
+
// INTEROP HANDLERS (faf-mcp 2.0.0; ported from faf-cli 4.5.0 via claude-faf-mcp 4.5.0)
|
|
2590
2572
|
// ============================================================================
|
|
2591
2573
|
async handleFafAgents(args) {
|
|
2592
2574
|
const cwd = this.getProjectPath(args?.path);
|
|
@@ -2710,9 +2692,12 @@ Use force: true to overwrite, or use faf_go / faf_human_add to modify.`
|
|
|
2710
2692
|
}
|
|
2711
2693
|
const outputPath = args?.path ? this.getProjectPath(args.path) : undefined;
|
|
2712
2694
|
try {
|
|
2695
|
+
// --force goes last: the engine reads url and the output path from the
|
|
2696
|
+
// positional args, flags excluded.
|
|
2713
2697
|
const result = await this.engineAdapter.callEngine('git', [
|
|
2714
2698
|
url,
|
|
2715
2699
|
...(outputPath ? [outputPath] : []),
|
|
2700
|
+
...(args?.force ? ['--force'] : []),
|
|
2716
2701
|
]);
|
|
2717
2702
|
if (!result.success) {
|
|
2718
2703
|
return {
|
|
@@ -2722,9 +2707,9 @@ Use force: true to overwrite, or use faf_go / faf_human_add to modify.`
|
|
|
2722
2707
|
}
|
|
2723
2708
|
const data = result.data;
|
|
2724
2709
|
let output = `GitHub Context:\n\n✅ ${data?.message || 'Done'}\n⏱️ ${result.duration}ms`;
|
|
2725
|
-
// Include
|
|
2710
|
+
// Include the authored .faf content if no output path (preview mode)
|
|
2726
2711
|
if (!outputPath && data?.data?.fafContent) {
|
|
2727
|
-
output += `\n\n---
|
|
2712
|
+
output += `\n\n--- Authored project.faf ---\n${data.data.fafContent}`;
|
|
2728
2713
|
}
|
|
2729
2714
|
return {
|
|
2730
2715
|
content: [{ type: 'text', text: output }]
|