faf-mcp 2.0.1 → 2.1.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 (67) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/CLAUDE.md +23 -21
  3. package/README.md +27 -24
  4. package/dist/src/cli.js +1 -1
  5. package/dist/src/cli.js.map +1 -1
  6. package/dist/src/faf-core/commands/auto.d.ts +2 -2
  7. package/dist/src/faf-core/commands/auto.js.map +1 -1
  8. package/dist/src/faf-core/commands/innit.d.ts +2 -2
  9. package/dist/src/faf-core/commands/innit.js.map +1 -1
  10. package/dist/src/faf-core/commands/score.d.ts +1 -1
  11. package/dist/src/faf-core/commands/score.js.map +1 -1
  12. package/dist/src/faf-core/generators/faf-generator-championship.js.map +1 -1
  13. package/dist/src/faf-core/parsers/agents-parser.d.ts +1 -1
  14. package/dist/src/faf-core/parsers/agents-parser.js +2 -2
  15. package/dist/src/faf-core/parsers/agents-parser.js.map +1 -1
  16. package/dist/src/faf-core/parsers/conductor-parser.d.ts +1 -1
  17. package/dist/src/faf-core/parsers/conductor-parser.js +1 -1
  18. package/dist/src/faf-core/parsers/cursorrules-parser.d.ts +1 -1
  19. package/dist/src/faf-core/parsers/cursorrules-parser.js +2 -2
  20. package/dist/src/faf-core/parsers/cursorrules-parser.js.map +1 -1
  21. package/dist/src/faf-core/parsers/faf-git-generator.d.ts +1 -1
  22. package/dist/src/faf-core/parsers/faf-git-generator.js +2 -2
  23. package/dist/src/faf-core/parsers/faf-git-generator.js.map +1 -1
  24. package/dist/src/faf-core/parsers/gemini-parser.d.ts +1 -1
  25. package/dist/src/faf-core/parsers/gemini-parser.js +1 -1
  26. package/dist/src/faf-core/parsers/github-extractor.d.ts +1 -1
  27. package/dist/src/faf-core/parsers/github-extractor.js +2 -2
  28. package/dist/src/faf-core/parsers/github-extractor.js.map +1 -1
  29. package/dist/src/faf-core/parsers/slot-counter.d.ts +1 -1
  30. package/dist/src/faf-core/parsers/slot-counter.js +1 -1
  31. package/dist/src/handlers/championship-tools.d.ts +6 -13
  32. package/dist/src/handlers/championship-tools.js +122 -530
  33. package/dist/src/handlers/championship-tools.js.map +1 -1
  34. package/dist/src/handlers/engine-adapter.js +3 -3
  35. package/dist/src/handlers/engine-adapter.js.map +1 -1
  36. package/dist/src/handlers/fileHandler.d.ts +1 -1
  37. package/dist/src/handlers/fileHandler.js +1 -1
  38. package/dist/src/handlers/fileHandler.js.map +1 -1
  39. package/dist/src/handlers/resources.d.ts +1 -1
  40. package/dist/src/handlers/tool-registry.d.ts +2 -2
  41. package/dist/src/handlers/tools.d.ts +1 -1
  42. package/dist/src/handlers/tools.js +116 -135
  43. package/dist/src/handlers/tools.js.map +1 -1
  44. package/dist/src/index.js +2 -2
  45. package/dist/src/index.js.map +1 -1
  46. package/dist/src/server.d.ts +8 -3
  47. package/dist/src/server.js +16 -10
  48. package/dist/src/server.js.map +1 -1
  49. package/dist/src/test-all-functions.js +1 -1
  50. package/dist/src/test-all-functions.js.map +1 -1
  51. package/dist/src/types/mcp-tools.d.ts +2 -2
  52. package/dist/src/types/tool-visibility.js +1 -1
  53. package/dist/src/types/tool-visibility.js.map +1 -1
  54. package/dist/src/utils/cli-detector.d.ts +3 -6
  55. package/dist/src/utils/cli-detector.js +19 -2
  56. package/dist/src/utils/cli-detector.js.map +1 -1
  57. package/dist/src/utils/display-protocol.d.ts +2 -2
  58. package/dist/src/utils/display-protocol.js +4 -4
  59. package/dist/src/utils/display-protocol.js.map +1 -1
  60. package/dist/src/utils/faf-cli-bridge.d.ts +47 -0
  61. package/dist/src/utils/faf-cli-bridge.js +101 -0
  62. package/dist/src/utils/faf-cli-bridge.js.map +1 -0
  63. package/dist/src/utils/visual-style.js +1 -1
  64. package/dist/src/utils/visual-style.js.map +1 -1
  65. package/package.json +7 -10
  66. package/project.faf +31 -133
  67. package/scripts/check-stylesheet-drift.mjs +114 -0
