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.
Files changed (62) hide show
  1. package/CHANGELOG.md +37 -2
  2. package/CLAUDE.md +6 -2
  3. package/README.md +48 -28
  4. package/dist/src/cli.js +2 -1
  5. package/dist/src/cli.js.map +1 -1
  6. package/dist/src/faf-core/commands/agents.d.ts +2 -2
  7. package/dist/src/faf-core/commands/agents.js +21 -7
  8. package/dist/src/faf-core/commands/agents.js.map +1 -1
  9. package/dist/src/faf-core/commands/claude.d.ts +30 -0
  10. package/dist/src/faf-core/commands/{bi-sync.js → claude.js} +19 -15
  11. package/dist/src/faf-core/commands/claude.js.map +1 -0
  12. package/dist/src/faf-core/commands/conductor.d.ts +1 -1
  13. package/dist/src/faf-core/commands/conductor.js +36 -11
  14. package/dist/src/faf-core/commands/conductor.js.map +1 -1
  15. package/dist/src/faf-core/commands/cursor.d.ts +2 -2
  16. package/dist/src/faf-core/commands/cursor.js +21 -7
  17. package/dist/src/faf-core/commands/cursor.js.map +1 -1
  18. package/dist/src/faf-core/commands/gemini.d.ts +2 -2
  19. package/dist/src/faf-core/commands/gemini.js +21 -7
  20. package/dist/src/faf-core/commands/gemini.js.map +1 -1
  21. package/dist/src/faf-core/commands/git-context.d.ts +13 -4
  22. package/dist/src/faf-core/commands/git-context.js +30 -8
  23. package/dist/src/faf-core/commands/git-context.js.map +1 -1
  24. package/dist/src/faf-core/commands/sync.d.ts +8 -0
  25. package/dist/src/faf-core/commands/sync.js +20 -23
  26. package/dist/src/faf-core/commands/sync.js.map +1 -1
  27. package/dist/src/faf-core/fix-once/yaml.js +12 -13
  28. package/dist/src/faf-core/fix-once/yaml.js.map +1 -1
  29. package/dist/src/faf-core/parsers/agents-parser.d.ts +4 -3
  30. package/dist/src/faf-core/parsers/agents-parser.js +4 -3
  31. package/dist/src/faf-core/parsers/agents-parser.js.map +1 -1
  32. package/dist/src/faf-core/parsers/conductor-parser.d.ts +1 -1
  33. package/dist/src/faf-core/parsers/conductor-parser.js +1 -1
  34. package/dist/src/faf-core/parsers/cursorrules-parser.d.ts +4 -3
  35. package/dist/src/faf-core/parsers/cursorrules-parser.js +4 -3
  36. package/dist/src/faf-core/parsers/cursorrules-parser.js.map +1 -1
  37. package/dist/src/faf-core/parsers/faf-git-generator.d.ts +1 -2
  38. package/dist/src/faf-core/parsers/faf-git-generator.js +19 -39
  39. package/dist/src/faf-core/parsers/faf-git-generator.js.map +1 -1
  40. package/dist/src/faf-core/parsers/gemini-parser.d.ts +4 -3
  41. package/dist/src/faf-core/parsers/gemini-parser.js +4 -3
  42. package/dist/src/faf-core/parsers/gemini-parser.js.map +1 -1
  43. package/dist/src/faf-core/parsers/github-extractor.d.ts +1 -1
  44. package/dist/src/faf-core/parsers/github-extractor.js +1 -1
  45. package/dist/src/faf-core/parsers/slot-counter.d.ts +1 -1
  46. package/dist/src/faf-core/parsers/slot-counter.js +1 -1
  47. package/dist/src/handlers/engine-adapter.js +10 -7
  48. package/dist/src/handlers/engine-adapter.js.map +1 -1
  49. package/dist/src/handlers/tools.d.ts +2 -2
  50. package/dist/src/handlers/tools.js +191 -206
  51. package/dist/src/handlers/tools.js.map +1 -1
  52. package/dist/src/server.js +1 -1
  53. package/dist/src/server.js.map +1 -1
  54. package/dist/src/utils/faf-cli-bridge.d.ts +12 -8
  55. package/dist/src/utils/faf-cli-bridge.js +12 -8
  56. package/dist/src/utils/faf-cli-bridge.js.map +1 -1
  57. package/package.json +5 -5
  58. package/project.faf +5 -5
  59. package/scripts/sync-version.js +2 -1
  60. package/skills/faf-ide/SKILL.md +5 -5
  61. package/dist/src/faf-core/commands/bi-sync.d.ts +0 -29
  62. 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 temporary one-file re-export
