faf-mcp 2.3.1 → 3.0.0

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 (177) hide show
  1. package/CHANGELOG.md +26 -3
  2. package/CLAUDE.md +6 -6
  3. package/README.md +71 -72
  4. package/dist/src/faf-core/commands/bi-sync.d.ts +1 -3
  5. package/dist/src/faf-core/commands/bi-sync.js +21 -64
  6. package/dist/src/faf-core/commands/bi-sync.js.map +1 -1
  7. package/dist/src/faf-core/commands/sync.js.map +1 -1
  8. package/dist/src/faf-core/engines/dependency-tsa.js.map +1 -1
  9. package/dist/src/faf-core/fix-once/colors.js +1 -1
  10. package/dist/src/faf-core/fix-once/colors.js.map +1 -1
  11. package/dist/src/faf-core/parsers/agents-parser.d.ts +15 -0
  12. package/dist/src/faf-core/parsers/agents-parser.js +29 -123
  13. package/dist/src/faf-core/parsers/agents-parser.js.map +1 -1
  14. package/dist/src/faf-core/parsers/conductor-parser.js +1 -1
  15. package/dist/src/faf-core/parsers/conductor-parser.js.map +1 -1
  16. package/dist/src/faf-core/parsers/cursorrules-parser.d.ts +15 -0
  17. package/dist/src/faf-core/parsers/cursorrules-parser.js +54 -111
  18. package/dist/src/faf-core/parsers/cursorrules-parser.js.map +1 -1
  19. package/dist/src/faf-core/parsers/faf-git-generator.js +0 -1
  20. package/dist/src/faf-core/parsers/faf-git-generator.js.map +1 -1
  21. package/dist/src/faf-core/parsers/gemini-parser.d.ts +10 -0
  22. package/dist/src/faf-core/parsers/gemini-parser.js +19 -77
  23. package/dist/src/faf-core/parsers/gemini-parser.js.map +1 -1
  24. package/dist/src/handlers/engine-adapter.d.ts +13 -12
  25. package/dist/src/handlers/engine-adapter.js +103 -682
  26. package/dist/src/handlers/engine-adapter.js.map +1 -1
  27. package/dist/src/handlers/resources.d.ts +19 -1
  28. package/dist/src/handlers/resources.js +108 -23
  29. package/dist/src/handlers/resources.js.map +1 -1
  30. package/dist/src/handlers/skills.d.ts +70 -0
  31. package/dist/src/handlers/skills.js +174 -0
  32. package/dist/src/handlers/skills.js.map +1 -0
  33. package/dist/src/handlers/tools.d.ts +41 -5
  34. package/dist/src/handlers/tools.js +213 -258
  35. package/dist/src/handlers/tools.js.map +1 -1
  36. package/dist/src/server.d.ts +4 -0
  37. package/dist/src/server.js +51 -6
  38. package/dist/src/server.js.map +1 -1
  39. package/dist/src/utils/faf-cli-bridge.d.ts +2 -0
  40. package/dist/src/utils/faf-cli-bridge.js +16 -0
  41. package/dist/src/utils/faf-cli-bridge.js.map +1 -1
  42. package/package.json +16 -13
  43. package/project.faf +11 -6
  44. package/scripts/check-engines.mjs +53 -0
  45. package/scripts/sync-version.js +33 -19
  46. package/skills/faf-ide/SKILL.md +40 -0
  47. package/dist/src/config/visibility.d.ts +0 -41
  48. package/dist/src/config/visibility.js +0 -158
  49. package/dist/src/config/visibility.js.map +0 -1
  50. package/dist/src/faf-core/commands/audit.d.ts +0 -21
  51. package/dist/src/faf-core/commands/audit.js +0 -83
  52. package/dist/src/faf-core/commands/audit.js.map +0 -1
  53. package/dist/src/faf-core/commands/auto.d.ts +0 -25
  54. package/dist/src/faf-core/commands/auto.js +0 -74
  55. package/dist/src/faf-core/commands/auto.js.map +0 -1
  56. package/dist/src/faf-core/commands/doctor.d.ts +0 -17
  57. package/dist/src/faf-core/commands/doctor.js +0 -198
  58. package/dist/src/faf-core/commands/doctor.js.map +0 -1
  59. package/dist/src/faf-core/commands/enhance.d.ts +0 -46
  60. package/dist/src/faf-core/commands/enhance.js +0 -360
  61. package/dist/src/faf-core/commands/enhance.js.map +0 -1
  62. package/dist/src/faf-core/commands/formats.d.ts +0 -22
  63. package/dist/src/faf-core/commands/formats.js +0 -117
  64. package/dist/src/faf-core/commands/formats.js.map +0 -1
  65. package/dist/src/faf-core/commands/human.d.ts +0 -27
  66. package/dist/src/faf-core/commands/human.js +0 -183
  67. package/dist/src/faf-core/commands/human.js.map +0 -1
  68. package/dist/src/faf-core/commands/init.d.ts +0 -26
  69. package/dist/src/faf-core/commands/init.js +0 -114
  70. package/dist/src/faf-core/commands/init.js.map +0 -1
  71. package/dist/src/faf-core/commands/innit.d.ts +0 -7
  72. package/dist/src/faf-core/commands/innit.js +0 -13
  73. package/dist/src/faf-core/commands/innit.js.map +0 -1
  74. package/dist/src/faf-core/commands/migrate.d.ts +0 -15
  75. package/dist/src/faf-core/commands/migrate.js +0 -86
  76. package/dist/src/faf-core/commands/migrate.js.map +0 -1
  77. package/dist/src/faf-core/commands/quick.d.ts +0 -16
  78. package/dist/src/faf-core/commands/quick.js +0 -184
  79. package/dist/src/faf-core/commands/quick.js.map +0 -1
  80. package/dist/src/faf-core/commands/readme.d.ts +0 -47
  81. package/dist/src/faf-core/commands/readme.js +0 -361
  82. package/dist/src/faf-core/commands/readme.js.map +0 -1
  83. package/dist/src/faf-core/commands/score.d.ts +0 -47
  84. package/dist/src/faf-core/commands/score.js +0 -49
  85. package/dist/src/faf-core/commands/score.js.map +0 -1
  86. package/dist/src/faf-core/commands/update.d.ts +0 -12
  87. package/dist/src/faf-core/commands/update.js +0 -46
  88. package/dist/src/faf-core/commands/update.js.map +0 -1
  89. package/dist/src/faf-core/commands/validate.d.ts +0 -21
  90. package/dist/src/faf-core/commands/validate.js +0 -81
  91. package/dist/src/faf-core/commands/validate.js.map +0 -1
  92. package/dist/src/faf-core/compiler/faf-compiler.d.ts +0 -138
  93. package/dist/src/faf-core/compiler/faf-compiler.js +0 -1362
  94. package/dist/src/faf-core/compiler/faf-compiler.js.map +0 -1
  95. package/dist/src/faf-core/engines/fab-formats-processor.d.ts +0 -166
  96. package/dist/src/faf-core/engines/fab-formats-processor.js +0 -1274
  97. package/dist/src/faf-core/engines/fab-formats-processor.js.map +0 -1
  98. package/dist/src/faf-core/engines/faf-dna.d.ts +0 -159
  99. package/dist/src/faf-core/engines/faf-dna.js +0 -555
  100. package/dist/src/faf-core/engines/faf-dna.js.map +0 -1
  101. package/dist/src/faf-core/engines/relentless-context-extractor.d.ts +0 -100
  102. package/dist/src/faf-core/engines/relentless-context-extractor.js +0 -625
  103. package/dist/src/faf-core/engines/relentless-context-extractor.js.map +0 -1
  104. package/dist/src/faf-core/fix-once/types.d.ts +0 -257
  105. package/dist/src/faf-core/fix-once/types.js +0 -26
  106. package/dist/src/faf-core/fix-once/types.js.map +0 -1
  107. package/dist/src/faf-core/generators/faf-generator-championship.d.ts +0 -16
  108. package/dist/src/faf-core/generators/faf-generator-championship.js +0 -463
  109. package/dist/src/faf-core/generators/faf-generator-championship.js.map +0 -1
  110. package/dist/src/faf-core/inject.d.ts +0 -19
  111. package/dist/src/faf-core/inject.js +0 -57
  112. package/dist/src/faf-core/inject.js.map +0 -1
  113. package/dist/src/faf-core/parsers/index.d.ts +0 -12
  114. package/dist/src/faf-core/parsers/index.js +0 -29
  115. package/dist/src/faf-core/parsers/index.js.map +0 -1
  116. package/dist/src/faf-core/utils/balance-visualizer.d.ts +0 -37
  117. package/dist/src/faf-core/utils/balance-visualizer.js +0 -197
  118. package/dist/src/faf-core/utils/balance-visualizer.js.map +0 -1
  119. package/dist/src/faf-core/utils/championship-style.d.ts +0 -109
  120. package/dist/src/faf-core/utils/championship-style.js +0 -219
  121. package/dist/src/faf-core/utils/championship-style.js.map +0 -1
  122. package/dist/src/faf-core/utils/chrome-extension-detector.d.ts +0 -73
  123. package/dist/src/faf-core/utils/chrome-extension-detector.js +0 -268
  124. package/dist/src/faf-core/utils/chrome-extension-detector.js.map +0 -1
  125. package/dist/src/faf-core/utils/platform-detector.d.ts +0 -30
  126. package/dist/src/faf-core/utils/platform-detector.js +0 -218
  127. package/dist/src/faf-core/utils/platform-detector.js.map +0 -1
  128. package/dist/src/faf-core/utils/technical-credit.d.ts +0 -35
  129. package/dist/src/faf-core/utils/technical-credit.js +0 -286
  130. package/dist/src/faf-core/utils/technical-credit.js.map +0 -1
  131. package/dist/src/faf-core/utils/yaml-generator.d.ts +0 -41
  132. package/dist/src/faf-core/utils/yaml-generator.js +0 -360
  133. package/dist/src/faf-core/utils/yaml-generator.js.map +0 -1
  134. package/dist/src/handlers/behavioral-instruction.d.ts +0 -16
  135. package/dist/src/handlers/behavioral-instruction.js +0 -43
  136. package/dist/src/handlers/behavioral-instruction.js.map +0 -1
  137. package/dist/src/handlers/championship-tools.d.ts +0 -116
  138. package/dist/src/handlers/championship-tools.js +0 -2320
  139. package/dist/src/handlers/championship-tools.js.map +0 -1
  140. package/dist/src/handlers/cloud-handler.d.ts +0 -59
  141. package/dist/src/handlers/cloud-handler.js +0 -348
  142. package/dist/src/handlers/cloud-handler.js.map +0 -1
  143. package/dist/src/handlers/tool-registry.d.ts +0 -23
  144. package/dist/src/handlers/tool-registry.js +0 -68
  145. package/dist/src/handlers/tool-registry.js.map +0 -1
  146. package/dist/src/handlers/tool-types.d.ts +0 -167
  147. package/dist/src/handlers/tool-types.js +0 -7
  148. package/dist/src/handlers/tool-types.js.map +0 -1
  149. package/dist/src/test-all-functions.d.ts +0 -15
  150. package/dist/src/test-all-functions.js +0 -163
  151. package/dist/src/test-all-functions.js.map +0 -1
  152. package/dist/src/types/mcp-tools.d.ts +0 -53
  153. package/dist/src/types/mcp-tools.js +0 -77
  154. package/dist/src/types/mcp-tools.js.map +0 -1
  155. package/dist/src/types/tool-visibility.d.ts +0 -36
  156. package/dist/src/types/tool-visibility.js +0 -551
  157. package/dist/src/types/tool-visibility.js.map +0 -1
  158. package/dist/src/utils/auto-path-detection.d.ts +0 -26
  159. package/dist/src/utils/auto-path-detection.js +0 -198
  160. package/dist/src/utils/auto-path-detection.js.map +0 -1
  161. package/dist/src/utils/championship-format.d.ts +0 -30
  162. package/dist/src/utils/championship-format.js +0 -81
  163. package/dist/src/utils/championship-format.js.map +0 -1
  164. package/dist/src/utils/cli-detector.d.ts +0 -17
  165. package/dist/src/utils/cli-detector.js +0 -247
  166. package/dist/src/utils/cli-detector.js.map +0 -1
  167. package/dist/src/utils/display-protocol.d.ts +0 -57
  168. package/dist/src/utils/display-protocol.js +0 -131
  169. package/dist/src/utils/display-protocol.js.map +0 -1
  170. package/dist/src/utils/username-detector.d.ts +0 -27
  171. package/dist/src/utils/username-detector.js +0 -90
  172. package/dist/src/utils/username-detector.js.map +0 -1
  173. package/dist/src/utils/visual-style.d.ts +0 -62
  174. package/dist/src/utils/visual-style.js +0 -166
  175. package/dist/src/utils/visual-style.js.map +0 -1
  176. package/scripts/postinstall.js +0 -50
  177. package/skill/SKILL.md +0 -385
