faf-mcp 2.1.0 → 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +40 -1
- package/CLAUDE.md +1 -1
- package/README.md +14 -13
- package/dist/src/faf-core/commands/auto.d.ts +2 -2
- package/dist/src/faf-core/commands/auto.js.map +1 -1
- package/dist/src/faf-core/commands/innit.d.ts +2 -2
- package/dist/src/faf-core/commands/innit.js.map +1 -1
- package/dist/src/faf-core/commands/score.d.ts +1 -1
- package/dist/src/faf-core/commands/score.js.map +1 -1
- package/dist/src/faf-core/compiler/faf-compiler.js +65 -6
- package/dist/src/faf-core/compiler/faf-compiler.js.map +1 -1
- package/dist/src/faf-core/generators/faf-generator-championship.js.map +1 -1
- package/dist/src/handlers/championship-tools.d.ts +1 -1
- package/dist/src/handlers/cloud-handler.js +1 -1
- package/dist/src/handlers/cloud-handler.js.map +1 -1
- package/dist/src/handlers/fileHandler.d.ts +1 -1
- package/dist/src/handlers/resources.d.ts +1 -1
- package/dist/src/handlers/tool-registry.d.ts +2 -2
- package/dist/src/handlers/tools.d.ts +1 -1
- package/dist/src/handlers/tools.js +106 -125
- package/dist/src/handlers/tools.js.map +1 -1
- package/dist/src/server.d.ts +5 -0
- package/dist/src/server.js +16 -2
- package/dist/src/server.js.map +1 -1
- package/dist/src/types/mcp-tools.d.ts +2 -2
- package/dist/src/utils/cli-detector.d.ts +1 -4
- package/dist/src/utils/cli-detector.js +19 -2
- package/dist/src/utils/cli-detector.js.map +1 -1
- package/dist/src/utils/faf-cli-bridge.d.ts +47 -0
- package/dist/src/utils/faf-cli-bridge.js +101 -0
- package/dist/src/utils/faf-cli-bridge.js.map +1 -0
- package/package.json +4 -7
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* Central registry for all MCP tools with metadata
|
|
4
4
|
* Championship-grade organization
|
|
5
5
|
*/
|
|
6
|
-
import { Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
7
|
-
import { FafMcpTool } from '../types/mcp-tools.js';
|
|
6
|
+
import type { Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
7
|
+
import type { FafMcpTool } from '../types/mcp-tools.js';
|
|
8
8
|
/**
|
|
9
9
|
* Check if a tool should be visible based on configuration
|
|
10
10
|
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CallToolResult, Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
1
|
+
import type { CallToolResult, Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
2
2
|
import { FafEngineAdapter } from './engine-adapter';
|
|
3
3
|
export declare class FafToolHandler {
|
|
4
4
|
private engineAdapter;
|
|
@@ -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) {
|
|
@@ -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
|
-
|
|
807
|
-
|
|
808
|
-
|
|
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:
|
|
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
|
-
//
|
|
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:
|
|
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!
|