53
- // that pins the relative dist path (faf-cli 6.7.1's `bun` exports condition
54
- // resolves to a non-shipped src/, breaking the test runner). Both Node and
55
- // Bun load the same compiled module through the bridge. Bridge is removable
56
- // once faf-cli drops the bad `bun` condition. See the bridge header for full
57
- // rationale and the linked tracked issue.
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
- * (faf_bi_sync + faf_agents/faf_cursor/faf_gemini) is this edition's identity —
70
- * the Cursor / IDE multi-format sync — where CFM carries memory tools instead.
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
- 'faf_bi_sync', 'faf_agents', 'faf_cursor', 'faf_gemini',
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 for structural integrity and field consistency. Returns trust metrics flagging malformed, missing, or contradictory data. Use this to confirm the context is sound before relying on it or syncing it to other formats.',
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 faf_bi_sync for CLAUDE.md, AGENTS.md, .cursorrules and GEMINI.md.',
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: 'faf_bi_sync',
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: 'Sync .faf to CLAUDE.md and IDE formats',
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 sync to AGENTS.md (OpenAI/Codex format)' },
238
- cursor: { type: 'boolean', description: 'Also sync to .cursorrules (Cursor IDE format)' },
239
- gemini: { type: 'boolean', description: 'Also sync to GEMINI.md (Google Gemini format)' },
240
- all: { type: 'boolean', description: 'Sync to ALL formats: CLAUDE.md + AGENTS.md + .cursorrules + GEMINI.md' },
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 the ~/.faf-cli-cache directory left by older faf-mcp versions. This version persists nothing outside your project files.',
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 persisted (default; today that is the cache)' }
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 project root (cwd / FAF_ALLOWED_ROOTS). Paths that escape the project are refused.',
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 .faf File',
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 project root (cwd / FAF_ALLOWED_ROOTS). Paths that escape the project are refused.',
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 .faf File',
301
+ title: 'Write Project File',
303
302
  readOnlyHint: false,