@@ -206,9 +206,9 @@ class FafToolHandler {
206
206
  },
207
207
  {
208
208
  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.',
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.',
210
210
  annotations: {
211
- title: 'Sync .faf to CLAUDE.md',
211
+ title: 'Reconcile .faf with repo manifests',
212
212
  readOnlyHint: false,
213
213
  destructiveHint: false,
214
214
  openWorldHint: false
@@ -216,6 +216,7 @@ class FafToolHandler {
216
216
  inputSchema: {
217
217
  type: 'object',
218
218
  properties: {
219
+ apply: { type: 'boolean', description: 'Write the detected changes into project.faf (default: dry-run report only)' },
219
220
  path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
220
221
  },
221
222
  additionalProperties: false
@@ -223,9 +224,9 @@ class FafToolHandler {
223
224
  },
224
225
  {
225
226
  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.',
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.',
227
228
  annotations: {
228
- title: 'Bi-directional Sync',
229
+ title: 'Sync .faf to CLAUDE.md and IDE formats',
229
230
  readOnlyHint: false,
230
231
  destructiveHint: false,
231
232
  openWorldHint: false
@@ -233,9 +234,6 @@ class FafToolHandler {
233
234
  inputSchema: {
234
235
  type: 'object',
235
236
  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
237
  agents: { type: 'boolean', description: 'Also sync to AGENTS.md (OpenAI/Codex format)' },
240
238
  cursor: { type: 'boolean', description: 'Also sync to .cursorrules (Cursor IDE format)' },
241
239
  gemini: { type: 'boolean', description: 'Also sync to GEMINI.md (Google Gemini format)' },
@@ -247,7 +245,7 @@ class FafToolHandler {
247
245
  },
248
246
  {
249
247
  name: 'faf_clear',
250
- description: 'Clear caches, temporary files, and reset FAF state for a fresh start',
248
+ description: 'Remove the ~/.faf-cli-cache directory left by older faf-mcp versions. This version persists nothing outside your project files.',
251
249
  annotations: {
252
250
  title: 'Clear .faf Data',
253
251
  readOnlyHint: false,
@@ -257,17 +255,15 @@ class FafToolHandler {
257
255
  inputSchema: {
258
256
  type: 'object',
259
257
  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)' }
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)' }
264
260
  },
265
261
  additionalProperties: false
266
262
  }
267
263
  },
268
264
  {
269
265
  name: 'faf_debug',
270
- description: 'Debug faf-mcp environment - show working directory, permissions, and FAF CLI status',
266
+ description: 'Debug faf-mcp environment - show working directory, write permissions, the bundled faf-cli version, and whether a project.faf is present',
271
267
  annotations: {
272
268
  title: 'Debug Info',
273
269
  readOnlyHint: true,
@@ -486,8 +482,10 @@ class FafToolHandler {
486
482
  inputSchema: {
487
483
  type: 'object',
488
484
  properties: {
489
- path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' },
490
- force: { type: 'boolean', description: 'Force overwrite existing files' }
485
+ path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
486
+ // `force` used to be declared here; handleFafAuto never read it.
487
+ // faf_auto merges into an existing project.faf non-destructively
488
+ // and there is no overwrite mode to switch on.
491
489
  },
492
490
  additionalProperties: false
493
491
  }
@@ -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.`;
@@ -977,7 +975,7 @@ package_manager: ${projectData.package_manager}` : ''}
977
975
  return {
978
976
  content: [{
979
977
  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`
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`
981
979
  }]
982
980
  };
983
981
  }
@@ -991,25 +989,65 @@ package_manager: ${projectData.package_manager}` : ''}
991
989
  };
992
990
  }
993
991
  }
992
+ /**
993
+ * v3.0: was `this.engineAdapter.callEngine('trust')` — no bundled 'trust'
994
+ * branch exists in engine-adapter.ts, so this fell straight through to
995
+ * the ambient-PATH shell-out fallback (`which faf`, then execAsync).
996
+ * faf-cli itself has no `trust` subcommand at all — on any host where an
997
+ * unrelated `faf`-named binary sits first on PATH (verified live on this
998
+ * machine: Rust "FAFb"), this either hard-errors on an "unrecognized
999
+ * subcommand" or, worse, silently "succeeds" with that tool's own output.
1000
+ * Rewired onto the bundled faf-cli bridge — bundled faf-cli only, never
1001
+ * ambient PATH — composing `validateFaf` (structural integrity) +
1002
+ * `scoreFafYaml` (trust metrics) directly, matching the tool's own
1003
+ * description ("structural integrity and field consistency... trust
1004
+ * metrics"). No CLI subcommand to shell out to even exists for this.
1005
+ */
994
1006
  async handleFafTrust(_args) {
995
- const result = await this.engineAdapter.callEngine('trust');
996
- if (!result.success) {
1007
+ const cwd = this.getProjectPath();
1008
+ const { findFafFile: findFaf, readFaf, readFafRaw, validateFaf, scoreFafYaml } = await faf_cli_bridge_js_1.fafCli;
1009
+ const fafPath = findFaf(cwd);
1010
+ if (!fafPath) {
997
1011
  return {
998
1012
  content: [{
999
1013
  type: 'text',
1000
- text: `🔒 Claude FAF Trust Validation:\n\nFailed to check trust: ${result.error}`
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.`
1015
+ }]
1016
+ };
1017
+ }
1018
+ let raw;
1019
+ let data;
1020
+ try {
1021
+ raw = readFafRaw(fafPath);
1022
+ data = readFaf(fafPath);
1023
+ }
1024
+ catch (error) {
1025
+ return {
1026
+ content: [{
1027
+ type: 'text',
1028
+ text: `🔒 Claude FAF Trust Validation:\n\n❌ Could not read ${fafPath}: ${error?.message ?? String(error)}`
1001
1029
  }],
1002
1030
  isError: true
1003
1031
  };
1004
1032
  }
1005
- const output = typeof result.data === 'string'
1006
- ? result.data
1007
- : result.data?.output || JSON.stringify(result.data, null, 2);
1033
+ const validation = validateFaf(data);
1034
+ let output = validation.valid
1035
+ ? `✅ Structurally sound — no malformed, missing, or contradictory fields found.`
1036
+ : `❌ ${validation.errors.length} issue${validation.errors.length === 1 ? '' : 's'} found:\n` +
1037
+ validation.errors.map((e) => ` • ${e}`).join('\n');
1038
+ try {
1039
+ const score = scoreFafYaml(raw);
1040
+ output += `\n\n📊 ${score.score}/100 (${score.populated}/${score.active} slots populated) — ${score.tier.name}`;
1041
+ }
1042
+ catch {
1043
+ // Score is a bonus signal here; a validation-only result is still honest.
1044
+ }
1008
1045
  return {
1009
1046
  content: [{
1010
1047
  type: 'text',
1011
1048
  text: `🔒 Claude FAF Trust Validation:\n\n${output}`
1012
- }]
1049
+ }],
1050
+ isError: !validation.valid
1013
1051
  };
1014
1052
  }
1015
1053
  async handleFafSync(args) {
@@ -1017,7 +1055,9 @@ package_manager: ${projectData.package_manager}` : ''}
1017
1055
  if (args?.path) {
1018
1056
  this.getProjectPath(args.path);
1019
1057
  }
1020
- const result = await this.engineAdapter.callEngine('sync');
1058
+ // `apply` maps to the bundled sync command's --auto; without it the
1059
+ // command only reports what would change.
1060
+ const result = await this.engineAdapter.callEngine('sync', args?.apply ? ['--auto'] : []);
1021
1061
  if (!result.success) {
1022
1062
  return {
1023
1063
  content: [{
@@ -1042,15 +1082,23 @@ package_manager: ${projectData.package_manager}` : ''}
1042
1082
  if (args?.path) {
1043
1083
  this.getProjectPath(args.path);
1044
1084
  }
1085
+ // 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.
1045
1087
  const biSyncArgs = [];
1046
- if (args?.auto) {
1047
- biSyncArgs.push('--auto');
1088
+ // 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.
1091
+ if (args?.agents) {
1092
+ biSyncArgs.push('--agents');
1048
1093
  }
1049
- if (args?.watch) {
1050
- biSyncArgs.push('--watch');
1094
+ if (args?.cursor) {
1095
+ biSyncArgs.push('--cursor');
1051
1096
  }
1052
- if (args?.force) {
1053
- biSyncArgs.push('--force');
1097
+ if (args?.gemini) {
1098
+ biSyncArgs.push('--gemini');
1099
+ }
1100
+ if (args?.all) {
1101
+ biSyncArgs.push('--all');
1054
1102
  }
1055
1103
  const result = await this.engineAdapter.callEngine('bi-sync', biSyncArgs);
1056
1104
  if (!result.success) {
@@ -1072,37 +1120,46 @@ package_manager: ${projectData.package_manager}` : ''}
1072
1120
  }]
1073
1121
  };
1074
1122
  }
1123
+ /**
1124
+ * v3.0: was `this.engineAdapter.callEngine('clear', clearArgs)` — no
1125
+ * bundled 'clear' branch exists in engine-adapter.ts, so this fell
1126
+ * straight through to the ambient-PATH shell-out fallback. Worse: even
1127
+ * when a real faf-cli WAS first on PATH, its actual `clear` subcommand
1128
+ * only clears `faf-git-*` temp directories from the OS tmpdir (see
1129
+ * ~/FAF/cli/src/commands/clear.ts) — entirely unrelated to this tool's
1130
+ * own cache/todos/backups semantics. There was never a correct backing
1131
+ * implementation for this tool's contract, on any PATH.
1132
+ *
1133
+ * Rewired as a genuinely local operation — no CLI dependency, bundled or
1134
+ * ambient, at all. `cache` clears the one real cache path this codebase
1135
+ * writes (~/.faf-cli-cache, faf-core/utils/technical-credit.ts). The
1136
+ * schema used to advertise `todos` and `backups` as well; no persisted todo
1137
+ * list or backup-file store exists anywhere in faf-mcp, so those flags are
1138
+ * gone rather than answered with a made-up "cleared".
1139
+ */
1075
1140
  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
- };
1141
+ const wantCache = !!args?.cache;
1142
+ const wantAll = !!args?.all || !wantCache;
1143
+ const lines = [];
1144
+ if (wantCache || wantAll) {
1145
+ // HOME/USERPROFILE first (Node's own os.homedir() precedence; Bun's ignores a
1146
+ // runtime override) so a test can redirect the home dir instead of
1147
+ // deleting from the developer's real one.
1148
+ const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
1149
+ const cacheDir = pathModule.join(home, '.faf-cli-cache');
1150
+ const creditCache = pathModule.join(cacheDir, 'technical-credit.json');
1151
+ if (fs.existsSync(creditCache)) {
1152
+ fs.rmSync(creditCache, { force: true });
1153
+ lines.push(`✅ cache: cleared ${creditCache}`);
1154
+ }
1155
+ else {
1156
+ lines.push('✅ cache: nothing to clear');
1157
+ }
1098
1158
  }
1099
- const output = typeof result.data === 'string'
1100
- ? result.data
1101
- : result.data?.output || JSON.stringify(result.data, null, 2);
1102
1159
  return {
1103
1160
  content: [{
1104
1161
  type: 'text',
1105
- text: `🧹 Claude FAF Clear:\n\n${output}`
1162
+ text: `🧹 Claude FAF Clear:\n\n${lines.join('\n')}`
1106
1163
  }]
1107
1164
  };
1108
1165
  }
@@ -1180,18 +1237,15 @@ REMEMBER: Always use ".faf" with the dot - it's a FORMAT!
1180
1237
  try {
1181
1238
  const fs = await import('fs');
1182
1239
  const path = await import('path');
1183
- const { exec } = await import('child_process');
1184
- const { promisify } = await import('util');
1185
- const _execAsync = promisify(exec);
1186
1240
  const cwd = this.engineAdapter.getWorkingDirectory();
1187
1241
  const debugInfo = {
1188
1242
  workingDirectory: cwd,
1189
1243
  canWrite: false,
1190
- fafCliPath: null,
1191
- fafVersion: null,
1244
+ // Every tool runs on the faf-cli bundled in node_modules — report
1245
+ // that, not whatever `which faf` happens to find on PATH.
1246
+ fafVersion: (0, faf_cli_bridge_js_1.bundledFafCliVersion)(),
1192
1247
  permissions: {},
1193
1248
  enginePath: this.engineAdapter.getEnginePath(),
1194
- pathEnv: process.env.PATH?.split(':') || []
1195
1249
  };
1196
1250
  // Check write permissions
1197
1251
  try {
@@ -1203,21 +1257,6 @@ REMEMBER: Always use ".faf" with the dot - it's a FORMAT!
1203
1257
  catch (error) {
1204
1258
  debugInfo.permissions.writeError = error instanceof Error ? error.message : String(error);
1205
1259
  }
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
1260
  // Check for existing FAF file (v1.2.0: project.faf, *.faf, or .faf)
1222
1261
  const fafResult = await (0, faf_file_finder_js_1.findFafFile)(cwd);
1223
1262
  const hasFaf = fafResult !== null;
@@ -1226,15 +1265,12 @@ REMEMBER: Always use ".faf" with the dot - it's a FORMAT!
1226
1265
  📂 Working Directory: ${debugInfo.workingDirectory}
1227
1266
  ✏️ Write Permissions: ${debugInfo.canWrite ? '✅ Yes' : '❌ No'}
1228
1267
  ${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 ? '...' : ''}
1268
+ 📋 faf-cli (bundled): ${debugInfo.fafVersion ? `v${debugInfo.fafVersion}` : '❌ not found in node_modules — reinstall faf-mcp'}
1269
+ 📄 FAF File: ${hasFaf ? `✅ ${fafResult.filename} exists` : '❌ Not found (run faf_init)'}
1233
1270
 
1234
1271
  💡 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"
1272
+ 1. If .faf file missing: use faf_init or faf_auto
1273
+ 2. To check AI-readiness: use faf_score
1238
1274
  `;
1239
1275
  return {
1240
1276
  content: [{
@@ -1780,7 +1816,7 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
1780
1816
  // they're SOURCED by Turbo-Cat (faf-cli's separate STACK_INTERVIEW if ever
1781
1817
  // needed), never asked of a human. (Decision: single-source the 8-Q 6Ws
1782
1818
  // Interview, wolfejam 2026-06-10 — language is not on the human side.)
1783
- const { SIX_WS_INTERVIEW } = await faf_cli_bridge_js_1.fafCli;
1819
+ const { SIX_WS_INTERVIEW, isPlaceholder, scoreFafYaml } = await faf_cli_bridge_js_1.fafCli;
1784
1820
  const QUESTION_REGISTRY = Object.fromEntries(SIX_WS_INTERVIEW.map((q) => [q.path, q]));
1785
1821
  // Priority order for questions
1786
1822
  const priorityOrder = [
@@ -1825,16 +1861,18 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
1825
1861
  }
1826
1862
  current[parts[parts.length - 1]] = value;
1827
1863
  };
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
- };
1864
+ // Empty = what the scorer calls empty. faf-cli's isPlaceholder is the one
1865
+ // rule the kernel scores by; a local list ('TBD', 'Unknown', ...) drifted
1866
+ // from it in both directions and made faf_go's numbers disagree with
1867
+ // faf_score on the same file. slotignored is inactive, never asked.
1868
+ const isEmpty = (value) => isPlaceholder(value) && value !== 'slotignored';
1869
+ // One score function, period: the real scorer on the real bytes.
1870
+ const scoreOf = (raw) => scoreFafYaml(raw).score;
1871
+ const tierLabel = (score) => score >= 100 ? '✪ Trophy — 100% AI-Readiness' :
1872
+ score >= 99 ? '★ Gold' :
1873
+ score >= 95 ? '◆ Silver' :
1874
+ score >= 85 ? '◇ Bronze — production ready' :
1875
+ score >= 70 ? '● Green — solid foundation' : '📈 Keep going!';
1838
1876
  // PHASE 2: Apply answers if provided
1839
1877
  if (args?.answers && typeof args.answers === 'object') {
1840
1878
  const answers = args.answers;
@@ -1845,15 +1883,10 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
1845
1883
  appliedCount++;
1846
1884
  }
1847
1885
  }
1848
- // Write updated file
1886
+ // Write updated file, then score the bytes that were written.
1849
1887
  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!';
1888
+ const newScore = scoreOf(fs.readFileSync(fafResult.path, 'utf-8'));
1889
+ const celebration = tierLabel(newScore);
1857
1890
  return {
1858
1891
  content: [{
1859
1892
  type: 'text',
@@ -1864,12 +1897,10 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
1864
1897
  // PHASE 1: Analyze and return questions — the canonical 6Ws (SIX_WS_INTERVIEW),
1865
1898
  // scoped to slots that are empty AND active (slotignored is never asked).
1866
1899
  const missingFields = SIX_WS_INTERVIEW
1867
- .filter((q) => { const v = getNestedValue(fafData, q.path); return v !== 'slotignored' && isEmpty(v); })
1900
+ .filter((q) => isEmpty(getNestedValue(fafData, q.path)))
1868
1901
  .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);
1902
+ // Current score — the same number faf_score reports for this file.
1903
+ const currentScore = scoreOf(fafContent);
1873
1904
  // Already at 100%?
1874
1905
  if (currentScore >= 100) {
1875
1906
  return {
@@ -1892,7 +1923,7 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
1892
1923
  text: JSON.stringify({
1893
1924
  score: currentScore,
1894
1925
  message: `Score is ${currentScore}%. All fields filled but content may need enhancement.`,
1895
- suggestion: 'Use faf_enhance to improve content quality.',
1926
+ suggestion: 'Use faf_go or faf_human_add to improve content quality.',
1896
1927
  context: 'faf_go'
1897
1928
  }, null, 2)
1898
1929
  }]
@@ -1945,120 +1976,52 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
1945
1976
  /**
1946
1977
  * faf_auto - ONE COMMAND TO RULE THEM ALL
1947
1978
  * Zero to Championship in one command
1948
- * Runs: init + formats + sync + bi-sync + score
1979
+ *
1980
+ * v3.0: composes faf-cli's own `faf auto` chain — `updateExistingFaf` on an
1981
+ * existing file, `assembleFreshFaf` on a new one, `writeFaf` for the bytes,
1982
+ * `renderClaudeMd`/`writeClaudeMd` for CLAUDE.md — plus the same
1983
+ * `scoreFafYaml` faf_score reads. Previously this handler ran a bespoke
1984
+ * bootstrap template plus a local `calculateSimpleScore` pseudo-scorer,
1985
+ * which could (and did) report a different score than faf_score for the
1986
+ * identical file in the same server run. NEVER reimplement detection or
1987
+ * scoring here — compose faf-cli, exactly like faf_score/faf_formats do.
1949
1988
  */
1950
1989
  async handleFafAuto(args) {
1951
1990
  const startTime = Date.now();
1952
1991
  const cwd = this.getProjectPath(args?.path);
1953
- const yaml = await import('yaml');
1954
1992
  const path = await import('path');
1955
1993
  try {
1956
1994
  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);
1995
+ const { findFafFile: findFaf, readFaf, readFafRaw, scoreFafYaml, assembleFreshFaf, updateExistingFaf, writeFaf, renderClaudeMd, writeClaudeMd, } = await faf_cli_bridge_js_1.fafCli;
1996
+ // Step 1: locate any existing .faf and its real "before" score.
1997
+ const existingPath = findFaf(cwd);
1998
+ const beforeScore = existingPath ? scoreFafYaml(readFafRaw(existingPath)).score : 0;
1999
+ steps.push(existingPath ? `✅ Found ${path.basename(existingPath)}` : '✅ Will create project.faf');
2000
+ // Step 2: exactly what faf-cli's own `faf auto` does — an existing file
2001
+ // is updated with updateExistingFaf (existing values win; interrogated →
2002
+ // detected → Turbo-Cat → Relentless fill the empties), a new one is
2003
+ // assembled with assembleFreshFaf — and written with faf-cli's writer,
2004
+ // so the bytes match `faf auto` (no runtime `_meta` block, same layout).
1960
2005
  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');
1980
- }
1981
- 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`);
2006
+ if (existingPath) {
2007
+ fafPath = existingPath;
2008
+ writeFaf(fafPath, updateExistingFaf(cwd, readFaf(existingPath)));
2009
+ steps.push('✅ Merged fresh facts into existing project.faf (existing values preserved)');
2008
2010
  }
2009
2011
  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');
2012
+ fafPath = path.join(cwd, 'project.faf');
2013
+ writeFaf(fafPath, assembleFreshFaf(cwd));
2014
+ steps.push('✅ Created project.faf');
2056
2015
  }
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;
2016
+ // Step 3: CLAUDE.md — faf-cli's render of the file just written (the
2017
+ // same bytes `faf sync` writes), injected non-destructively.
2018
+ const claudeExisted = fs.existsSync(path.join(cwd, 'CLAUDE.md'));
2019
+ writeClaudeMd(cwd, renderClaudeMd(readFaf(fafPath)));
2020
+ steps.push(claudeExisted ? '✅ Updated CLAUDE.md (faf-managed block)' : '✅ Created CLAUDE.md');
2021
+ // Step 4: final score — same real scorer faf_score reads, on the file
2022
+ // just written. By construction this can never disagree with faf_score.
2023
+ const newScore = scoreFafYaml(readFafRaw(fafPath)).score;
2024
+ const scoreDelta = newScore - beforeScore;
2062
2025
  // Calculate elapsed time
2063
2026
  const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
2064
2027
  // Format output
@@ -2068,7 +2031,7 @@ ${fafData.stack_signature || 'Auto-detected stack'}
2068
2031
  output += steps.join('\n') + '\n\n';
2069
2032
  output += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
2070
2033
  output += `⏱️ Completed in ${elapsed}s\n`;
2071
- output += `📊 Before: ${currentScore}% | After: ${newScore}% ${deltaDisplay}\n`;
2034
+ output += `📊 Before: ${beforeScore}% | After: ${newScore}% ${deltaDisplay}\n`;
2072
2035
  output += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n`;
2073
2036
  if (newScore >= 99) {
2074
2037
  output += `🏆 CHAMPIONSHIP ACHIEVED! Your AI has complete context.\n`;
@@ -2082,7 +2045,7 @@ ${fafData.stack_signature || 'Auto-detected stack'}
2082
2045
  else {
2083
2046
  output += `🚀 Good start! Run faf_go for guided improvement.\n`;
2084
2047
  }
2085
- output += `\n💡 Next: faf_score --details | faf_go | faf_enhance`;
2048
+ output += `\n💡 Next: faf_score | faf_go | faf_bi_sync`;
2086
2049
  return { content: [{ type: 'text', text: output }] };
2087
2050
  }
2088
2051
  catch (error) {
@@ -2113,11 +2076,10 @@ ${fafData.stack_signature || 'Auto-detected stack'}
2113
2076
  }]
2114
2077
  };
2115
2078
  }
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);
2079
+ // .faf exists but no DNA - create initial DNA. Real scorer — same
2080
+ // one faf_score reads — never a local pseudo-score (v3.0 fix).
2081
+ const { readFafRaw, scoreFafYaml } = await faf_cli_bridge_js_1.fafCli;
2082
+ const currentScore = scoreFafYaml(readFafRaw(fafResult.path)).score;
2121
2083
  const dna = {
2122
2084
  birthCertificate: {
2123
2085
  born: new Date().toISOString(),
@@ -2149,8 +2111,24 @@ ${fafData.stack_signature || 'Auto-detected stack'}
2149
2111
  const dna = JSON.parse(dnaContent);
2150
2112
  // Build journey string
2151
2113
  const birthScore = dna.birthCertificate?.birthDNA || 0;
2152
- const currentScore = dna.current?.score || 0;
2114
+ let currentScore = dna.current?.score || 0;
2153
2115
  const milestones = dna.milestones || [];
2116
+ dna.milestones = milestones;
2117
+ // "Current" is the live score of the file on disk, not whatever was
2118
+ // stored last time. Nothing else records growth into .faf-dna, so this
2119
+ // is where the journey gets its next point.
2120
+ const fafResult = await (0, faf_file_finder_js_1.findFafFile)(cwd);
2121
+ if (fafResult) {
2122
+ const { readFafRaw, scoreFafYaml } = await faf_cli_bridge_js_1.fafCli;
2123
+ const live = scoreFafYaml(readFafRaw(fafResult.path)).score;
2124
+ if (live !== currentScore) {
2125
+ const now = new Date().toISOString();
2126
+ dna.current = { ...(dna.current ?? {}), score: live, lastSync: now };
2127
+ milestones.push({ type: 'sync', score: live, date: now, version: dna.current.version ?? 'v1.0.0' });
2128
+ fs.writeFileSync(dnaPath, JSON.stringify(dna, null, 2));
2129
+ currentScore = live;
2130
+ }
2131
+ }
2154
2132
  // Find key milestones
2155
2133
  const _birth = milestones.find((m) => m.type === 'birth');
2156
2134
  const peak = milestones.find((m) => m.type === 'peak');
@@ -2192,7 +2170,7 @@ ${fafData.stack_signature || 'Auto-detected stack'}
2192
2170
  }
2193
2171
  output += `\n🧬 MILESTONES\n`;
2194
2172
  const milestoneIcons = {
2195
- birth: '🐣', first_save: '💾', doubled: '2️⃣',
2173
+ birth: '🐣', first_save: '💾', doubled: '2️⃣', sync: '🔄',
2196
2174
  championship: '🏆', elite: '⭐', peak: '🏔️', perfect: '💎'
2197
2175
  };
2198
2176
  for (const m of milestones) {
@@ -2271,33 +2249,6 @@ ${fafData.stack_signature || 'Auto-detected stack'}
2271
2249
  }
2272
2250
  }
2273
2251
  /**
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
2252
  /**
2302
2253
  * faf_quick - Lightning-fast .faf creation
2303
2254
  * One-liner format: "name, description, language, framework, hosting"
@@ -2357,7 +2308,7 @@ Example: "my-app, e-commerce platform"`
2357
2308
 
2358
2309
  ⚠️ project.faf already exists at: ${fafPath}
2359
2310
 
2360
- Use force: true to overwrite, or use faf_enhance to modify.`
2311
+ Use force: true to overwrite, or use faf_go / faf_human_add to modify.`
2361
2312
  }]
2362
2313
  };
2363
2314
  }
@@ -2394,7 +2345,6 @@ Use force: true to overwrite, or use faf_enhance to modify.`
2394
2345
  output += `✅ Created: ${fafPath}\n\n`;
2395
2346
  output += `Next steps:\n`;
2396
2347
  output += ` • faf_score - Check AI-readiness\n`;
2397
- output += ` • faf_enhance - Improve context\n`;
2398
2348
  output += ` • faf_go - Guided interview to 100%`;
2399
2349
  return { content: [{ type: 'text', text: output }] };
2400
2350
  }
@@ -2502,7 +2452,7 @@ Use force: true to overwrite, or use faf_enhance to modify.`
2502
2452
  results.push({
2503
2453
  status: 'warning',
2504
2454
  message: `Missing important fields: ${missingFields.join(', ')}`,
2505
- fix: 'Run: faf_enhance or faf_go to add missing info'
2455
+ fix: 'Run: faf_go to add missing info'
2506
2456
  });
2507
2457
  }
2508
2458
  else {
@@ -2511,26 +2461,31 @@ Use force: true to overwrite, or use faf_enhance to modify.`
2511
2461
  message: '.faf structure is valid'
2512
2462
  });
2513
2463
  }
2514
- // Check 4: Score
2515
- const score = this.calculateSimpleScore(fafData);
2464
+ // Check 4: Score — real scorer (same one faf_score reads), never
2465
+ // a local pseudo-score (v3.0 fix). Slot breakdown included so
2466
+ // faf_doctor's "score could be better" fix advice is actionable.
2467
+ const { scoreFafYaml } = await faf_cli_bridge_js_1.fafCli;
2468
+ const scoreResult = scoreFafYaml(content);
2469
+ const score = scoreResult.score;
2470
+ const slotSummary = `${scoreResult.populated}/${scoreResult.active} slots populated`;
2516
2471
  if (score < 30) {
2517
2472
  results.push({
2518
2473
  status: 'error',
2519
- message: `Score too low: ${score}%`,
2520
- fix: 'Run: faf_enhance or faf_go to improve context'
2474
+ message: `Score too low: ${score}% (${slotSummary})`,
2475
+ fix: 'Run: faf_go to improve context'
2521
2476
  });
2522
2477
  }
2523
2478
  else if (score < 70) {
2524
2479
  results.push({
2525
2480
  status: 'warning',
2526
- message: `Score could be better: ${score}%`,
2481
+ message: `Score could be better: ${score}% (${slotSummary})`,
2527
2482
  fix: 'Target 70%+ for championship AI context'
2528
2483
  });
2529
2484
  }
2530
2485
  else {
2531
2486
  results.push({
2532
2487
  status: 'ok',
2533
- message: `Great score: ${score}%`
2488
+ message: `Great score: ${score}% (${slotSummary})`
2534
2489
  });
2535
2490
  }
2536
2491
  }