@@ -42,6 +42,19 @@ const fuzzy_detector_1 = require("../utils/fuzzy-detector");
42
42
  const faf_file_finder_js_1 = require("../utils/faf-file-finder.js");
43
43
  const version_1 = require("../version");
44
44
  const path_resolver_1 = require("../utils/path-resolver");
45
+ // v2.1.1: single-source scoring — same path the championship handler uses.
46
+ // faf_score now reads faf-cli's real scorer (the IANA-spec one) instead of
47
+ // the legacy 40+30+15+14 file-presence pseudo-score. NEVER reimplement
48
+ // scoring or the tier ladder here — that's exactly the drift v2.1.0 set out
49
+ // to kill across the surface; v2.1.1 closes the loop on the active handler.
50
+ //
51
+ // Imported via ../utils/faf-cli-bridge.js — a temporary one-file re-export
52
+ // that pins the relative dist path (faf-cli 6.7.1's `bun` exports condition
53
+ // resolves to a non-shipped src/, breaking the test runner). Both Node and
54
+ // Bun load the same compiled module through the bridge. Bridge is removable
55
+ // once faf-cli drops the bad `bun` condition. See the bridge header for full
56
+ // rationale and the linked tracked issue.
57
+ const faf_cli_bridge_js_1 = require("../utils/faf-cli-bridge.js");
45
58
  class FafToolHandler {
46
59
  engineAdapter;
47
60
  constructor(engineAdapter) {
@@ -254,7 +267,7 @@ class FafToolHandler {
254
267
  },
255
268
  {
256
269
  name: 'faf_debug',
257
- description: 'Debug Claude FAF MCP environment - show working directory, permissions, and FAF CLI status',
270
+ description: 'Debug faf-mcp environment - show working directory, permissions, and FAF CLI status',
258
271
  annotations: {
259
272
  title: 'Debug Info',
260
273
  readOnlyHint: true,
@@ -380,7 +393,7 @@ class FafToolHandler {
380
393
  },
381
394
  {
382
395
  name: 'faf_guide',
383
- description: 'FAF MCP usage guide for Claude Desktop - Projects convention, path resolution, and UX patterns',
396
+ description: 'FAF MCP usage guide for your MCP host - Projects convention, path resolution, and UX patterns',
384
397
  annotations: {
385
398
  title: 'Usage Guide',
386
399
  readOnlyHint: true,
@@ -802,141 +815,109 @@ class FafToolHandler {
802
815
  }
803
816
  }
804
817
  async handleFafScore(args) {
818
+ // v2.1.1: single-sourced from faf-cli's real scorer — same number `faf
819
+ // score` (CLI) and the championship handler emit. The legacy file-
820
+ // presence pseudo-score (40 + 30 + 15 + 14, capped at 100) is dead.
821
+ // Headline format carries both `FAF SCORE: <n>/100` AND `(<n>%)` so the
822
+ // AERO parity regex AND any consumer scanning for the legacy `\d+%`
823
+ // form continue to match. Invalid/unreadable .faf paths return an
824
+ // honest `0/100 (0%)` with a diagnostic — no fake numbers, no crash.
825
+ const cwd = this.getProjectPath(args?.path);
826
+ const { findFafFile, readFafRaw, scoreFafYaml, getNextTier } = await faf_cli_bridge_js_1.fafCli;
827
+ const fafPath = findFafFile(cwd);
828
+ if (!fafPath) {
829
+ return {
830
+ content: [
831
+ {
832
+ type: 'text',
833
+ text: `FAF SCORE: 0/100 (0%) ♡ no .faf\n\n` +
834
+ `No \`.faf\` found in \`${cwd}\`.\n` +
835
+ `Run \`faf_init\` to create one — then \`faf_score\` reports the real score.`,
836
+ },
837
+ ],
838
+ };
839
+ }
840
+ // Strip ANSI from tier indicator (faf-cli emits colored glyphs).
841
+ // eslint-disable-next-line no-control-regex
842
+ const strip = (s) => s.replace(/\[[0-9;]*m/g, '').trim();
843
+ let raw;
805
844
  try {
806
- const fs = await import('fs').then(m => m.promises);
807
- const path = await import('path');
808
- // Get current working directory - uses path param or session context
809
- const cwd = this.getProjectPath(args?.path);
810
- // Score calculation components
811
- let score = 0;
812
- const details = [];
813
- // 1. Check for FAF file (40 points) - v1.2.0: project.faf, *.faf, or .faf
814
- const fafResult = await (0, faf_file_finder_js_1.findFafFile)(cwd);
815
- let hasFaf = false;
816
- if (fafResult) {
817
- hasFaf = true;
818
- score += 40;
819
- details.push(`✅ ${fafResult.filename} present (+40)`);
820
- }
821
- else {
822
- details.push('❌ FAF file missing (0/40)');
823
- }
824
- // 2. Check for CLAUDE.md (30 points)
825
- const claudePath = path.join(cwd, 'CLAUDE.md');
826
- let hasClaude = false;
827
- try {
828
- await fs.access(claudePath);
829
- hasClaude = true;
830
- score += 30;
831
- details.push('✅ CLAUDE.md present (+30)');
832
- }
833
- catch {
834
- details.push('❌ CLAUDE.md missing (0/30)');
835
- }
836
- // 3. Check for README.md (15 points)
837
- const readmePath = path.join(cwd, 'README.md');
838
- let hasReadme = false;
839
- try {
840
- await fs.access(readmePath);
841
- hasReadme = true;
842
- score += 15;
843
- details.push('✅ README.md present (+15)');
844
- }
845
- catch {
846
- details.push('⚠️ README.md missing (0/15)');
847
- }
848
- // 4. Check for package.json or other project files (14 points)
849
- const projectFiles = ['package.json', 'pyproject.toml', 'Cargo.toml', 'go.mod', 'pom.xml'];
850
- let hasProjectFile = false;
851
- for (const file of projectFiles) {
852
- try {
853
- await fs.access(path.join(cwd, file));
854
- hasProjectFile = true;
855
- score += 14;
856
- details.push(`✅ ${file} detected (+14)`);
857
- break;
858
- }
859
- catch {
860
- // Continue checking
861
- }
862
- }
863
- if (!hasProjectFile) {
864
- details.push('⚠️ No project file found (0/14)');
865
- }
866
- // Format the output
867
- let output = '';
868
- if (score >= 100) {
869
- // Perfect score - Trophy
870
- output = `🏎️ FAF SCORE: 100%\n🏆 Trophy\n🏁 Championship Complete!\n\n`;
871
- if (args?.details) {
872
- output += `${details.join('\n')}\n\n`;
873
- output += `🏆 PERFECT SCORE!\n`;
874
- output += `Both .faf and CLAUDE.md are championship-quality!\n`;
875
- output += `\n💡 Note: 🍊 Big Orange is a BADGE awarded separately for excellence beyond metrics.`;
876
- }
877
- }
878
- else {
879
- // Regular score - FAF standard tiers
880
- const percentage = Math.min(score, 100);
881
- let rating = '';
882
- let emoji = '';
883
- if (percentage >= 99) {
884
- rating = 'Gold';
885
- emoji = '🥇';
886
- }
887
- else if (percentage >= 95) {
888
- rating = 'Silver';
889
- emoji = '🥈';
890
- }
891
- else if (percentage >= 85) {
892
- rating = 'Bronze';
893
- emoji = '🥉';
894
- }
895
- else if (percentage >= 70) {
896
- rating = 'Green';
897
- emoji = '🟢';
898
- }
899
- else if (percentage >= 55) {
900
- rating = 'Yellow';
901
- emoji = '🟡';
902
- }
903
- else {
904
- rating = 'Red';
905
- emoji = '🔴';
906
- }
907
- // The 3-line killer display
908
- output = `📊 FAF SCORE: ${percentage}%\n${emoji} ${rating}\n🏁 AI-Ready: ${percentage >= 85 ? 'Yes' : 'Building'}\n`;
909
- if (args?.details) {
910
- output += `\n${details.join('\n')}`;
911
- if (percentage < 100) {
912
- output += `\n\n💡 Tips to improve:\n`;
913
- if (!hasFaf)
914
- output += `- Create .faf file with project context\n`;
915
- if (!hasClaude)
916
- output += `- Add CLAUDE.md for AI instructions\n`;
917
- if (!hasReadme)
918
- output += `- Include README.md for documentation\n`;
919
- if (!hasProjectFile)
920
- output += `- Add project configuration file\n`;
921
- }
922
- }
923
- }
845
+ raw = readFafRaw(fafPath);
846
+ }
847
+ catch (error) {
924
848
  return {
925
- content: [{
849
+ content: [
850
+ {
926
851
  type: 'text',
927
- text: output
928
- }]
852
+ text: `FAF SCORE: 0/100 (0%) ○ UNREADABLE\n\n` +
853
+ `Could not read \`${fafPath}\`: ${error?.message ?? String(error)}`,
854
+ },
855
+ ],
856
+ isError: true,
929
857
  };
930
858
  }
859
+ let result;
860
+ try {
861
+ result = scoreFafYaml(raw);
862
+ }
931
863
  catch (error) {
932
- // Fallback to displaying a motivational score
864
+ // Invalid .faf content (malformed YAML, etc.) — honest 0 score with a
865
+ // diagnostic, not a fake number. The output still carries `0%` so
866
+ // downstream regex matchers like `/\d+%/` find a percentage token.
933
867
  return {
934
- content: [{
868
+ content: [
869
+ {
935
870
  type: 'text',
936
- text: `📊 FAF SCORE: 92%\n⭐ Excellence Building\n🏁 Keep Going!\n\n${args?.details ? 'Unable to analyze project files, but your commitment to excellence is clear!' : ''}`
937
- }]
871
+ text: `FAF SCORE: 0/100 (0%) ○ INVALID\n\n` +
872
+ `\`${fafPath}\` couldn't be parsed as a valid .faf YAML:\n` +
873
+ ` ${error?.message ?? String(error)}\n\n` +
874
+ `Re-run \`faf_init\` to regenerate a valid file.`,
875
+ },
876
+ ],
877
+ isError: true,
938
878
  };
939
879
  }
880
+ const score = result.score;
881
+ const tierDisplay = strip(result.tier.indicator);
882
+ const next = getNextTier(score);
883
+ const nextTierDisplay = next ? `${strip(next.indicator)} (${next.threshold}%)` : null;
884
+ // Progress bar — same width/style as the championship handler.
885
+ const barWidth = 24;
886
+ const filled = Math.max(0, Math.min(barWidth, Math.round((score / 100) * barWidth)));
887
+ const progressBar = '█'.repeat(filled) + '░'.repeat(barWidth - filled);
888
+ // Headline carries both `/100` AND `(%)` so multiple matchers stay happy.
889
+ let output = `FAF SCORE: ${score}/100 (${score}%) ${tierDisplay}\n` +
890
+ `${progressBar} ${score}%\n` +
891
+ `${result.populated}/${result.total} slots populated` +
892
+ (nextTierDisplay ? ` · next: ${nextTierDisplay}` : ' · top tier') +
893
+ `\n\n` +
894
+ `Scored by faf-cli — the same context your AI reads.`;
895
+ if (args?.details) {
896
+ const populatedSlots = Object.entries(result.slots)
897
+ .filter(([, state]) => state === 'populated')
898
+ .map(([slot]) => slot);
899
+ const emptySlots = Object.entries(result.slots)
900
+ .filter(([, state]) => state === 'empty')
901
+ .map(([slot]) => slot);
902
+ const ignoredSlots = Object.entries(result.slots)
903
+ .filter(([, state]) => state === 'slotignored')
904
+ .map(([slot]) => slot);
905
+ output += `\n\n--- Slot breakdown ---\n`;
906
+ output += `Populated (${populatedSlots.length}): ${populatedSlots.join(', ') || '(none)'}\n`;
907
+ output += `Empty (${emptySlots.length}): ${emptySlots.join(', ') || '(none)'}\n`;
908
+ output += `Ignored (${ignoredSlots.length}): ${ignoredSlots.join(', ') || '(none)'}`;
909
+ if (score < 100 && emptySlots.length > 0) {
910
+ output += `\n\nTip: fill empty slots or mark them \`slotignored\` to climb tiers. Slot-by-slot detail: \`faf score\` (CLI).`;
911
+ }
912
+ }
913
+ return {
914
+ content: [
915
+ {
916
+ type: 'text',
917
+ text: output,
918
+ },
919
+ ],
920
+ };
940
921
  }
941
922
  async handleFafInit(args) {
942
923
  // Native implementation - creates project.faf with Pomelli-simple path resolution!
@@ -994,7 +975,7 @@ output: Championship Performance
994
975
 
995
976
  # Quick Context
996
977
  working_directory: ${targetDir}
997
- initialized_by: claude-faf-mcp${projectData._friday_feature ? `\nfriday_feature: ${projectData._friday_feature}` : ''}
978
+ initialized_by: faf-mcp${projectData._friday_feature ? `\nfriday_feature: ${projectData._friday_feature}` : ''}
998
979
  vitamin_context: true
999
980
  faffless: true
1000
981
 
@@ -1186,12 +1167,12 @@ package_manager: ${projectData.package_manager}` : ''}
1186
1167
  async handleFafAbout(_args) {
1187
1168
  // Stop FAFfing about and get the facts!
1188
1169
  const packageInfo = {
1189
- name: 'claude-faf-mcp',
1170
+ name: 'faf-mcp',
1190
1171
  version: version_1.VERSION,
1191
1172
  description: 'We ARE the C in MCP. I⚡🍊 - The formula that changes everything.',
1192
1173
  author: 'FAF Team (team@faf.one)',
1193
1174
  website: 'https://faf.one',
1194
- npm: 'https://www.npmjs.com/package/claude-faf-mcp'
1175
+ npm: 'https://www.npmjs.com/package/faf-mcp'
1195
1176
  };
1196
1177
  const aboutText = `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1197
1178
  🤖 .faf = project DNA for AI
@@ -1293,7 +1274,7 @@ REMEMBER: Always use ".faf" with the dot - it's a FORMAT!
1293
1274
  // Check for existing FAF file (v1.2.0: project.faf, *.faf, or .faf)
1294
1275
  const fafResult = await (0, faf_file_finder_js_1.findFafFile)(cwd);
1295
1276
  const hasFaf = fafResult !== null;
1296
- const debugOutput = `🔍 Claude FAF MCP Server Debug Information:
1277
+ const debugOutput = `🔍 faf-mcp Debug Information:
1297
1278
 
1298
1279
  📂 Working Directory: ${debugInfo.workingDirectory}
1299
1280
  ✏️ Write Permissions: ${debugInfo.canWrite ? '✅ Yes' : '❌ No'}
@@ -1400,7 +1381,7 @@ ${debugInfo.permissions.fafError ? ` FAF Error: ${debugInfo.permissions.fafErr
1400
1381
  };
1401
1382
  }
1402
1383
  async handleFafGuide(_args) {
1403
- const guide = `# FAF MCP - Claude Desktop Guide
1384
+ const guide = `# FAF MCP - your MCP host Guide
1404
1385
 
1405
1386
  ## Path Convention (CRITICAL)
1406
1387
  **Default**: \`~/Projects/[project-name]/project.faf\`
@@ -2220,7 +2201,7 @@ version: ${version_1.VERSION}
2220
2201
 
2221
2202
  # Quick Context
2222
2203
  working_directory: ${cwd}
2223
- initialized_by: claude-faf-mcp-auto
2204
+ initialized_by: faf-mcp-auto
2224
2205
  vitamin_context: true
2225
2206
  faffless: true
2226
2207
  `;
@@ -2729,7 +2710,7 @@ Use force: true to overwrite, or use faf_enhance to modify.`
2729
2710
  type: projectType,
2730
2711
  generated: new Date().toISOString(),
2731
2712
  version: version_1.VERSION,
2732
- initialized_by: 'claude-faf-mcp-quick'
2713
+ initialized_by: 'faf-mcp-quick'
2733
2714
  };
2734
2715
  if (framework && framework !== 'none') {
2735
2716
  fafData.stack = { frontend: framework };
@@ -2820,7 +2801,7 @@ Use force: true to overwrite, or use faf_enhance to modify.`
2820
2801
  // Check 1: MCP Version
2821
2802
  results.push({
2822
2803
  status: 'ok',
2823
- message: `claude-faf-mcp version: ${version_1.VERSION}`
2804
+ message: `faf-mcp version: ${version_1.VERSION}`
2824
2805
  });
2825
2806
  // Check 2: .faf file exists
2826
2807
  const fafResult = await (0, faf_file_finder_js_1.findFafFile)(cwd);