304
- destructiveHint: false,
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 .faf Files',
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/excellent. Returns the ratings; with protect it locks good/excellent fields from being overwritten, with unlock it releases them. Use this to gauge context quality and guard your best answers.',
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: 'Lock good/excellent fields from being overwritten' },
429
- unlock: { type: 'boolean', description: 'Remove all field protections' },
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_ calls resolve against. Returns the current context path. Call this once at the start of a session so the other tools target the right project.',
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 full setup pipeline in one call — init, stack detection, sync, bi-sync, and score — taking a project from no context to a complete project.faf. 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.',
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 - See your evolution from birth to championship (22% 85% 99%)',
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: true,
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) and surface which project.faf stack slots to fill. Returns the detected formats and slot-fill recommendations. Use this to populate the technical context.',
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, empty or weak slots, and common configuration issues, each with how to fix it. Returns a prioritized checklist. Use this when your score is low and you need to know why.',
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
- // v4.5.0 INTEROP TOOLS
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, export, or sync context between AGENTS.md (the OpenAI/Codex convention) and 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.',
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 (bidirectional)' },
578
- force: { type: 'boolean', description: 'Force overwrite existing files' },
579
- merge: { type: 'boolean', description: 'Merge imported data with existing .faf instead of replacing' },
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, export, or sync context between .cursorrules (the Cursor IDE convention) and 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.',
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 (bidirectional)' },
599
- force: { type: 'boolean', description: 'Force overwrite existing files' },
600
- merge: { type: 'boolean', description: 'Merge imported data with existing .faf instead of replacing' },
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, export, or sync context between GEMINI.md (the Google Gemini CLI convention) and 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.',
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 (bidirectional)' },
620
- force: { type: 'boolean', description: 'Force overwrite existing files' },
621
- merge: { type: 'boolean', description: 'Merge imported data with existing .faf instead of replacing' },
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: false,
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: 'Merge imported data with existing .faf instead of replacing' },
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: 'Generate a project.faf from any public GitHub repository URL — fetches the repo and extracts its stack and context. Returns the generated .faf. Use this to bootstrap context for a project you have the URL for but not cloned locally.',
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: false,
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 generated project.faf. If omitted, returns content without writing.' }
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
- return await this.handleFafBiSync(args);
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
- // v4.5.0 Interop tools
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: `🤖 Claude FAF Project Status:\n\n❌ No FAF file found in ${cwd}\n💡 Run faf_init to create project.faf`
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: `🤖 Claude FAF Project Status:\n\n✅ ${fafResult.filename} found in ${cwd}\n\nContent preview:\n${lines.join('\n')}`
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: `🤖 Claude FAF Project Status:\n\n❌ Error: ${error.message}`
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 regenerate a valid file.`,
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. Slot-by-slot detail: \`faf score\` (CLI).`;
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
- // Native implementation - creates project.faf with Pomelli-simple path resolution!
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
- // Use smart path resolution (supports "my-app", "~/Projects/my-app", "/full/path")
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: `🚀 Claude FAF Initialization:\n\n⚠️ ${existingFaf.filename} already exists in ${targetDir}\n💡 Use force: true to overwrite`
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
- // Check project type with fuzzy detection (Friday Feature!)
924
- const projectDescription = args?.description || '';
925
- // Detect Chrome Extension with fuzzy matching
926
- const chromeDetection = fuzzy_detector_1.FuzzyDetector.detectChromeExtension(projectDescription);
927
- const projectType = fuzzy_detector_1.FuzzyDetector.detectProjectType(projectDescription);
928
- // Build project data with Intel-Friday auto-fill!
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: `🚀 Claude FAF Initialization:\n\n✅ Created project.faf\n\n${pathConfirmation}${sourceExplanation}\n\n🍊 Vitamin Context activated!\n⚡ FAFFLESS AI ready!${chromeDetection.detected ? '\n\n🎯 Friday Feature: Chrome Extension detected!\n📈 Auto-filled 7 slots for 90%+ score!' : ''}${chromeDetection.corrected ? `\n📝 Auto-corrected: "${args?.description}" → "${chromeDetection.corrected}"` : ''}\n\n🏁 Next steps:\n • Run faf_score for AI-readiness score\n • Run faf_bi_sync to create CLAUDE.md\n • Run faf_go to fill the gaps`
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: `🚀 Claude FAF Initialization:\n\n❌ Error: ${error.message}`
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: `🔒 Claude FAF Trust Validation:\n\n❌ No .faf found in ${cwd}\n💡 Run faf_init to create one — then faf_trust reports real trust metrics.`
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: `🔒 Claude FAF Trust Validation:\n\n❌ Could not read ${fafPath}: ${error?.message ?? String(error)}`
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
- ? `✅ Structurally sound — no malformed, missing, or contradictory fields found.`
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: `🔒 Claude FAF Trust Validation:\n\n${output}`
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: `🔄 Claude FAF Sync:\n\nFailed to sync: ${result.error}`
1032
+ text: `🔄 FAF Sync:\n\nFailed to sync: ${result.error}`
1066
1033
  }],
1067
1034
  isError: true
1068
1035
  };
1069
1036
  }
1070
- const output = typeof result.data === 'string'
1071
- ? result.data
1072
- : result.data?.output || JSON.stringify(result.data, null, 2);
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: `🔄 Claude FAF Sync:\n\n${output}`
1050
+ text: `🔄 FAF Sync:\n\n${output}`
1077
1051
  }]
1078
1052
  };
1079
1053
  }
1080
- async handleFafBiSync(args) {
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 syncBiDirectional, so the schema no longer promises them.
1087
- const biSyncArgs = [];
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-adapter's bi-sync dispatch —
1090
- // faf_bi_sync silently ignored them and only ever wrote CLAUDE.md.
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
- biSyncArgs.push('--agents');
1066
+ claudeArgs.push('--agents');
1093
1067
  }
1094
1068
  if (args?.cursor) {
1095
- biSyncArgs.push('--cursor');
1069
+ claudeArgs.push('--cursor');
1096
1070
  }
1097
1071
  if (args?.gemini) {
1098
- biSyncArgs.push('--gemini');
1072
+ claudeArgs.push('--gemini');
1099
1073
  }
1100
1074
  if (args?.all) {
1101
- biSyncArgs.push('--all');
1075
+ claudeArgs.push('--all');
1102
1076
  }
