faf-mcp 2.3.1 → 3.0.1

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 (196) hide show
  1. package/CHANGELOG.md +49 -4
  2. package/CLAUDE.md +10 -6
  3. package/README.md +95 -86
  4. package/dist/src/faf-core/commands/agents.d.ts +2 -2
  5. package/dist/src/faf-core/commands/agents.js +2 -2
  6. package/dist/src/faf-core/commands/claude.d.ts +31 -0
  7. package/dist/src/faf-core/commands/{bi-sync.js → claude.js} +39 -77
  8. package/dist/src/faf-core/commands/claude.js.map +1 -0
  9. package/dist/src/faf-core/commands/conductor.d.ts +1 -1
  10. package/dist/src/faf-core/commands/conductor.js +1 -1
  11. package/dist/src/faf-core/commands/cursor.d.ts +2 -2
  12. package/dist/src/faf-core/commands/cursor.js +2 -2
  13. package/dist/src/faf-core/commands/gemini.d.ts +2 -2
  14. package/dist/src/faf-core/commands/gemini.js +2 -2
  15. package/dist/src/faf-core/commands/git-context.d.ts +1 -1
  16. package/dist/src/faf-core/commands/git-context.js +2 -2
  17. package/dist/src/faf-core/commands/git-context.js.map +1 -1
  18. package/dist/src/faf-core/commands/sync.js.map +1 -1
  19. package/dist/src/faf-core/engines/dependency-tsa.js.map +1 -1
  20. package/dist/src/faf-core/fix-once/colors.js +1 -1
  21. package/dist/src/faf-core/fix-once/colors.js.map +1 -1
  22. package/dist/src/faf-core/parsers/agents-parser.d.ts +17 -1
  23. package/dist/src/faf-core/parsers/agents-parser.js +31 -124
  24. package/dist/src/faf-core/parsers/agents-parser.js.map +1 -1
  25. package/dist/src/faf-core/parsers/conductor-parser.d.ts +1 -1
  26. package/dist/src/faf-core/parsers/conductor-parser.js +2 -2
  27. package/dist/src/faf-core/parsers/conductor-parser.js.map +1 -1
  28. package/dist/src/faf-core/parsers/cursorrules-parser.d.ts +17 -1
  29. package/dist/src/faf-core/parsers/cursorrules-parser.js +56 -112
  30. package/dist/src/faf-core/parsers/cursorrules-parser.js.map +1 -1
  31. package/dist/src/faf-core/parsers/faf-git-generator.d.ts +1 -1
  32. package/dist/src/faf-core/parsers/faf-git-generator.js +6 -6
  33. package/dist/src/faf-core/parsers/faf-git-generator.js.map +1 -1
  34. package/dist/src/faf-core/parsers/gemini-parser.d.ts +12 -1
  35. package/dist/src/faf-core/parsers/gemini-parser.js +21 -78
  36. package/dist/src/faf-core/parsers/gemini-parser.js.map +1 -1
  37. package/dist/src/faf-core/parsers/github-extractor.d.ts +1 -1
  38. package/dist/src/faf-core/parsers/github-extractor.js +1 -1
  39. package/dist/src/faf-core/parsers/slot-counter.d.ts +1 -1
  40. package/dist/src/faf-core/parsers/slot-counter.js +1 -1
  41. package/dist/src/handlers/engine-adapter.d.ts +13 -12
  42. package/dist/src/handlers/engine-adapter.js +105 -683
  43. package/dist/src/handlers/engine-adapter.js.map +1 -1
  44. package/dist/src/handlers/resources.d.ts +19 -1
  45. package/dist/src/handlers/resources.js +108 -23
  46. package/dist/src/handlers/resources.js.map +1 -1
  47. package/dist/src/handlers/skills.d.ts +70 -0
  48. package/dist/src/handlers/skills.js +174 -0
  49. package/dist/src/handlers/skills.js.map +1 -0
  50. package/dist/src/handlers/tools.d.ts +43 -7
  51. package/dist/src/handlers/tools.js +300 -375
  52. package/dist/src/handlers/tools.js.map +1 -1
  53. package/dist/src/server.d.ts +4 -0
  54. package/dist/src/server.js +51 -6
  55. package/dist/src/server.js.map +1 -1
  56. package/dist/src/utils/faf-cli-bridge.d.ts +14 -8
  57. package/dist/src/utils/faf-cli-bridge.js +28 -8
  58. package/dist/src/utils/faf-cli-bridge.js.map +1 -1
  59. package/package.json +18 -15
  60. package/project.faf +14 -9
  61. package/scripts/check-engines.mjs +53 -0
  62. package/scripts/sync-version.js +33 -19
  63. package/skills/faf-ide/SKILL.md +40 -0
  64. package/dist/src/config/visibility.d.ts +0 -41
  65. package/dist/src/config/visibility.js +0 -158
  66. package/dist/src/config/visibility.js.map +0 -1
  67. package/dist/src/faf-core/commands/audit.d.ts +0 -21
  68. package/dist/src/faf-core/commands/audit.js +0 -83
  69. package/dist/src/faf-core/commands/audit.js.map +0 -1
  70. package/dist/src/faf-core/commands/auto.d.ts +0 -25
  71. package/dist/src/faf-core/commands/auto.js +0 -74
  72. package/dist/src/faf-core/commands/auto.js.map +0 -1
  73. package/dist/src/faf-core/commands/bi-sync.d.ts +0 -31
  74. package/dist/src/faf-core/commands/bi-sync.js.map +0 -1
  75. package/dist/src/faf-core/commands/doctor.d.ts +0 -17
  76. package/dist/src/faf-core/commands/doctor.js +0 -198
  77. package/dist/src/faf-core/commands/doctor.js.map +0 -1
  78. package/dist/src/faf-core/commands/enhance.d.ts +0 -46
  79. package/dist/src/faf-core/commands/enhance.js +0 -360
  80. package/dist/src/faf-core/commands/enhance.js.map +0 -1
  81. package/dist/src/faf-core/commands/formats.d.ts +0 -22
  82. package/dist/src/faf-core/commands/formats.js +0 -117
  83. package/dist/src/faf-core/commands/formats.js.map +0 -1
  84. package/dist/src/faf-core/commands/human.d.ts +0 -27
  85. package/dist/src/faf-core/commands/human.js +0 -183
  86. package/dist/src/faf-core/commands/human.js.map +0 -1
  87. package/dist/src/faf-core/commands/init.d.ts +0 -26
  88. package/dist/src/faf-core/commands/init.js +0 -114
  89. package/dist/src/faf-core/commands/init.js.map +0 -1
  90. package/dist/src/faf-core/commands/innit.d.ts +0 -7
  91. package/dist/src/faf-core/commands/innit.js +0 -13
  92. package/dist/src/faf-core/commands/innit.js.map +0 -1
  93. package/dist/src/faf-core/commands/migrate.d.ts +0 -15
  94. package/dist/src/faf-core/commands/migrate.js +0 -86
  95. package/dist/src/faf-core/commands/migrate.js.map +0 -1
  96. package/dist/src/faf-core/commands/quick.d.ts +0 -16
  97. package/dist/src/faf-core/commands/quick.js +0 -184
  98. package/dist/src/faf-core/commands/quick.js.map +0 -1
  99. package/dist/src/faf-core/commands/readme.d.ts +0 -47
  100. package/dist/src/faf-core/commands/readme.js +0 -361
  101. package/dist/src/faf-core/commands/readme.js.map +0 -1
  102. package/dist/src/faf-core/commands/score.d.ts +0 -47
  103. package/dist/src/faf-core/commands/score.js +0 -49
  104. package/dist/src/faf-core/commands/score.js.map +0 -1
  105. package/dist/src/faf-core/commands/update.d.ts +0 -12
  106. package/dist/src/faf-core/commands/update.js +0 -46
  107. package/dist/src/faf-core/commands/update.js.map +0 -1
  108. package/dist/src/faf-core/commands/validate.d.ts +0 -21
  109. package/dist/src/faf-core/commands/validate.js +0 -81
  110. package/dist/src/faf-core/commands/validate.js.map +0 -1
  111. package/dist/src/faf-core/compiler/faf-compiler.d.ts +0 -138
  112. package/dist/src/faf-core/compiler/faf-compiler.js +0 -1362
  113. package/dist/src/faf-core/compiler/faf-compiler.js.map +0 -1
  114. package/dist/src/faf-core/engines/fab-formats-processor.d.ts +0 -166
  115. package/dist/src/faf-core/engines/fab-formats-processor.js +0 -1274
  116. package/dist/src/faf-core/engines/fab-formats-processor.js.map +0 -1
  117. package/dist/src/faf-core/engines/faf-dna.d.ts +0 -159
  118. package/dist/src/faf-core/engines/faf-dna.js +0 -555
  119. package/dist/src/faf-core/engines/faf-dna.js.map +0 -1
  120. package/dist/src/faf-core/engines/relentless-context-extractor.d.ts +0 -100
  121. package/dist/src/faf-core/engines/relentless-context-extractor.js +0 -625
  122. package/dist/src/faf-core/engines/relentless-context-extractor.js.map +0 -1
  123. package/dist/src/faf-core/fix-once/types.d.ts +0 -257
  124. package/dist/src/faf-core/fix-once/types.js +0 -26
  125. package/dist/src/faf-core/fix-once/types.js.map +0 -1
  126. package/dist/src/faf-core/generators/faf-generator-championship.d.ts +0 -16
  127. package/dist/src/faf-core/generators/faf-generator-championship.js +0 -463
  128. package/dist/src/faf-core/generators/faf-generator-championship.js.map +0 -1
  129. package/dist/src/faf-core/inject.d.ts +0 -19
  130. package/dist/src/faf-core/inject.js +0 -57
  131. package/dist/src/faf-core/inject.js.map +0 -1
  132. package/dist/src/faf-core/parsers/index.d.ts +0 -12
  133. package/dist/src/faf-core/parsers/index.js +0 -29
  134. package/dist/src/faf-core/parsers/index.js.map +0 -1
  135. package/dist/src/faf-core/utils/balance-visualizer.d.ts +0 -37
  136. package/dist/src/faf-core/utils/balance-visualizer.js +0 -197
  137. package/dist/src/faf-core/utils/balance-visualizer.js.map +0 -1
  138. package/dist/src/faf-core/utils/championship-style.d.ts +0 -109
  139. package/dist/src/faf-core/utils/championship-style.js +0 -219
  140. package/dist/src/faf-core/utils/championship-style.js.map +0 -1
  141. package/dist/src/faf-core/utils/chrome-extension-detector.d.ts +0 -73
  142. package/dist/src/faf-core/utils/chrome-extension-detector.js +0 -268
  143. package/dist/src/faf-core/utils/chrome-extension-detector.js.map +0 -1
  144. package/dist/src/faf-core/utils/platform-detector.d.ts +0 -30
  145. package/dist/src/faf-core/utils/platform-detector.js +0 -218
  146. package/dist/src/faf-core/utils/platform-detector.js.map +0 -1
  147. package/dist/src/faf-core/utils/technical-credit.d.ts +0 -35
  148. package/dist/src/faf-core/utils/technical-credit.js +0 -286
  149. package/dist/src/faf-core/utils/technical-credit.js.map +0 -1
  150. package/dist/src/faf-core/utils/yaml-generator.d.ts +0 -41
  151. package/dist/src/faf-core/utils/yaml-generator.js +0 -360
  152. package/dist/src/faf-core/utils/yaml-generator.js.map +0 -1
  153. package/dist/src/handlers/behavioral-instruction.d.ts +0 -16
  154. package/dist/src/handlers/behavioral-instruction.js +0 -43
  155. package/dist/src/handlers/behavioral-instruction.js.map +0 -1
  156. package/dist/src/handlers/championship-tools.d.ts +0 -116
  157. package/dist/src/handlers/championship-tools.js +0 -2320
  158. package/dist/src/handlers/championship-tools.js.map +0 -1
  159. package/dist/src/handlers/cloud-handler.d.ts +0 -59
  160. package/dist/src/handlers/cloud-handler.js +0 -348
  161. package/dist/src/handlers/cloud-handler.js.map +0 -1
  162. package/dist/src/handlers/tool-registry.d.ts +0 -23
  163. package/dist/src/handlers/tool-registry.js +0 -68
  164. package/dist/src/handlers/tool-registry.js.map +0 -1
  165. package/dist/src/handlers/tool-types.d.ts +0 -167
  166. package/dist/src/handlers/tool-types.js +0 -7
  167. package/dist/src/handlers/tool-types.js.map +0 -1
  168. package/dist/src/test-all-functions.d.ts +0 -15
  169. package/dist/src/test-all-functions.js +0 -163
  170. package/dist/src/test-all-functions.js.map +0 -1
  171. package/dist/src/types/mcp-tools.d.ts +0 -53
  172. package/dist/src/types/mcp-tools.js +0 -77
  173. package/dist/src/types/mcp-tools.js.map +0 -1
  174. package/dist/src/types/tool-visibility.d.ts +0 -36
  175. package/dist/src/types/tool-visibility.js +0 -551
  176. package/dist/src/types/tool-visibility.js.map +0 -1
  177. package/dist/src/utils/auto-path-detection.d.ts +0 -26
  178. package/dist/src/utils/auto-path-detection.js +0 -198
  179. package/dist/src/utils/auto-path-detection.js.map +0 -1
  180. package/dist/src/utils/championship-format.d.ts +0 -30
  181. package/dist/src/utils/championship-format.js +0 -81
  182. package/dist/src/utils/championship-format.js.map +0 -1
  183. package/dist/src/utils/cli-detector.d.ts +0 -17
  184. package/dist/src/utils/cli-detector.js +0 -247
  185. package/dist/src/utils/cli-detector.js.map +0 -1
  186. package/dist/src/utils/display-protocol.d.ts +0 -57
  187. package/dist/src/utils/display-protocol.js +0 -131
  188. package/dist/src/utils/display-protocol.js.map +0 -1
  189. package/dist/src/utils/username-detector.d.ts +0 -27
  190. package/dist/src/utils/username-detector.js +0 -90
  191. package/dist/src/utils/username-detector.js.map +0 -1
  192. package/dist/src/utils/visual-style.d.ts +0 -62
  193. package/dist/src/utils/visual-style.js +0 -166
  194. package/dist/src/utils/visual-style.js.map +0 -1
  195. package/scripts/postinstall.js +0 -50
  196. package/skill/SKILL.md +0 -385
@@ -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
  ]);
@@ -206,9 +205,9 @@ class FafToolHandler {
206
205
  },
207
206
  {
208
207
  name: 'faf_sync',
209
- description: 'Sync a project.faf with CLAUDE.md so the two stay aligned. Returns what was written. Use this for the single CLAUDE.md target; use faf_bi_sync to fan out to AGENTS.md, .cursorrules, and GEMINI.md as well.',
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
- title: 'Sync .faf to CLAUDE.md',
210
+ title: 'Reconcile .faf with repo manifests',
212
211
  readOnlyHint: false,
213
212
  destructiveHint: false,
214
213
  openWorldHint: false
@@ -216,16 +215,17 @@ class FafToolHandler {
216
215
  inputSchema: {
217
216
  type: 'object',
218
217
  properties: {
218
+ apply: { type: 'boolean', description: 'Write the detected changes into project.faf (default: dry-run report only)' },
219
219
  path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
220
220
  },
221
221
  additionalProperties: false
222
222
  }
223
223
  },
224
224
  {
225
- name: 'faf_bi_sync',
226
- description: 'Bi-directionally sync a project.faf with CLAUDE.md and, with the format flags or all, also AGENTS.md, .cursorrules, and GEMINI.md. Returns the formats written and any conflicts. Use this to keep every AI tool context file aligned from the single .faf 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.)',
227
227
  annotations: {
228
- title: 'Bi-directional Sync',
228
+ title: 'Write CLAUDE.md and IDE formats from .faf',
229
229
  readOnlyHint: false,
230
230
  destructiveHint: false,
231
231
  openWorldHint: false
@@ -233,13 +233,10 @@ class FafToolHandler {
233
233
  inputSchema: {
234
234
  type: 'object',
235
235
  properties: {
236
- auto: { type: 'boolean', description: 'Enable automatic synchronization' },
237
- watch: { type: 'boolean', description: 'Start real-time file watching for changes' },
238
- force: { type: 'boolean', description: 'Force overwrite conflicting changes' },
239
- agents: { type: 'boolean', description: 'Also sync to AGENTS.md (OpenAI/Codex format)' },
240
- cursor: { type: 'boolean', description: 'Also sync to .cursorrules (Cursor IDE format)' },
241
- gemini: { type: 'boolean', description: 'Also sync to GEMINI.md (Google Gemini format)' },
242
- 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' },
243
240
  path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
244
241
  },
245
242
  additionalProperties: false
@@ -247,7 +244,7 @@ class FafToolHandler {
247
244
  },
248
245
  {
249
246
  name: 'faf_clear',
250
- description: 'Clear caches, temporary files, and reset FAF state for a fresh start',
247
+ description: 'Remove the ~/.faf-cli-cache directory left by older faf-mcp versions. This version persists nothing outside your project files.',
251
248
  annotations: {
252
249
  title: 'Clear .faf Data',
253
250
  readOnlyHint: false,
@@ -257,17 +254,15 @@ class FafToolHandler {
257
254
  inputSchema: {
258
255
  type: 'object',
259
256
  properties: {
260
- cache: { type: 'boolean', description: 'Clear trust cache only' },
261
- todos: { type: 'boolean', description: 'Clear todo lists only' },
262
- backups: { type: 'boolean', description: 'Clear backup files only' },
263
- all: { type: 'boolean', description: 'Clear everything (default)' }
257
+ cache: { type: 'boolean', description: 'Remove ~/.faf-cli-cache (written by faf-mcp < 3.0)' },
258
+ all: { type: 'boolean', description: 'Remove everything older versions persisted (default; today that is the cache)' }
264
259
  },
265
260
  additionalProperties: false
266
261
  }
267
262
  },
268
263
  {
269
264
  name: 'faf_debug',
270
- description: 'Debug faf-mcp environment - show working directory, permissions, and FAF CLI status',
265
+ description: 'Debug faf-mcp environment - show working directory, write permissions, the bundled faf-cli version, and whether a project.faf is present',
271
266
  annotations: {
272
267
  title: 'Debug Info',
273
268
  readOnlyHint: true,
@@ -476,7 +471,7 @@ class FafToolHandler {
476
471
  },
477
472
  {
478
473
  name: 'faf_auto',
479
- 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 full setup pipeline in one call — init, stack detection, sync, CLAUDE.md, 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.',
480
475
  annotations: {
481
476
  title: 'Auto-detect Context',
482
477
  readOnlyHint: false,
@@ -486,15 +481,17 @@ class FafToolHandler {
486
481
  inputSchema: {
487
482
  type: 'object',
488
483
  properties: {
489
- path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' },
490
- force: { type: 'boolean', description: 'Force overwrite existing files' }
484
+ path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
485
+ // `force` used to be declared here; handleFafAuto never read it.
486
+ // faf_auto merges into an existing project.faf non-destructively
487
+ // and there is no overwrite mode to switch on.
491
488
  },
492
489
  additionalProperties: false
493
490
  }
494
491
  },
495
492
  {
496
493
  name: 'faf_dna',
497
- description: 'Show your FAF DNA journey - See your evolution from birth to championship (22% 85% 99%)',
494
+ description: 'Show your FAF DNA journey — the score evolution of this project.faf from birth to today (e.g. 22% → 85% → 100%)',
498
495
  annotations: {
499
496
  title: 'View Project DNA',
500
497
  readOnlyHint: true,
@@ -562,11 +559,11 @@ class FafToolHandler {
562
559
  }
563
560
  },
564
561
  // ============================================================================
565
- // v4.5.0 INTEROP TOOLS
562
+ // 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
566
563
  // ============================================================================
567
564
  {
568
565
  name: 'faf_agents',
569
- 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.',
566
+ 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.',
570
567
  annotations: {
571
568
  title: 'Sync AGENTS.md',
572
569
  readOnlyHint: false,
@@ -576,7 +573,7 @@ class FafToolHandler {
576
573
  inputSchema: {
577
574
  type: 'object',
578
575
  properties: {
579
- action: { type: 'string', enum: ['import', 'export', 'sync'], description: 'Action: import (AGENTS.md -> .faf), export (.faf -> AGENTS.md), sync (bidirectional)' },
576
+ 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)' },
580
577
  force: { type: 'boolean', description: 'Force overwrite existing files' },
581
578
  merge: { type: 'boolean', description: 'Merge imported data with existing .faf instead of replacing' },
582
579
  path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
@@ -587,7 +584,7 @@ class FafToolHandler {
587
584
  },
588
585
  {
589
586
  name: 'faf_cursor',
590
- 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.',
587
+ 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.',
591
588
  annotations: {
592
589
  title: 'Sync .cursorrules',
593
590
  readOnlyHint: false,
@@ -597,7 +594,7 @@ class FafToolHandler {
597
594
  inputSchema: {
598
595
  type: 'object',
599
596
  properties: {
600
- action: { type: 'string', enum: ['import', 'export', 'sync'], description: 'Action: import (.cursorrules -> .faf), export (.faf -> .cursorrules), sync (bidirectional)' },
597
+ 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)' },
601
598
  force: { type: 'boolean', description: 'Force overwrite existing files' },
602
599
  merge: { type: 'boolean', description: 'Merge imported data with existing .faf instead of replacing' },
603
600
  path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
@@ -608,7 +605,7 @@ class FafToolHandler {
608
605
  },
609
606
  {
610
607
  name: 'faf_gemini',
611
- 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.',
608
+ 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.',
612
609
  annotations: {
613
610
  title: 'Sync GEMINI.md',
614
611
  readOnlyHint: false,
@@ -618,7 +615,7 @@ class FafToolHandler {
618
615
  inputSchema: {
619
616
  type: 'object',
620
617
  properties: {
621
- action: { type: 'string', enum: ['import', 'export', 'sync'], description: 'Action: import (GEMINI.md -> .faf), export (.faf -> GEMINI.md), sync (bidirectional)' },
618
+ 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)' },
622
619
  force: { type: 'boolean', description: 'Force overwrite existing files' },
623
620
  merge: { type: 'boolean', description: 'Merge imported data with existing .faf instead of replacing' },
624
621
  path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
@@ -650,7 +647,7 @@ class FafToolHandler {
650
647
  },
651
648
  {
652
649
  name: 'faf_git',
653
- 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.',
650
+ 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.',
654
651
  annotations: {
655
652
  title: 'Extract from GitHub',
656
653
  readOnlyHint: false,
@@ -661,7 +658,7 @@ class FafToolHandler {
661
658
  type: 'object',
662
659
  properties: {
663
660
  url: { type: 'string', description: 'GitHub repository URL (e.g., https://github.com/owner/repo or owner/repo)' },
664
- path: { type: 'string', description: 'Output directory for generated project.faf. If omitted, returns content without writing.' }
661
+ path: { type: 'string', description: 'Output directory for the authored project.faf. If omitted, returns content without writing.' }
665
662
  },
666
663
  required: ['url'],
667
664
  additionalProperties: false
@@ -691,8 +688,9 @@ class FafToolHandler {
691
688
  return await this.handleFafTrust(args);
692
689
  case 'faf_sync':
693
690
  return await this.handleFafSync(args);
694
- case 'faf_bi_sync':
695
- return await this.handleFafBiSync(args);
691
+ case 'faf_bi_sync': // pre-3.0.1 name: still callable, never listed
692
+ case 'faf_claude':
693
+ return await this.handleFafClaude(args);
696
694
  case 'faf_clear':
697
695
  return await this.handleFafClear(args);
698
696
  case 'faf_debug':
@@ -738,7 +736,7 @@ class FafToolHandler {
738
736
  return await this.handleFafQuick(args);
739
737
  case 'faf_doctor':
740
738
  return await this.handleFafDoctor(args);
741
- // v4.5.0 Interop tools
739
+ // Interop tools (faf-mcp 2.0.0; ported from faf-cli 4.5.0 via claude-faf-mcp 4.5.0)
742
740
  case 'faf_agents':
743
741
  return await this.handleFafAgents(args);
744
742
  case 'faf_cursor':
@@ -771,7 +769,7 @@ class FafToolHandler {
771
769
  return {
772
770
  content: [{
773
771
  type: 'text',
774
- text: `🤖 Claude FAF Project Status:\n\n❌ No FAF file found in ${cwd}\n💡 Run faf_init to create project.faf`
772
+ text: `🤖 FAF Project Status:\n\n❌ No FAF file found in ${cwd}\n💡 Run faf_init to create project.faf`
775
773
  }]
776
774
  };
777
775
  }
@@ -780,7 +778,7 @@ class FafToolHandler {
780
778
  return {
781
779
  content: [{
782
780
  type: 'text',
783
- text: `🤖 Claude FAF Project Status:\n\n✅ ${fafResult.filename} found in ${cwd}\n\nContent preview:\n${lines.join('\n')}`
781
+ text: `🤖 FAF Project Status:\n\n✅ ${fafResult.filename} found in ${cwd}\n\nContent preview:\n${lines.join('\n')}`
784
782
  }]
785
783
  };
786
784
  }
@@ -788,7 +786,7 @@ class FafToolHandler {
788
786
  return {
789
787
  content: [{
790
788
  type: 'text',
791
- text: `🤖 Claude FAF Project Status:\n\n❌ Error: ${error.message}`
789
+ text: `🤖 FAF Project Status:\n\n❌ Error: ${error.message}`
792
790
  }],
793
791
  isError: true
794
792
  };
@@ -851,7 +849,7 @@ class FafToolHandler {
851
849
  text: `FAF SCORE: 0/100 (0%) ○ INVALID\n\n` +
852
850
  `\`${fafPath}\` couldn't be parsed as a valid .faf YAML:\n` +
853
851
  ` ${error?.message ?? String(error)}\n\n` +
854
- `Re-run \`faf_init\` to regenerate a valid file.`,
852
+ `Re-run \`faf_init\` with force: true to write a fresh, valid file.`,
855
853
  },
856
854
  ],
857
855
  isError: true,
@@ -868,7 +866,7 @@ class FafToolHandler {
868
866
  // Headline carries both `/100` AND `(%)` so multiple matchers stay happy.
869
867
  let output = `FAF SCORE: ${score}/100 (${score}%) ${tierDisplay}\n` +
870
868
  `${progressBar} ${score}%\n` +
871
- `${result.populated}/${result.total} slots populated` +
869
+ `${result.populated}/${result.active} slots populated` +
872
870
  (nextTierDisplay ? ` · next: ${nextTierDisplay}` : ' · top tier') +
873
871
  `\n\n` +
874
872
  `Scored by faf-cli — the same context your AI reads.`;
@@ -900,13 +898,19 @@ class FafToolHandler {
900
898
  };
901
899
  }
902
900
  async handleFafInit(args) {
903
- // Native implementation - creates project.faf with Pomelli-simple path resolution!
901
+ // faf_init is `faf init`: the same exported faf-cli steps the CLI command
902
+ // runs (assembleFreshFaf detects the folder → writeFaf → readFafRaw →
903
+ // scoreFafYaml), on the folder the path resolves to. Until 3.0.1 this
904
+ // wrote its own legacy template (no format version, `project:` as a
905
+ // plain string) that scored 0% whatever the folder held, failed faf_trust and
906
+ // crashed faf_go. Two steps of `faf init` are not exported by faf-cli
907
+ // 7.12 and are not repeated here: the home/root refusal and the
908
+ // .faf-dna birth record (faf_dna writes that on its first call).
904
909
  try {
905
- // Use smart path resolution (supports "my-app", "~/Projects/my-app", "/full/path")
910
+ // Path resolution unchanged (supports "my-app", "~/Projects/my-app", "/full/path")
906
911
  const userInput = args?.path;
907
912
  const resolution = (0, path_resolver_1.resolveProjectPath)(userInput);
908
913
  const targetDir = resolution.projectPath;
909
- const projectName = resolution.projectName;
910
914
  const fafPath = resolution.fafFilePath;
911
915
  // Ensure project directory exists
912
916
  if (!fs.existsSync(targetDir)) {
@@ -918,57 +922,16 @@ class FafToolHandler {
918
922
  return {
919
923
  content: [{
920
924
  type: 'text',
921
- text: `🚀 Claude FAF Initialization:\n\n⚠️ ${existingFaf.filename} already exists in ${targetDir}\n💡 Use force: true to overwrite`
925
+ text: `🚀 FAF Initialization:\n\n⚠️ ${existingFaf.filename} already exists in ${targetDir}\n💡 Use force: true to overwrite`
922
926
  }]
923
927
  };
924
928
  }
925
- // Check project type with fuzzy detection (Friday Feature!)
926
- const projectDescription = args?.description || '';
927
- // Detect Chrome Extension with fuzzy matching
928
- const chromeDetection = fuzzy_detector_1.FuzzyDetector.detectChromeExtension(projectDescription);
929
- const projectType = fuzzy_detector_1.FuzzyDetector.detectProjectType(projectDescription);
930
- // Build project data with Intel-Friday auto-fill!
931
- let projectData = {
932
- project: projectName,
933
- project_type: projectType,
934
- description: projectDescription,
935
- generated: new Date().toISOString(),
936
- version: version_1.VERSION
937
- };
938
- // Apply Intel-Friday: Auto-fill Chrome Extension slots for 90%+ score!
939
- if (chromeDetection.detected) {
940
- projectData = (0, fuzzy_detector_1.applyIntelFriday)(projectData);
941
- }
942
- // Create enhanced .faf content
943
- const fafContent = `# FAF - Foundational AI Context
944
- project: ${projectData.project}
945
- type: ${projectData.project_type}${chromeDetection.detected ? ' 🎯' : ''}
946
- context: I⚡🍊
947
- generated: ${projectData.generated}
948
- version: ${projectData.version}
949
- ${chromeDetection.corrected ? `# Auto-corrected: "${args?.description}" → "${chromeDetection.corrected}"` : ''}
950
-
951
- # The Formula
952
- human_input: Your project files
953
- multiplier: FAF Context
954
- output: Championship Performance
955
-
956
- # Quick Context
957
- working_directory: ${targetDir}
958
- initialized_by: faf-mcp${projectData._friday_feature ? `\nfriday_feature: ${projectData._friday_feature}` : ''}
959
- vitamin_context: true
960
- faffless: true
961
-
962
- ${chromeDetection.detected ? `# Chrome Extension Auto-Fill (90%+ Score!)
963
- runtime: ${projectData.runtime}
964
- hosting: ${projectData.hosting}
965
- api_type: ${projectData.api_type}
966
- backend: ${projectData.backend}
967
- database: ${projectData.database}
968
- build: ${projectData.build}
969
- package_manager: ${projectData.package_manager}` : ''}
970
- `;
971
- fs.writeFileSync(fafPath, fafContent);
929
+ const { assembleFreshFaf, writeFaf, readFafRaw, scoreFafYaml } = await faf_cli_bridge_js_1.fafCli;
930
+ writeFaf(fafPath, assembleFreshFaf(targetDir));
931
+ const score = scoreFafYaml(readFafRaw(fafPath));
932
+ // Like `cd` + `faf init`: the new project becomes the current one, so the
933
+ // next steps printed below (faf_score, faf_claude, faf_go) act on it.
934
+ this.engineAdapter.setWorkingDirectory(targetDir);
972
935
  // Pomelli-style success confirmation with path resolution info
973
936
  const pathConfirmation = (0, path_resolver_1.formatPathConfirmation)(resolution);
974
937
  const sourceExplanation = resolution.source === 'user-name'
@@ -977,7 +940,7 @@ package_manager: ${projectData.package_manager}` : ''}
977
940
  return {
978
941
  content: [{
979
942
  type: 'text',
980
- 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_sync to create CLAUDE.md\n • Run faf_enhance to improve context`
943
+ 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`
981
944
  }]
982
945
  };
983
946
  }
@@ -985,31 +948,71 @@ package_manager: ${projectData.package_manager}` : ''}
985
948
  return {
986
949
  content: [{
987
950
  type: 'text',
988
- text: `🚀 Claude FAF Initialization:\n\n❌ Error: ${error.message}`
951
+ text: `🚀 FAF Initialization:\n\n❌ Error: ${error.message}`
989
952
  }],
990
953
  isError: true
991
954
  };
992
955
  }
993
956
  }
957
+ /**
958
+ * v3.0: was `this.engineAdapter.callEngine('trust')` — no bundled 'trust'
959
+ * branch exists in engine-adapter.ts, so this fell straight through to
960
+ * the ambient-PATH shell-out fallback (`which faf`, then execAsync).
961
+ * faf-cli itself has no `trust` subcommand at all — on any host where an
962
+ * unrelated `faf`-named binary sits first on PATH (verified live on this
963
+ * machine: Rust "FAFb"), this either hard-errors on an "unrecognized
964
+ * subcommand" or, worse, silently "succeeds" with that tool's own output.
965
+ * Rewired onto the bundled faf-cli bridge — bundled faf-cli only, never
966
+ * ambient PATH — composing `validateFaf` (structural integrity) +
967
+ * `scoreFafYaml` (trust metrics) directly, matching the tool's own
968
+ * description ("structural integrity and field consistency... trust
969
+ * metrics"). No CLI subcommand to shell out to even exists for this.
970
+ */
994
971
  async handleFafTrust(_args) {
995
- const result = await this.engineAdapter.callEngine('trust');
996
- if (!result.success) {
972
+ const cwd = this.getProjectPath();
973
+ const { findFafFile: findFaf, readFaf, readFafRaw, validateFaf, scoreFafYaml } = await faf_cli_bridge_js_1.fafCli;
974
+ const fafPath = findFaf(cwd);
975
+ if (!fafPath) {
976
+ return {
977
+ content: [{
978
+ type: 'text',
979
+ 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.`
980
+ }]
981
+ };
982
+ }
983
+ let raw;
984
+ let data;
985
+ try {
986
+ raw = readFafRaw(fafPath);
987
+ data = readFaf(fafPath);
988
+ }
989
+ catch (error) {
997
990
  return {
998
991
  content: [{
999
992
  type: 'text',
1000
- text: `🔒 Claude FAF Trust Validation:\n\nFailed to check trust: ${result.error}`
993
+ text: `🔒 FAF Trust Validation:\n\n❌ Could not read ${fafPath}: ${error?.message ?? String(error)}`
1001
994
  }],
1002
995
  isError: true
1003
996
  };
1004
997
  }
1005
- const output = typeof result.data === 'string'
1006
- ? result.data
1007
- : result.data?.output || JSON.stringify(result.data, null, 2);
998
+ const validation = validateFaf(data);
999
+ let output = validation.valid
1000
+ ? `✅ Structurally sound — no malformed, missing, or contradictory fields found.`
1001
+ : `❌ ${validation.errors.length} issue${validation.errors.length === 1 ? '' : 's'} found:\n` +
1002
+ validation.errors.map((e) => ` • ${e}`).join('\n');
1003
+ try {
1004
+ const score = scoreFafYaml(raw);
1005
+ output += `\n\n📊 ${score.score}/100 (${score.populated}/${score.active} slots populated) — ${score.tier.name}`;
1006
+ }
1007
+ catch {
1008
+ // Score is a bonus signal here; a validation-only result is still honest.
1009
+ }
1008
1010
  return {
1009
1011
  content: [{
1010
1012
  type: 'text',
1011
- text: `🔒 Claude FAF Trust Validation:\n\n${output}`
1012
- }]
1013
+ text: `🔒 FAF Trust Validation:\n\n${output}`
1014
+ }],
1015
+ isError: !validation.valid
1013
1016
  };
1014
1017
  }
1015
1018
  async handleFafSync(args) {
@@ -1017,12 +1020,14 @@ package_manager: ${projectData.package_manager}` : ''}
1017
1020
  if (args?.path) {
1018
1021
  this.getProjectPath(args.path);
1019
1022
  }
1020
- const result = await this.engineAdapter.callEngine('sync');
1023
+ // `apply` maps to the bundled sync command's --auto; without it the
1024
+ // command only reports what would change.
1025
+ const result = await this.engineAdapter.callEngine('sync', args?.apply ? ['--auto'] : []);
1021
1026
  if (!result.success) {
1022
1027
  return {
1023
1028
  content: [{
1024
1029
  type: 'text',
1025
- text: `🔄 Claude FAF Sync:\n\nFailed to sync: ${result.error}`
1030
+ text: `🔄 FAF Sync:\n\nFailed to sync: ${result.error}`
1026
1031
  }],
1027
1032
  isError: true
1028
1033
  };
@@ -1033,31 +1038,39 @@ package_manager: ${projectData.package_manager}` : ''}
1033
1038
  return {
1034
1039
  content: [{
1035
1040
  type: 'text',
1036
- text: `🔄 Claude FAF Sync:\n\n${output}`
1041
+ text: `🔄 FAF Sync:\n\n${output}`
1037
1042
  }]
1038
1043
  };
1039
1044
  }
1040
- async handleFafBiSync(args) {
1045
+ async handleFafClaude(args) {
1041
1046
  // Set project context if path provided
1042
1047
  if (args?.path) {
1043
1048
  this.getProjectPath(args.path);
1044
1049
  }
1045
- const biSyncArgs = [];
1046
- if (args?.auto) {
1047
- biSyncArgs.push('--auto');
1050
+ // auto / watch / force used to be declared here too; none of them was
1051
+ // ever read by the command, so the schema no longer promises them.
1052
+ const claudeArgs = [];
1053
+ // v3.0 fix: the tool's own schema advertises agents/cursor/gemini/all,
1054
+ // but before 3.0 they were never forwarded to the engine adapter —
1055
+ // the tool silently ignored them and only ever wrote CLAUDE.md.
1056
+ if (args?.agents) {
1057
+ claudeArgs.push('--agents');
1058
+ }
1059
+ if (args?.cursor) {
1060
+ claudeArgs.push('--cursor');
1048
1061
  }
1049
- if (args?.watch) {
1050
- biSyncArgs.push('--watch');
1062
+ if (args?.gemini) {
1063
+ claudeArgs.push('--gemini');
1051
1064
  }
1052
- if (args?.force) {
1053
- biSyncArgs.push('--force');
1065
+ if (args?.all) {
1066
+ claudeArgs.push('--all');
1054
1067
  }
1055
- const result = await this.engineAdapter.callEngine('bi-sync', biSyncArgs);
1068
+ const result = await this.engineAdapter.callEngine('claude', claudeArgs);
1056
1069
  if (!result.success) {
1057
1070
  return {
1058
1071
  content: [{
1059
1072
  type: 'text',
1060
- text: `🔗 Claude FAF Bi-Sync:\n\nFailed to bi-sync: ${result.error}`
1073
+ text: `🔗 FAF → CLAUDE.md:\n\nCould not write CLAUDE.md: ${result.error}`
1061
1074
  }],
1062
1075
  isError: true
1063
1076
  };
@@ -1068,41 +1081,50 @@ package_manager: ${projectData.package_manager}` : ''}
1068
1081
  return {
1069
1082
  content: [{
1070
1083
  type: 'text',
1071
- text: `🔗 Claude FAF Bi-Sync:\n\n${output}`
1084
+ text: `🔗 FAF → CLAUDE.md:\n\n${output}`
1072
1085
  }]
1073
1086
  };
1074
1087
  }
1088
+ /**
1089
+ * v3.0: was `this.engineAdapter.callEngine('clear', clearArgs)` — no
1090
+ * bundled 'clear' branch exists in engine-adapter.ts, so this fell
1091
+ * straight through to the ambient-PATH shell-out fallback. Worse: even
1092
+ * when a real faf-cli WAS first on PATH, its actual `clear` subcommand
1093
+ * only clears `faf-git-*` temp directories from the OS tmpdir (see
1094
+ * ~/FAF/cli/src/commands/clear.ts) — entirely unrelated to this tool's
1095
+ * own cache/todos/backups semantics. There was never a correct backing
1096
+ * implementation for this tool's contract, on any PATH.
1097
+ *
1098
+ * Rewired as a genuinely local operation — no CLI dependency, bundled or
1099
+ * ambient, at all. `cache` clears the one real cache path this codebase
1100
+ * writes (~/.faf-cli-cache, faf-core/utils/technical-credit.ts). The
1101
+ * schema used to advertise `todos` and `backups` as well; no persisted todo
1102
+ * list or backup-file store exists anywhere in faf-mcp, so those flags are
1103
+ * gone rather than answered with a made-up "cleared".
1104
+ */
1075
1105
  async handleFafClear(args) {
1076
- const clearArgs = [];
1077
- if (args?.cache) {
1078
- clearArgs.push('--cache');
1079
- }
1080
- if (args?.todos) {
1081
- clearArgs.push('--todos');
1082
- }
1083
- if (args?.backups) {
1084
- clearArgs.push('--backups');
1085
- }
1086
- if (args?.all || (!args?.cache && !args?.todos && !args?.backups)) {
1087
- clearArgs.push('--all');
1088
- }
1089
- const result = await this.engineAdapter.callEngine('clear', clearArgs);
1090
- if (!result.success) {
1091
- return {
1092
- content: [{
1093
- type: 'text',
1094
- text: `🧹 Claude FAF Clear:\n\nFailed to clear: ${result.error}`
1095
- }],
1096
- isError: true
1097
- };
1106
+ const wantCache = !!args?.cache;
1107
+ const wantAll = !!args?.all || !wantCache;
1108
+ const lines = [];
1109
+ if (wantCache || wantAll) {
1110
+ // HOME/USERPROFILE first (Node's own os.homedir() precedence; Bun's ignores a
1111
+ // runtime override) so a test can redirect the home dir instead of
1112
+ // deleting from the developer's real one.
1113
+ const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
1114
+ const cacheDir = pathModule.join(home, '.faf-cli-cache');
1115
+ const creditCache = pathModule.join(cacheDir, 'technical-credit.json');
1116
+ if (fs.existsSync(creditCache)) {
1117
+ fs.rmSync(creditCache, { force: true });
1118
+ lines.push(`✅ cache: cleared ${creditCache}`);
1119
+ }
1120
+ else {
1121
+ lines.push('✅ cache: nothing to clear');
1122
+ }
1098
1123
  }
1099
- const output = typeof result.data === 'string'
1100
- ? result.data
1101
- : result.data?.output || JSON.stringify(result.data, null, 2);
1102
1124
  return {
1103
1125
  content: [{
1104
1126
  type: 'text',
1105
- text: `🧹 Claude FAF Clear:\n\n${output}`
1127
+ text: `🧹 FAF Clear:\n\n${lines.join('\n')}`
1106
1128
  }]
1107
1129
  };
1108
1130
  }
@@ -1131,7 +1153,7 @@ WHAT IS .FAF?
1131
1153
  • The dot (.) means it's a file format!
1132
1154
 
1133
1155
  🧡 Trust: Context verified
1134
- ⚡️ Speed: Generated in <29ms
1156
+ ⚡️ Speed: Authored in <29ms
1135
1157
  SPEEDY AI you can TRUST!
1136
1158
 
1137
1159
  Version ${packageInfo.version}
@@ -1142,10 +1164,10 @@ Just like JPEG makes images universal,
1142
1164
  HOW IT WORKS:
1143
1165
  1. Drop a file or paste the path
1144
1166
  2. Create .faf (Foundational AI-context Format)
1145
- 3. Talk to Claude to bi-sync it
1167
+ 3. Ask your AI to run faf_claude
1146
1168
  4. You're done⚡
1147
1169
 
1148
- 🩵 You just made Claude Happy
1170
+ 🩵 You just made your AI happy
1149
1171
  🧡⚡️ SPEEDY AI you can TRUST!`;
1150
1172
  return {
1151
1173
  content: [{
@@ -1164,7 +1186,7 @@ WHY: Just like JPEG makes images viewable everywhere,
1164
1186
  .faf makes projects understandable by AI.
1165
1187
 
1166
1188
  HOW: Run 'faf' on any project to create one.
1167
- Run 'faf_score' to check AI-readiness (target: 99%).
1189
+ Run 'faf_score' to check AI-readiness (target: 100%).
1168
1190
 
1169
1191
  REMEMBER: Always use ".faf" with the dot - it's a FORMAT!
1170
1192
 
@@ -1180,18 +1202,15 @@ REMEMBER: Always use ".faf" with the dot - it's a FORMAT!
1180
1202
  try {
1181
1203
  const fs = await import('fs');
1182
1204
  const path = await import('path');
1183
- const { exec } = await import('child_process');
1184
- const { promisify } = await import('util');
1185
- const _execAsync = promisify(exec);
1186
1205
  const cwd = this.engineAdapter.getWorkingDirectory();
1187
1206
  const debugInfo = {
1188
1207
  workingDirectory: cwd,
1189
1208
  canWrite: false,
1190
- fafCliPath: null,
1191
- fafVersion: null,
1209
+ // Every tool runs on the faf-cli bundled in node_modules — report
1210
+ // that, not whatever `which faf` happens to find on PATH.
1211
+ fafVersion: (0, faf_cli_bridge_js_1.bundledFafCliVersion)(),
1192
1212
  permissions: {},
1193
1213
  enginePath: this.engineAdapter.getEnginePath(),
1194
- pathEnv: process.env.PATH?.split(':') || []
1195
1214
  };
1196
1215
  // Check write permissions
1197
1216
  try {
@@ -1203,21 +1222,6 @@ REMEMBER: Always use ".faf" with the dot - it's a FORMAT!
1203
1222
  catch (error) {
1204
1223
  debugInfo.permissions.writeError = error instanceof Error ? error.message : String(error);
1205
1224
  }
1206
- // Check FAF CLI availability using championship auto-detection
1207
- try {
1208
- const cliInfo = this.engineAdapter.getCliInfo();
1209
- if (cliInfo.detected && cliInfo.path) {
1210
- debugInfo.fafCliPath = cliInfo.path;
1211
- debugInfo.fafVersion = cliInfo.version || null;
1212
- }
1213
- else {
1214
- debugInfo.fafCliPath = null;
1215
- debugInfo.fafVersion = null;
1216
- }
1217
- }
1218
- catch (error) {
1219
- debugInfo.permissions.fafError = error instanceof Error ? error.message : String(error);
1220
- }
1221
1225
  // Check for existing FAF file (v1.2.0: project.faf, *.faf, or .faf)
1222
1226
  const fafResult = await (0, faf_file_finder_js_1.findFafFile)(cwd);
1223
1227
  const hasFaf = fafResult !== null;
@@ -1226,15 +1230,12 @@ REMEMBER: Always use ".faf" with the dot - it's a FORMAT!
1226
1230
  📂 Working Directory: ${debugInfo.workingDirectory}
1227
1231
  ✏️ Write Permissions: ${debugInfo.canWrite ? '✅ Yes' : '❌ No'}
1228
1232
  ${debugInfo.permissions.writeError ? ` Error: ${debugInfo.permissions.writeError}\n` : ''}🤖 FAF Engine Path: ${debugInfo.enginePath}
1229
- 🏎️ FAF CLI Path: ${debugInfo.fafCliPath || '❌ Not found'}
1230
- 📋 FAF Version: ${debugInfo.fafVersion || 'Unknown'}
1231
- ${debugInfo.permissions.fafError ? ` FAF Error: ${debugInfo.permissions.fafError}\n` : ''}📄 FAF File: ${hasFaf ? `✅ ${fafResult.filename} exists` : '❌ Not found (run faf_init)'}
1232
- 🛤️ System PATH: ${debugInfo.pathEnv.slice(0, 3).join(', ')}${debugInfo.pathEnv.length > 3 ? '...' : ''}
1233
+ 📋 faf-cli (bundled): ${debugInfo.fafVersion ? `v${debugInfo.fafVersion}` : '❌ not found in node_modules — reinstall faf-mcp'}
1234
+ 📄 FAF File: ${hasFaf ? `✅ ${fafResult.filename} exists` : '❌ Not found (run faf_init)'}
1233
1235
 
1234
1236
  💡 Quick Start:
1235
- 1. If FAF CLI not found: npm install -g faf-cli
1236
- 2. If .faf file missing: use faf_init tool
1237
- 3. For optimization: use faf_enhance tool with model="claude"
1237
+ 1. If .faf file missing: use faf_init or faf_auto
1238
+ 2. To check AI-readiness: use faf_score
1238
1239
  `;
1239
1240
  return {
1240
1241
  content: [{
@@ -1247,7 +1248,7 @@ ${debugInfo.permissions.fafError ? ` FAF Error: ${debugInfo.permissions.fafErr
1247
1248
  return {
1248
1249
  content: [{
1249
1250
  type: 'text',
1250
- text: `🔍 Claude FAF Debug Failed: ${error instanceof Error ? error.message : String(error)}`
1251
+ text: `🔍 faf-mcp Debug Failed: ${error instanceof Error ? error.message : String(error)}`
1251
1252
  }],
1252
1253
  isError: true
1253
1254
  };
@@ -1299,7 +1300,6 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
1299
1300
  **Extensions:**
1300
1301
  - \`new\` - force overwrite existing
1301
1302
  - \`full\` - detailed output
1302
- - \`bi\` - bi-directional sync
1303
1303
 
1304
1304
  ## UX Rules
1305
1305
  1. **Don't offer option menus** - just solve it
@@ -1459,7 +1459,7 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
1459
1459
  }
1460
1460
  }
1461
1461
  // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1462
- // NEW: Human Context Tools (v3.2.0 parity)
1462
+ // Human Context Tools (parity with faf-cli v3.2.0)
1463
1463
  // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1464
1464
  async handleFafReadme(args) {
1465
1465
  try {
@@ -1686,7 +1686,7 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
1686
1686
  }
1687
1687
  // Default: show quality report
1688
1688
  const icons = {
1689
- empty: '⬜', generic: '🟡', good: '🟢', excellent: '💎'
1689
+ empty: '⬜', generic: '◻️', good: '◼️', excellent: '💎'
1690
1690
  };
1691
1691
  let output = `🔍 FAF Human Context Quality\n\n`;
1692
1692
  for (const field of fields) {
@@ -1780,7 +1780,7 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
1780
1780
  // they're SOURCED by Turbo-Cat (faf-cli's separate STACK_INTERVIEW if ever
1781
1781
  // needed), never asked of a human. (Decision: single-source the 8-Q 6Ws
1782
1782
  // Interview, wolfejam 2026-06-10 — language is not on the human side.)
1783
- const { SIX_WS_INTERVIEW } = await faf_cli_bridge_js_1.fafCli;
1783
+ const { SIX_WS_INTERVIEW, isPlaceholder, scoreFafYaml } = await faf_cli_bridge_js_1.fafCli;
1784
1784
  const QUESTION_REGISTRY = Object.fromEntries(SIX_WS_INTERVIEW.map((q) => [q.path, q]));
1785
1785
  // Priority order for questions
1786
1786
  const priorityOrder = [
@@ -1825,16 +1825,18 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
1825
1825
  }
1826
1826
  current[parts[parts.length - 1]] = value;
1827
1827
  };
1828
- // Check if value is empty/placeholder
1829
- const isEmpty = (value) => {
1830
- return value === undefined ||
1831
- value === null ||
1832
- value === '' ||
1833
- value === 'Unknown' ||
1834
- value === 'TBD' ||
1835
- value === 'None' ||
1836
- (typeof value === 'string' && value.toLowerCase().includes('placeholder'));
1837
- };
1828
+ // Empty = what the scorer calls empty. faf-cli's isPlaceholder is the one
1829
+ // rule the kernel scores by; a local list ('TBD', 'Unknown', ...) drifted
1830
+ // from it in both directions and made faf_go's numbers disagree with
1831
+ // faf_score on the same file. slotignored is inactive, never asked.
1832
+ const isEmpty = (value) => isPlaceholder(value) && value !== 'slotignored';
1833
+ // One score function, period: the real scorer on the real bytes.
1834
+ const scoreOf = (raw) => scoreFafYaml(raw).score;
1835
+ const tierLabel = (score) => score >= 100 ? '✪ Trophy — 100% AI-Readiness' :
1836
+ score >= 99 ? '★ Gold' :
1837
+ score >= 95 ? '◆ Silver' :
1838
+ score >= 85 ? '◇ Bronze — production ready' :
1839
+ score >= 70 ? '● Green — solid foundation' : '📈 Keep going!';
1838
1840
  // PHASE 2: Apply answers if provided
1839
1841
  if (args?.answers && typeof args.answers === 'object') {
1840
1842
  const answers = args.answers;
@@ -1845,15 +1847,10 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
1845
1847
  appliedCount++;
1846
1848
  }
1847
1849
  }
1848
- // Write updated file
1850
+ // Write updated file, then score the bytes that were written.
1849
1851
  fs.writeFileSync(fafResult.path, yaml.stringify(fafData), 'utf-8');
1850
- // Calculate new score (simple count-based)
1851
- const totalFields = Object.keys(QUESTION_REGISTRY).length;
1852
- const filledFields = Object.keys(QUESTION_REGISTRY).filter(field => !isEmpty(getNestedValue(fafData, field))).length;
1853
- const newScore = Math.round((filledFields / totalFields) * 100);
1854
- const celebration = newScore >= 100 ? '🏆 GOLD CODE ACHIEVED!' :
1855
- newScore >= 85 ? '🥇 Championship grade!' :
1856
- newScore >= 70 ? '🥈 Great progress!' : '📈 Keep going!';
1852
+ const newScore = scoreOf(fs.readFileSync(fafResult.path, 'utf-8'));
1853
+ const celebration = tierLabel(newScore);
1857
1854
  return {
1858
1855
  content: [{
1859
1856
  type: 'text',
@@ -1864,12 +1861,10 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
1864
1861
  // PHASE 1: Analyze and return questions — the canonical 6Ws (SIX_WS_INTERVIEW),
1865
1862
  // scoped to slots that are empty AND active (slotignored is never asked).
1866
1863
  const missingFields = SIX_WS_INTERVIEW
1867
- .filter((q) => { const v = getNestedValue(fafData, q.path); return v !== 'slotignored' && isEmpty(v); })
1864
+ .filter((q) => isEmpty(getNestedValue(fafData, q.path)))
1868
1865
  .map((q) => q.path);
1869
- // Calculate current score
1870
- const totalFields = Object.keys(QUESTION_REGISTRY).length;
1871
- const filledFields = totalFields - missingFields.length;
1872
- const currentScore = Math.round((filledFields / totalFields) * 100);
1866
+ // Current score — the same number faf_score reports for this file.
1867
+ const currentScore = scoreOf(fafContent);
1873
1868
  // Already at 100%?
1874
1869
  if (currentScore >= 100) {
1875
1870
  return {
@@ -1878,7 +1873,7 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
1878
1873
  text: JSON.stringify({
1879
1874
  complete: true,
1880
1875
  score: 100,
1881
- message: '🏆 GOLD CODE ACHIEVED! Your project has 100% AI-Readiness.',
1876
+ message: '✪ GOLD CODE ACHIEVED! Your project has 100% AI-Readiness.',
1882
1877
  context: 'faf_go'
1883
1878
  }, null, 2)
1884
1879
  }]
@@ -1892,7 +1887,7 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
1892
1887
  text: JSON.stringify({
1893
1888
  score: currentScore,
1894
1889
  message: `Score is ${currentScore}%. All fields filled but content may need enhancement.`,
1895
- suggestion: 'Use faf_enhance to improve content quality.',
1890
+ suggestion: 'Use faf_go or faf_human_add to improve content quality.',
1896
1891
  context: 'faf_go'
1897
1892
  }, null, 2)
1898
1893
  }]
@@ -1945,120 +1940,52 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
1945
1940
  /**
1946
1941
  * faf_auto - ONE COMMAND TO RULE THEM ALL
1947
1942
  * Zero to Championship in one command
1948
- * Runs: init + formats + sync + bi-sync + score
1943
+ *
1944
+ * v3.0: composes faf-cli's own `faf auto` chain — `updateExistingFaf` on an
1945
+ * existing file, `assembleFreshFaf` on a new one, `writeFaf` for the bytes,
1946
+ * `renderClaudeMd`/`writeClaudeMd` for CLAUDE.md — plus the same
1947
+ * `scoreFafYaml` faf_score reads. Previously this handler ran a bespoke
1948
+ * bootstrap template plus a local `calculateSimpleScore` pseudo-scorer,
1949
+ * which could (and did) report a different score than faf_score for the
1950
+ * identical file in the same server run. NEVER reimplement detection or
1951
+ * scoring here — compose faf-cli, exactly like faf_score/faf_formats do.
1949
1952
  */
1950
1953
  async handleFafAuto(args) {
1951
1954
  const startTime = Date.now();
1952
1955
  const cwd = this.getProjectPath(args?.path);
1953
- const yaml = await import('yaml');
1954
1956
  const path = await import('path');
1955
1957
  try {
1956
1958
  const steps = [];
1957
- let currentScore = 0;
1958
- // Step 1: Check/Create .faf file
1959
- const fafResult = await (0, faf_file_finder_js_1.findFafFile)(cwd);
1959
+ const { findFafFile: findFaf, readFaf, readFafRaw, scoreFafYaml, assembleFreshFaf, updateExistingFaf, writeFaf, renderClaudeMd, writeClaudeMd, } = await faf_cli_bridge_js_1.fafCli;
1960
+ // Step 1: locate any existing .faf and its real "before" score.
1961
+ const existingPath = findFaf(cwd);
1962
+ const beforeScore = existingPath ? scoreFafYaml(readFafRaw(existingPath)).score : 0;
1963
+ steps.push(existingPath ? `✅ Found ${path.basename(existingPath)}` : '✅ Will create project.faf');
1964
+ // Step 2: exactly what faf-cli's own `faf auto` does — an existing file
1965
+ // is updated with updateExistingFaf (existing values win; interrogated →
1966
+ // detected → Turbo-Cat → Relentless fill the empties), a new one is
1967
+ // assembled with assembleFreshFaf — and written with faf-cli's writer,
1968
+ // so the bytes match `faf auto` (no runtime `_meta` block, same layout).
1960
1969
  let fafPath;
1961
- if (!fafResult) {
1962
- // Create .faf file
1963
- const projectName = path.basename(cwd);
1964
- fafPath = path.join(cwd, 'project.faf');
1965
- const initFafContent = `# FAF - Foundational AI Context
1966
- project: ${projectName}
1967
- type: auto-detected
1968
- context: I⚡🍊
1969
- generated: ${new Date().toISOString()}
1970
- version: ${version_1.VERSION}
1971
-
1972
- # Quick Context
1973
- working_directory: ${cwd}
1974
- initialized_by: faf-mcp-auto
1975
- vitamin_context: true
1976
- faffless: true
1977
- `;
1978
- fs.writeFileSync(fafPath, initFafContent);
1979
- steps.push('✅ Created project.faf');
1970
+ if (existingPath) {
1971
+ fafPath = existingPath;
1972
+ writeFaf(fafPath, updateExistingFaf(cwd, readFaf(existingPath)));
1973
+ steps.push('✅ Merged fresh facts into existing project.faf (existing values preserved)');
1980
1974
  }
1981
1975
  else {
1982
- fafPath = fafResult.path;
1983
- steps.push(`✅ Found ${fafResult.filename}`);
1984
- }
1985
- // Get initial score
1986
- const fafContent = fs.readFileSync(fafPath, 'utf-8');
1987
- const fafData = yaml.parse(fafContent) || {};
1988
- currentScore = this.calculateSimpleScore(fafData);
1989
- // Step 2: Run TURBO-CAT format discovery — composed from faf-cli's engine.
1990
- const formatsResult = await (0, turbocat_bridge_js_1.composedTurboCat)(cwd);
1991
- if (formatsResult && formatsResult.discoveredFormats.length > 0) {
1992
- // Apply slot fills to .faf. turboCatSlots routes them into the correct
1993
- // .faf sections; fill the stack section (main_language lives under
1994
- // project, which is a string here) — clean keys, no noise.
1995
- const slots = await (0, turbocat_bridge_js_1.composedTurboCatSlots)(cwd);
1996
- if (!fafData.stack)
1997
- fafData.stack = {};
1998
- for (const [key, value] of Object.entries(slots?.stack ?? {})) {
1999
- if (!fafData.stack[key] || fafData.stack[key] === 'None') {
2000
- fafData.stack[key] = value;
2001
- }
2002
- }
2003
- if (formatsResult.stackSignature) {
2004
- fafData.stack_signature = formatsResult.stackSignature;
2005
- }
2006
- fs.writeFileSync(fafPath, yaml.stringify(fafData), 'utf-8');
2007
- steps.push(`✅ TURBO-CAT discovered ${formatsResult.discoveredFormats.length} formats`);
2008
- }
2009
- else {
2010
- steps.push('⚠️ No additional formats detected');
2011
- }
2012
- // Step 3: Extract human context from README
2013
- const readmePath = path.join(cwd, 'README.md');
2014
- if (fs.existsSync(readmePath)) {
2015
- const readmeContent = fs.readFileSync(readmePath, 'utf-8');
2016
- const extracted = this.extractSixWsFromReadme(readmeContent);
2017
- if (!fafData.human_context)
2018
- fafData.human_context = {};
2019
- let extractedCount = 0;
2020
- for (const [field, value] of Object.entries(extracted)) {
2021
- if (value && !fafData.human_context[field]) {
2022
- fafData.human_context[field] = value;
2023
- extractedCount++;
2024
- }
2025
- }
2026
- if (extractedCount > 0) {
2027
- fs.writeFileSync(fafPath, yaml.stringify(fafData), 'utf-8');
2028
- steps.push(`✅ Extracted ${extractedCount} human context fields from README`);
2029
- }
2030
- }
2031
- // Step 4: Create/Update CLAUDE.md (bi-sync)
2032
- const claudePath = path.join(cwd, 'CLAUDE.md');
2033
- if (!fs.existsSync(claudePath)) {
2034
- const claudeContent = `# 🏎️ CLAUDE.md - AI Telemetry Link
2035
-
2036
- ## Project: ${fafData.project || path.basename(cwd)}
2037
- **Championship-Grade Project DNA Foundation**
2038
-
2039
- ### 🎯 Project Mission
2040
- ${fafData.human_context?.why || fafData.project?.goal || 'AI-ready project context'}
2041
-
2042
- ### 🏗️ Architecture Overview
2043
- ${fafData.stack_signature || 'Auto-detected stack'}
2044
-
2045
- ---
2046
-
2047
- **STATUS: BI-SYNC ACTIVE 🔗**
2048
- *Last Sync: ${new Date().toISOString()}*
2049
- *Sync Engine: FAF Auto*
2050
- `;
2051
- fs.writeFileSync(claudePath, claudeContent);
2052
- steps.push('✅ Created CLAUDE.md');
2053
- }
2054
- else {
2055
- steps.push('✅ CLAUDE.md already exists');
1976
+ fafPath = path.join(cwd, 'project.faf');
1977
+ writeFaf(fafPath, assembleFreshFaf(cwd));
1978
+ steps.push('✅ Created project.faf');
2056
1979
  }
2057
- // Step 5: Calculate final score
2058
- const updatedContent = fs.readFileSync(fafPath, 'utf-8');
2059
- const updatedData = yaml.parse(updatedContent) || {};
2060
- const newScore = this.calculateSimpleScore(updatedData);
2061
- const scoreDelta = newScore - currentScore;
1980
+ // Step 3: CLAUDE.md — faf-cli's render of the file just written (the
1981
+ // same bytes `faf sync` writes), injected non-destructively.
1982
+ const claudeExisted = fs.existsSync(path.join(cwd, 'CLAUDE.md'));
1983
+ writeClaudeMd(cwd, renderClaudeMd(readFaf(fafPath)));
1984
+ steps.push(claudeExisted ? '✅ Updated CLAUDE.md (faf-managed block)' : '✅ Created CLAUDE.md');
1985
+ // Step 4: final score — same real scorer faf_score reads, on the file
1986
+ // just written. By construction this can never disagree with faf_score.
1987
+ const newScore = scoreFafYaml(readFafRaw(fafPath)).score;
1988
+ const scoreDelta = newScore - beforeScore;
2062
1989
  // Calculate elapsed time
2063
1990
  const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
2064
1991
  // Format output
@@ -2068,21 +1995,27 @@ ${fafData.stack_signature || 'Auto-detected stack'}
2068
1995
  output += steps.join('\n') + '\n\n';
2069
1996
  output += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
2070
1997
  output += `⏱️ Completed in ${elapsed}s\n`;
2071
- output += `📊 Before: ${currentScore}% | After: ${newScore}% ${deltaDisplay}\n`;
1998
+ output += `📊 Before: ${beforeScore}% | After: ${newScore}% ${deltaDisplay}\n`;
2072
1999
  output += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n`;
2073
- if (newScore >= 99) {
2074
- output += `🏆 CHAMPIONSHIP ACHIEVED! Your AI has complete context.\n`;
2000
+ if (newScore >= 100) {
2001
+ output += `✪ TROPHY — 100% AI-Readiness. Your AI has complete context.\n`;
2002
+ }
2003
+ else if (newScore >= 99) {
2004
+ output += `★ Gold — 1% to Trophy. Run faf_go to finish.\n`;
2005
+ }
2006
+ else if (newScore >= 95) {
2007
+ output += `◆ Silver — ${100 - newScore}% to Trophy.\n`;
2075
2008
  }
2076
2009
  else if (newScore >= 85) {
2077
- output += `🥇 Elite level! ${100 - newScore}% to perfection.\n`;
2010
+ output += `◇ Bronze — production ready. ${100 - newScore}% to Trophy.\n`;
2078
2011
  }
2079
2012
  else if (newScore >= 70) {
2080
- output += `🥈 Great progress! Run faf_go to reach championship.\n`;
2013
+ output += `● Green — solid foundation. Run faf_go to climb tiers.\n`;
2081
2014
  }
2082
2015
  else {
2083
2016
  output += `🚀 Good start! Run faf_go for guided improvement.\n`;
2084
2017
  }
2085
- output += `\n💡 Next: faf_score --details | faf_go | faf_enhance`;
2018
+ output += `\n💡 Next: faf_score | faf_go | faf_claude`;
2086
2019
  return { content: [{ type: 'text', text: output }] };
2087
2020
  }
2088
2021
  catch (error) {
@@ -2094,7 +2027,7 @@ ${fafData.stack_signature || 'Auto-detected stack'}
2094
2027
  }
2095
2028
  /**
2096
2029
  * faf_dna - Show your FAF DNA journey
2097
- * Displays evolution from birth to current (22% → 85% → 99%)
2030
+ * Displays the score evolution from birth to today (e.g. 22% → 85% → 100%)
2098
2031
  */
2099
2032
  async handleFafDna(args) {
2100
2033
  const cwd = this.getProjectPath(args?.path);
@@ -2113,11 +2046,10 @@ ${fafData.stack_signature || 'Auto-detected stack'}
2113
2046
  }]
2114
2047
  };
2115
2048
  }
2116
- // .faf exists but no DNA - create initial DNA
2117
- const yaml = await import('yaml');
2118
- const fafContent = fs.readFileSync(fafResult.path, 'utf-8');
2119
- const fafData = yaml.parse(fafContent) || {};
2120
- const currentScore = this.calculateSimpleScore(fafData);
2049
+ // .faf exists but no DNA - create initial DNA. Real scorer — same
2050
+ // one faf_score reads — never a local pseudo-score (v3.0 fix).
2051
+ const { readFafRaw, scoreFafYaml } = await faf_cli_bridge_js_1.fafCli;
2052
+ const currentScore = scoreFafYaml(readFafRaw(fafResult.path)).score;
2121
2053
  const dna = {
2122
2054
  birthCertificate: {
2123
2055
  born: new Date().toISOString(),
@@ -2149,8 +2081,24 @@ ${fafData.stack_signature || 'Auto-detected stack'}
2149
2081
  const dna = JSON.parse(dnaContent);
2150
2082
  // Build journey string
2151
2083
  const birthScore = dna.birthCertificate?.birthDNA || 0;
2152
- const currentScore = dna.current?.score || 0;
2084
+ let currentScore = dna.current?.score || 0;
2153
2085
  const milestones = dna.milestones || [];
2086
+ dna.milestones = milestones;
2087
+ // "Current" is the live score of the file on disk, not whatever was
2088
+ // stored last time. Nothing else records growth into .faf-dna, so this
2089
+ // is where the journey gets its next point.
2090
+ const fafResult = await (0, faf_file_finder_js_1.findFafFile)(cwd);
2091
+ if (fafResult) {
2092
+ const { readFafRaw, scoreFafYaml } = await faf_cli_bridge_js_1.fafCli;
2093
+ const live = scoreFafYaml(readFafRaw(fafResult.path)).score;
2094
+ if (live !== currentScore) {
2095
+ const now = new Date().toISOString();
2096
+ dna.current = { ...(dna.current ?? {}), score: live, lastSync: now };
2097
+ milestones.push({ type: 'sync', score: live, date: now, version: dna.current.version ?? 'v1.0.0' });
2098
+ fs.writeFileSync(dnaPath, JSON.stringify(dna, null, 2));
2099
+ currentScore = live;
2100
+ }
2101
+ }
2154
2102
  // Find key milestones
2155
2103
  const _birth = milestones.find((m) => m.type === 'birth');
2156
2104
  const peak = milestones.find((m) => m.type === 'peak');
@@ -2192,8 +2140,8 @@ ${fafData.stack_signature || 'Auto-detected stack'}
2192
2140
  }
2193
2141
  output += `\n🧬 MILESTONES\n`;
2194
2142
  const milestoneIcons = {
2195
- birth: '🐣', first_save: '💾', doubled: '2️⃣',
2196
- championship: '🏆', elite: '⭐', peak: '🏔️', perfect: '💎'
2143
+ birth: '🐣', first_save: '💾', doubled: '2️⃣', sync: '🔄',
2144
+ championship: '✪', elite: '⭐', peak: '🏔️', perfect: '💎'
2197
2145
  };
2198
2146
  for (const m of milestones) {
2199
2147
  const icon = milestoneIcons[m.type] || '📍';
@@ -2242,7 +2190,7 @@ ${fafData.stack_signature || 'Auto-detected stack'}
2242
2190
  };
2243
2191
  }
2244
2192
  // Format human-readable output
2245
- let output = `😽 TURBO-CAT™ Format Discovery v2.0.0\n`;
2193
+ let output = `😽 TURBO-CAT™ Format Discovery\n`;
2246
2194
  output += `═══════════════════════════════════════════════════\n\n`;
2247
2195
  output += `✅ Found ${analysis.discoveredFormats.length} formats in ${elapsed}ms!\n\n`;
2248
2196
  output += `📋 Discovered Formats (A-Z):\n`;
@@ -2251,7 +2199,7 @@ ${fafData.stack_signature || 'Auto-detected stack'}
2251
2199
  output += ` ✅ ${format.fileName}\n`;
2252
2200
  }
2253
2201
  output += `\n💡 Stack Signature: ${analysis.stackSignature}\n`;
2254
- output += `🏆 Intelligence Score: ${analysis.totalIntelligenceScore}\n\n`;
2202
+ output += `🧠 Intelligence Score: ${analysis.totalIntelligenceScore}\n\n`;
2255
2203
  if (Object.keys(analysis.slotFillRecommendations).length > 0) {
2256
2204
  output += `📊 Recommended Slot Fills:\n`;
2257
2205
  for (const [key, value] of Object.entries(analysis.slotFillRecommendations)) {
@@ -2271,33 +2219,6 @@ ${fafData.stack_signature || 'Auto-detected stack'}
2271
2219
  }
2272
2220
  }
2273
2221
  /**
2274
- * Internal helper: Calculate simple score from .faf data
2275
- */
2276
- calculateSimpleScore(fafData) {
2277
- let score = 0;
2278
- const maxScore = 100;
2279
- // Project section (30 points)
2280
- if (fafData.project)
2281
- score += 15;
2282
- if (fafData.project?.goal || fafData.description)
2283
- score += 15;
2284
- // Human context (30 points)
2285
- const humanContext = fafData.human_context || {};
2286
- const wFields = ['who', 'what', 'why', 'where', 'when', 'how'];
2287
- const filledW = wFields.filter(f => humanContext[f] && humanContext[f] !== 'null').length;
2288
- score += Math.round((filledW / wFields.length) * 30);
2289
- // Stack section (20 points)
2290
- const stack = fafData.stack || {};
2291
- const stackFields = ['frontend', 'backend', 'database', 'hosting', 'build'];
2292
- const filledStack = stackFields.filter(f => stack[f] && stack[f] !== 'None').length;
2293
- score += Math.round((filledStack / stackFields.length) * 20);
2294
- // Files exist bonus (20 points)
2295
- if (fafData.initialized_by || fafData.generated)
2296
- score += 10;
2297
- if (fafData.stack_signature)
2298
- score += 10;
2299
- return Math.min(score, maxScore);
2300
- }
2301
2222
  /**
2302
2223
  * faf_quick - Lightning-fast .faf creation
2303
2224
  * One-liner format: "name, description, language, framework, hosting"
@@ -2357,7 +2278,7 @@ Example: "my-app, e-commerce platform"`
2357
2278
 
2358
2279
  ⚠️ project.faf already exists at: ${fafPath}
2359
2280
 
2360
- Use force: true to overwrite, or use faf_enhance to modify.`
2281
+ Use force: true to overwrite, or use faf_go / faf_human_add to modify.`
2361
2282
  }]
2362
2283
  };
2363
2284
  }
@@ -2394,7 +2315,6 @@ Use force: true to overwrite, or use faf_enhance to modify.`
2394
2315
  output += `✅ Created: ${fafPath}\n\n`;
2395
2316
  output += `Next steps:\n`;
2396
2317
  output += ` • faf_score - Check AI-readiness\n`;
2397
- output += ` • faf_enhance - Improve context\n`;
2398
2318
  output += ` • faf_go - Guided interview to 100%`;
2399
2319
  return { content: [{ type: 'text', text: output }] };
2400
2320
  }
@@ -2488,7 +2408,7 @@ Use force: true to overwrite, or use faf_enhance to modify.`
2488
2408
  results.push({
2489
2409
  status: 'error',
2490
2410
  message: '.faf file is empty',
2491
- fix: 'Run: faf_init with force option to regenerate'
2411
+ fix: 'Run: faf_init with force option to rewrite it'
2492
2412
  });
2493
2413
  }
2494
2414
  else {
@@ -2502,7 +2422,7 @@ Use force: true to overwrite, or use faf_enhance to modify.`
2502
2422
  results.push({
2503
2423
  status: 'warning',
2504
2424
  message: `Missing important fields: ${missingFields.join(', ')}`,
2505
- fix: 'Run: faf_enhance or faf_go to add missing info'
2425
+ fix: 'Run: faf_go to add missing info'
2506
2426
  });
2507
2427
  }
2508
2428
  else {
@@ -2511,26 +2431,31 @@ Use force: true to overwrite, or use faf_enhance to modify.`
2511
2431
  message: '.faf structure is valid'
2512
2432
  });
2513
2433
  }
2514
- // Check 4: Score
2515
- const score = this.calculateSimpleScore(fafData);
2434
+ // Check 4: Score — real scorer (same one faf_score reads), never
2435
+ // a local pseudo-score (v3.0 fix). Slot breakdown included so
2436
+ // faf_doctor's "score could be better" fix advice is actionable.
2437
+ const { scoreFafYaml } = await faf_cli_bridge_js_1.fafCli;
2438
+ const scoreResult = scoreFafYaml(content);
2439
+ const score = scoreResult.score;
2440
+ const slotSummary = `${scoreResult.populated}/${scoreResult.active} slots populated`;
2516
2441
  if (score < 30) {
2517
2442
  results.push({
2518
2443
  status: 'error',
2519
- message: `Score too low: ${score}%`,
2520
- fix: 'Run: faf_enhance or faf_go to improve context'
2444
+ message: `Score too low: ${score}% (${slotSummary})`,
2445
+ fix: 'Run: faf_go to improve context'
2521
2446
  });
2522
2447
  }
2523
2448
  else if (score < 70) {
2524
2449
  results.push({
2525
2450
  status: 'warning',
2526
- message: `Score could be better: ${score}%`,
2451
+ message: `Score could be better: ${score}% (${slotSummary})`,
2527
2452
  fix: 'Target 70%+ for championship AI context'
2528
2453
  });
2529
2454
  }
2530
2455
  else {
2531
2456
  results.push({
2532
2457
  status: 'ok',
2533
- message: `Great score: ${score}%`
2458
+ message: `Great score: ${score}% (${slotSummary})`
2534
2459
  });
2535
2460
  }
2536
2461
  }
@@ -2539,7 +2464,7 @@ Use force: true to overwrite, or use faf_enhance to modify.`
2539
2464
  results.push({
2540
2465
  status: 'error',
2541
2466
  message: '.faf file is corrupted or invalid YAML',
2542
- fix: 'Run: faf_init with force option to regenerate'
2467
+ fix: 'Run: faf_init with force option to rewrite it'
2543
2468
  });
2544
2469
  }
2545
2470
  }
@@ -2549,13 +2474,13 @@ Use force: true to overwrite, or use faf_enhance to modify.`
2549
2474
  results.push({
2550
2475
  status: 'warning',
2551
2476
  message: 'No CLAUDE.md file',
2552
- fix: 'Run: faf_auto or faf_bi_sync to create bi-directional sync'
2477
+ fix: 'Run: faf_auto or faf_claude to write CLAUDE.md'
2553
2478
  });
2554
2479
  }
2555
2480
  else {
2556
2481
  results.push({
2557
2482
  status: 'ok',
2558
- message: 'CLAUDE.md found (bi-sync ready)'
2483
+ message: 'CLAUDE.md found'
2559
2484
  });
2560
2485
  }
2561
2486
  // Check 6: Project detection
@@ -2613,7 +2538,7 @@ Use force: true to overwrite, or use faf_enhance to modify.`
2613
2538
  }
2614
2539
  output += `\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n`;
2615
2540
  if (!hasErrors && !hasWarnings) {
2616
- output += `🏆 Perfect health! Your FAF setup is championship-ready!`;
2541
+ output += `✅ Perfect health! Your FAF setup is championship-ready!`;
2617
2542
  }
2618
2543
  else if (!hasErrors) {
2619
2544
  output += `🎯 Good health with minor improvements suggested.`;
@@ -2631,7 +2556,7 @@ Use force: true to overwrite, or use faf_enhance to modify.`
2631
2556
  }
2632
2557
  }
2633
2558
  // ============================================================================
2634
- // v4.5.0 INTEROP HANDLERS
2559
+ // INTEROP HANDLERS (faf-mcp 2.0.0; ported from faf-cli 4.5.0 via claude-faf-mcp 4.5.0)
2635
2560
  // ============================================================================
2636
2561
  async handleFafAgents(args) {
2637
2562
  const cwd = this.getProjectPath(args?.path);
@@ -2769,7 +2694,7 @@ Use force: true to overwrite, or use faf_enhance to modify.`
2769
2694
  let output = `GitHub Context:\n\n✅ ${data?.message || 'Done'}\n⏱️ ${result.duration}ms`;
2770
2695
  // Include generated .faf content if no output path (preview mode)
2771
2696
  if (!outputPath && data?.data?.fafContent) {
2772
- output += `\n\n--- Generated project.faf ---\n${data.data.fafContent}`;
2697
+ output += `\n\n--- Authored project.faf ---\n${data.data.fafContent}`;
2773
2698
  }
2774
2699
  return {
2775
2700
  content: [{ type: 'text', text: output }]