1103
- const result = await this.engineAdapter.callEngine('bi-sync', biSyncArgs);
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: `🔗 Claude FAF Bi-Sync:\n\nFailed to bi-sync: ${result.error}`
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?.output || JSON.stringify(result.data, null, 2);
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: `🔗 Claude FAF Bi-Sync:\n\n${output}`
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: `🧹 Claude FAF Clear:\n\n${lines.join('\n')}`
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: Generated in <29ms
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 universal,
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. Talk to Claude to bi-sync it
1180
+ 3. Ask your AI to run faf_claude
1203
1181
  4. You're done⚡
1204
1182
 
1205
- 🩵 You just made Claude Happy
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 'faf' on any project to create one.
1224
- Run 'faf_score' to check AI-readiness (target: 99%).
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
- const testFile = path.join(cwd, '.claude-faf-test');
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 ? '✅ Yes' : '❌ No'}
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: `🔍 Claude FAF Debug Failed: ${error instanceof Error ? error.message : String(error)}`
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
- - ✅ \`/Users/wolfejam/Projects/my-app/\`
1298
+ - ✅ \`~/Projects/my-app/\`
1323
1299
  - ❌ \`/mnt/user-data/\` (container paths)
1324
1300
  - ❌ \`/home/claude/\` (container paths)
1325
1301
 
1326
1302
  ## Commands
1327
- All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
1328
-
1329
- **Core:**
1330
- - \`faf init\` - create FAF (infer path from context)
1331
- - \`faf score\` - show AI-readiness
1332
- - \`faf sync\` - synchronize files
1333
- - \`faf quick\` - rapid FAF creation
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
- // NEW: Human Context Tools (v3.2.0 parity)
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✅ All fields unlocked\n📁 Updated: ${fafResult.filename}`
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' || qualities[f] === 'excellent');
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 qualify for protection (need good or excellent quality)`
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✅ Protected ${toProtect.length} field(s): ${toProtect.join(', ')}\n📁 Updated: ${fafResult.filename}`
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: '🟡', good: '🟢', excellent: '💎'
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' || qualities[f] === 'excellent').length;
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 += `🔒 Protected: ${protectedFields.join(', ')}\n`;
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💡 Subsequent faf_* calls will use this context`
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: '🏆 GOLD CODE ACHIEVED! Your project has 100% AI-Readiness.',
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: 'Use AskUserQuestion to ask these questions, then call faf_go again with the answers parameter to apply them.'
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 = `🏎️⚡️ FAF AUTO - CHAMPIONSHIP MODE!\n`;
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 >= 99) {
2037
- output += `🏆 CHAMPIONSHIP ACHIEVED! Your AI has complete context.\n`;
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 += `🥇 Elite level! ${100 - newScore}% to perfection.\n`;
2018
+ output += `◇ Bronze — production ready. ${100 - newScore}% to Trophy.\n`;
2041
2019
  }
2042
2020
  else if (newScore >= 70) {
2043
- output += `🥈 Great progress! Run faf_go to reach championship.\n`;
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 | faf_bi_sync`;
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: `🏎️ FAF Auto:\n\n❌ Error: ${error.message}` }],
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 current (22% → 85% → 99%)
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: '🏆', elite: '⭐', peak: '🏔️', perfect: '💎'
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(analysis, null, 2)
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 v2.0.0\n`;
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 auto-detected!`
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
- const mainLanguage = parts[2] || 'TypeScript';
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
- output += `💻 Stack: ${mainLanguage}${framework !== 'none' ? ` + ${framework}` : ''}\n`;
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 regenerate'
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: 'Target 70%+ for championship AI context'
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 regenerate'
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 faf_bi_sync to create bi-directional sync'
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 (bi-sync ready)'
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 += `🏆 Perfect health! Your FAF setup is championship-ready!`;
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
- // v4.5.0 INTEROP HANDLERS
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 generated .faf content if no output path (preview mode)
2710
+ // Include the authored .faf content if no output path (preview mode)
2726
2711
  if (!outputPath && data?.data?.fafContent) {
2727
- output += `\n\n--- Generated project.faf ---\n${data.data.fafContent}`;
2712
+ output += `\n\n--- Authored project.faf ---\n${data.data.fafContent}`;
2728
2713
  }
2729
2714
  return {
2730
2715
  content: [{ type: 'text', text: output }]