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.
- package/CHANGELOG.md +52 -0
- package/CLAUDE.md +23 -21
- package/README.md +27 -24
- package/dist/src/cli.js +1 -1
- package/dist/src/cli.js.map +1 -1
- 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/generators/faf-generator-championship.js.map +1 -1
- package/dist/src/faf-core/parsers/agents-parser.d.ts +1 -1
- package/dist/src/faf-core/parsers/agents-parser.js +2 -2
- package/dist/src/faf-core/parsers/agents-parser.js.map +1 -1
- package/dist/src/faf-core/parsers/conductor-parser.d.ts +1 -1
- package/dist/src/faf-core/parsers/conductor-parser.js +1 -1
- package/dist/src/faf-core/parsers/cursorrules-parser.d.ts +1 -1
- package/dist/src/faf-core/parsers/cursorrules-parser.js +2 -2
- package/dist/src/faf-core/parsers/cursorrules-parser.js.map +1 -1
- package/dist/src/faf-core/parsers/faf-git-generator.d.ts +1 -1
- package/dist/src/faf-core/parsers/faf-git-generator.js +2 -2
- package/dist/src/faf-core/parsers/faf-git-generator.js.map +1 -1
- package/dist/src/faf-core/parsers/gemini-parser.d.ts +1 -1
- package/dist/src/faf-core/parsers/gemini-parser.js +1 -1
- package/dist/src/faf-core/parsers/github-extractor.d.ts +1 -1
- package/dist/src/faf-core/parsers/github-extractor.js +2 -2
- package/dist/src/faf-core/parsers/github-extractor.js.map +1 -1
- package/dist/src/faf-core/parsers/slot-counter.d.ts +1 -1
- package/dist/src/faf-core/parsers/slot-counter.js +1 -1
- package/dist/src/handlers/championship-tools.d.ts +6 -13
- package/dist/src/handlers/championship-tools.js +122 -530
- package/dist/src/handlers/championship-tools.js.map +1 -1
- package/dist/src/handlers/engine-adapter.js +3 -3
- package/dist/src/handlers/engine-adapter.js.map +1 -1
- package/dist/src/handlers/fileHandler.d.ts +1 -1
- package/dist/src/handlers/fileHandler.js +1 -1
- package/dist/src/handlers/fileHandler.js.map +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 +116 -135
- package/dist/src/handlers/tools.js.map +1 -1
- package/dist/src/index.js +2 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/server.d.ts +8 -3
- package/dist/src/server.js +16 -10
- package/dist/src/server.js.map +1 -1
- package/dist/src/test-all-functions.js +1 -1
- package/dist/src/test-all-functions.js.map +1 -1
- package/dist/src/types/mcp-tools.d.ts +2 -2
- package/dist/src/types/tool-visibility.js +1 -1
- package/dist/src/types/tool-visibility.js.map +1 -1
- package/dist/src/utils/cli-detector.d.ts +3 -6
- package/dist/src/utils/cli-detector.js +19 -2
- package/dist/src/utils/cli-detector.js.map +1 -1
- package/dist/src/utils/display-protocol.d.ts +2 -2
- package/dist/src/utils/display-protocol.js +4 -4
- package/dist/src/utils/display-protocol.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/dist/src/utils/visual-style.js +1 -1
- package/dist/src/utils/visual-style.js.map +1 -1
- package/package.json +7 -10
- package/project.faf +31 -133
- package/scripts/check-stylesheet-drift.mjs +114 -0
|
@@ -53,13 +53,16 @@ const version_js_1 = require("../version.js");
|
|
|
53
53
|
const visibility_js_1 = require("../config/visibility.js");
|
|
54
54
|
const tool_registry_js_1 = require("./tool-registry.js");
|
|
55
55
|
const auto_path_detection_js_1 = require("../utils/auto-path-detection.js");
|
|
56
|
+
// Single source: faf-cli's real scorer + project.html renderer. handleDisplay
|
|
57
|
+
// MUST use these — never reimplement scoring or HTML (kills divergence).
|
|
58
|
+
const faf_cli_1 = require("faf-cli");
|
|
56
59
|
// 🏆 FAF Score uses the 3-3-1 system: 3 lines, 3 words, 1 emoji!
|
|
57
60
|
// 💥 Format-Finder (FF) integration for GAME-CHANGING stack detection!
|
|
58
|
-
//
|
|
61
|
+
// your MCP host filesystem context instruction
|
|
59
62
|
const FILESYSTEM_CONTEXT_INSTRUCTION = `
|
|
60
63
|
CRITICAL CONTEXT FOR CLAUDE DESKTOP:
|
|
61
64
|
- This MCP server executes on the USER'S REAL FILESYSTEM
|
|
62
|
-
- NOT in
|
|
65
|
+
- NOT in your MCP host's container (/home/claude)
|
|
63
66
|
- All paths are REAL system paths (/Users/username/ on macOS, /home/username/ on Linux, C:\\ on Windows)
|
|
64
67
|
- When discussing file locations, use the user's actual filesystem paths, not container paths
|
|
65
68
|
`;
|
|
@@ -201,7 +204,7 @@ Working on REAL filesystem: ${targetDir}
|
|
|
201
204
|
},
|
|
202
205
|
{
|
|
203
206
|
name: 'faf_display',
|
|
204
|
-
description: '🖼️
|
|
207
|
+
description: '🖼️ Render project.faf → project.html (faf-cli\'s real scorer + renderer — the same context your AI reads)',
|
|
205
208
|
inputSchema: {
|
|
206
209
|
type: 'object',
|
|
207
210
|
properties: {
|
|
@@ -520,7 +523,7 @@ Working on REAL filesystem: ${targetDir}
|
|
|
520
523
|
},
|
|
521
524
|
{
|
|
522
525
|
name: 'faf_guide',
|
|
523
|
-
description: 'FAF MCP usage guide for
|
|
526
|
+
description: 'FAF MCP usage guide for your MCP host - Projects convention, path resolution, and UX patterns',
|
|
524
527
|
inputSchema: {
|
|
525
528
|
type: 'object',
|
|
526
529
|
properties: {},
|
|
@@ -1028,11 +1031,11 @@ Working on REAL filesystem: ${targetDir}
|
|
|
1028
1031
|
async handleInit(args) {
|
|
1029
1032
|
try {
|
|
1030
1033
|
const dir = args.directory || process.cwd();
|
|
1031
|
-
// 🚨
|
|
1034
|
+
// 🚨 your MCP host Protection: Detect root filesystem
|
|
1032
1035
|
if (dir === '/' || dir === '') {
|
|
1033
1036
|
const helpMessage = `🚨 Directory Required!
|
|
1034
1037
|
|
|
1035
|
-
|
|
1038
|
+
your MCP host needs a target directory:
|
|
1036
1039
|
|
|
1037
1040
|
**Usage**:
|
|
1038
1041
|
faf_init directory=/Users/wolfejam/my-project
|
|
@@ -1040,7 +1043,7 @@ Claude Desktop needs a target directory:
|
|
|
1040
1043
|
**Example**:
|
|
1041
1044
|
faf_init directory=/Users/wolfejam/GALLERY-SVELTE
|
|
1042
1045
|
|
|
1043
|
-
📁 Can't determine working directory automatically in
|
|
1046
|
+
📁 Can't determine working directory automatically in your MCP host.
|
|
1044
1047
|
⚠️ Root filesystem (/) is read-only - specify your project path!`;
|
|
1045
1048
|
return await this.formatResult('🚀 FAF Init', helpMessage);
|
|
1046
1049
|
}
|
|
@@ -1073,117 +1076,24 @@ Claude Desktop needs a target directory:
|
|
|
1073
1076
|
}
|
|
1074
1077
|
}
|
|
1075
1078
|
async handleDisplay(args) {
|
|
1076
|
-
//
|
|
1079
|
+
// Single-sourced render of project.faf → HTML. Same pipeline as
|
|
1080
|
+
// faf-cli's `faf show` / `faf export --html`: the real scorer + the
|
|
1081
|
+
// canonical project.html renderer. No reimplementation, no fake
|
|
1082
|
+
// file-presence score, no divergent template.
|
|
1077
1083
|
const targetDir = args?.directory || process.cwd();
|
|
1078
|
-
const
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
const
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
const hasReadme = await this.fileExists(path.join(targetDir, 'README.md'));
|
|
1088
|
-
if (hasReadme)
|
|
1089
|
-
score += 15;
|
|
1090
|
-
const hasPackage = await this.fileExists(path.join(targetDir, 'package.json'));
|
|
1091
|
-
if (hasPackage)
|
|
1092
|
-
score += 14;
|
|
1093
|
-
// Generate 3-3-1 display
|
|
1094
|
-
const barWidth = 24;
|
|
1095
|
-
const filled = Math.round((score / 100) * barWidth);
|
|
1096
|
-
const empty = barWidth - filled;
|
|
1097
|
-
const progressBar = '█'.repeat(filled) + '░'.repeat(empty);
|
|
1098
|
-
let status = '';
|
|
1099
|
-
let emoji = '';
|
|
1100
|
-
if (score >= 99) {
|
|
1101
|
-
status = 'Trophy!';
|
|
1102
|
-
emoji = '🏆';
|
|
1103
|
-
}
|
|
1104
|
-
else if (score >= 90) {
|
|
1105
|
-
status = 'Excellent!';
|
|
1106
|
-
emoji = '🧡';
|
|
1107
|
-
}
|
|
1108
|
-
else if (score >= 70) {
|
|
1109
|
-
status = 'Very Good';
|
|
1110
|
-
emoji = '⭐';
|
|
1111
|
-
}
|
|
1112
|
-
else if (score >= 60) {
|
|
1113
|
-
status = 'Good Progress';
|
|
1114
|
-
emoji = '📈';
|
|
1115
|
-
}
|
|
1116
|
-
else {
|
|
1117
|
-
status = 'Building Up';
|
|
1118
|
-
emoji = '🚀';
|
|
1119
|
-
}
|
|
1120
|
-
// Create HTML with ACTUAL output display
|
|
1121
|
-
const html = `<!DOCTYPE html>
|
|
1122
|
-
<html>
|
|
1123
|
-
<head>
|
|
1124
|
-
<meta charset="UTF-8">
|
|
1125
|
-
<title>FAF Score - ${path.basename(targetDir)}</title>
|
|
1126
|
-
<style>
|
|
1127
|
-
body {
|
|
1128
|
-
background: #000;
|
|
1129
|
-
color: #fff;
|
|
1130
|
-
font-family: 'Courier New', 'Monaco', 'Menlo', monospace;
|
|
1131
|
-
font-size: 14px;
|
|
1132
|
-
padding: 40px;
|
|
1133
|
-
line-height: 1.4;
|
|
1134
|
-
}
|
|
1135
|
-
pre {
|
|
1136
|
-
background: #111;
|
|
1137
|
-
padding: 30px;
|
|
1138
|
-
border-radius: 8px;
|
|
1139
|
-
border: 1px solid #333;
|
|
1140
|
-
font-family: inherit;
|
|
1141
|
-
white-space: pre;
|
|
1142
|
-
word-spacing: normal;
|
|
1143
|
-
letter-spacing: normal;
|
|
1144
|
-
}
|
|
1145
|
-
.cyan { color: #00ffff; font-weight: bold; }
|
|
1146
|
-
.orange { color: #ff6b35; }
|
|
1147
|
-
.green { color: #00bf63; }
|
|
1148
|
-
h1 { color: #ff6b35; }
|
|
1149
|
-
.footer {
|
|
1150
|
-
border-top: 1px solid #666;
|
|
1151
|
-
border-bottom: 1px solid #666;
|
|
1152
|
-
padding: 10px 0;
|
|
1153
|
-
margin: 20px 0;
|
|
1154
|
-
font-family: inherit;
|
|
1155
|
-
}
|
|
1156
|
-
</style>
|
|
1157
|
-
</head>
|
|
1158
|
-
<body>
|
|
1159
|
-
<h1>FAF Score Display - ACTUAL Output!</h1>
|
|
1160
|
-
<p>Generated: ${new Date().toISOString()}</p>
|
|
1161
|
-
<pre>📊 FAF Score (${path.basename(targetDir)}) 🏎️ 1ms
|
|
1162
|
-
|
|
1163
|
-
🧡 <span class="cyan">Score: ${score}/100</span>
|
|
1164
|
-
${progressBar} ${score}%
|
|
1165
|
-
${emoji} <span class="cyan">Status: ${status}</span>
|
|
1166
|
-
|
|
1167
|
-
Breakdown:
|
|
1168
|
-
• FAF: ${fafResult ? `☑️ ${fafResult.filename}` : '❌'} ${fafResult ? '40pts' : 'Missing'}
|
|
1169
|
-
• CLAUDE.md: ${hasClaude ? '☑️' : '❌'} ${hasClaude ? '30pts' : 'Missing'}
|
|
1170
|
-
• README.md: ${hasReadme ? '☑️' : '❌'} ${hasReadme ? '15pts' : 'Missing'}
|
|
1171
|
-
• package.json: ${hasPackage ? '☑️' : '❌'} ${hasPackage ? '14pts' : 'Missing'}
|
|
1172
|
-
|
|
1173
|
-
<div class="footer">━━━━━━━━━━━━━━━━━━━━━
|
|
1174
|
-
AI-Readiness: ${score}% ${emoji}
|
|
1175
|
-
━━━━━━━━━━━━━━━━━━━━━</div></pre>
|
|
1176
|
-
|
|
1177
|
-
<p style="color:#666; margin-top:40px;">
|
|
1178
|
-
This HTML shows EXACTLY what FAF outputs - no Claude interpretation!<br>
|
|
1179
|
-
The score, colors, and footer are all REAL and VISIBLE.
|
|
1180
|
-
</p>
|
|
1181
|
-
</body>
|
|
1182
|
-
</html>`;
|
|
1183
|
-
// Write the HTML file
|
|
1084
|
+
const fafPath = (0, faf_cli_1.findFafFile)(targetDir);
|
|
1085
|
+
if (!fafPath) {
|
|
1086
|
+
return await this.formatResult('🖼️ FAF Display', `No .faf found in ${targetDir}\n\n` +
|
|
1087
|
+
`Run faf_init to create one, then faf_display to render it.`);
|
|
1088
|
+
}
|
|
1089
|
+
const data = (0, faf_cli_1.readFaf)(fafPath);
|
|
1090
|
+
const result = (0, faf_cli_1.scoreFafYaml)((0, faf_cli_1.readFafRaw)(fafPath));
|
|
1091
|
+
const html = (0, faf_cli_1.generateProjectHtml)(data, result, fafPath);
|
|
1092
|
+
const outputPath = args?.output || path.join(targetDir, 'project.html');
|
|
1184
1093
|
await fs.writeFile(outputPath, html);
|
|
1185
|
-
return await this.formatResult('🖼️ FAF Display Generated', `
|
|
1186
|
-
`
|
|
1094
|
+
return await this.formatResult('🖼️ FAF Display Generated', `Rendered project.faf → ${outputPath}\n\n` +
|
|
1095
|
+
`Single-sourced from faf-cli — the real scorer and renderer, ` +
|
|
1096
|
+
`the same truth your AI reads.\n` +
|
|
1187
1097
|
`file://${outputPath}`);
|
|
1188
1098
|
}
|
|
1189
1099
|
async handleShow(args) {
|
|
@@ -1191,82 +1101,33 @@ AI-Readiness: ${score}% ${emoji}
|
|
|
1191
1101
|
const command = args.command || 'score';
|
|
1192
1102
|
const directory = args.directory || process.cwd();
|
|
1193
1103
|
if (command === 'score') {
|
|
1194
|
-
// Get the clean score data
|
|
1195
1104
|
const targetDir = directory;
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
const
|
|
1204
|
-
|
|
1205
|
-
if (hasFaf)
|
|
1206
|
-
score += 40;
|
|
1207
|
-
hasClaude = await this.fileExists(path.join(targetDir, 'CLAUDE.md'));
|
|
1208
|
-
if (hasClaude)
|
|
1209
|
-
score += 30;
|
|
1210
|
-
hasReadme = await this.fileExists(path.join(targetDir, 'README.md'));
|
|
1211
|
-
if (hasReadme)
|
|
1212
|
-
score += 15;
|
|
1213
|
-
hasPackage = await this.fileExists(path.join(targetDir, 'package.json'));
|
|
1214
|
-
if (hasPackage)
|
|
1215
|
-
score += 14;
|
|
1216
|
-
// Build CLEAN markdown - no wrappers!
|
|
1217
|
-
const progressBar = '█'.repeat(Math.floor(score * 24 / 100)) + '░'.repeat(24 - Math.floor(score * 24 / 100));
|
|
1218
|
-
// Mk3 Canonical Tier System
|
|
1219
|
-
const { medal: statusEmoji, status: statusText } = this.getScoreMedal(score);
|
|
1220
|
-
// Build clean output - just markdown, no wrappers!
|
|
1105
|
+
const s = this.getFafScore(targetDir);
|
|
1106
|
+
if (!s.found) {
|
|
1107
|
+
return display_protocol_js_1.DisplayProtocol.createResponse(`# FAF Score Card\n\n` +
|
|
1108
|
+
`No \`.faf\` found in \`${targetDir}\`.\n\n` +
|
|
1109
|
+
`Run \`faf_init\` to create one — then \`faf_show\` renders the real score.`, { tool: 'faf_show', command });
|
|
1110
|
+
}
|
|
1111
|
+
const barWidth = 24;
|
|
1112
|
+
const filled = Math.round((s.score / 100) * barWidth);
|
|
1113
|
+
const progressBar = '█'.repeat(filled) + '░'.repeat(barWidth - filled);
|
|
1221
1114
|
const output = `# FAF Score Card
|
|
1222
1115
|
|
|
1223
|
-
##
|
|
1224
|
-
|
|
1225
|
-
${progressBar} ${score}%
|
|
1226
|
-
|
|
1227
|
-
### ${statusEmoji} **Status: ${statusText}**
|
|
1228
|
-
|
|
1229
|
-
---
|
|
1230
|
-
|
|
1231
|
-
## 📊 Performance Breakdown
|
|
1232
|
-
|
|
1233
|
-
| Component | Status | Points | Performance |
|
|
1234
|
-
|-----------|--------|--------|-------------|
|
|
1235
|
-
| **.faf** | ${hasFaf ? '✅ **ACTIVE**' : '❌ **MISSING**'} | ${hasFaf ? '40' : '0'}pts | ${hasFaf ? 'Core config synchronized' : 'Create with faf_init'} |
|
|
1236
|
-
| **CLAUDE.md** | ${hasClaude ? '✅ **SYNCED**' : '❌ **MISSING**'} | ${hasClaude ? '30' : '0'}pts | ${hasClaude ? 'AI documentation live' : 'Generate with faf_sync'} |
|
|
1237
|
-
| **README.md** | ${hasReadme ? '✅ **READY**' : '❌ **MISSING**'} | ${hasReadme ? '15' : '0'}pts | ${hasReadme ? 'Project docs complete' : 'Add for extra points'} |
|
|
1238
|
-
| **package.json** | ${hasPackage ? '✅ **FOUND**' : '❌ **MISSING**'} | ${hasPackage ? '14' : '0'}pts | ${hasPackage ? 'Dependencies tracked' : 'Add for full score'} |
|
|
1239
|
-
|
|
1240
|
-
---
|
|
1116
|
+
## **${s.score}/100** ${s.tierDisplay}
|
|
1241
1117
|
|
|
1242
|
-
|
|
1118
|
+
${progressBar} ${s.score}%
|
|
1243
1119
|
|
|
1244
|
-
|
|
1245
|
-
${hasFaf && hasClaude ? '- Bi-directional sync active\n' : ''}${hasClaude ? '- AI-Ready Documentation: Full CLAUDE.md integration\n' : ''}${hasFaf ? '- Core Systems: FAF foundation in place\n' : ''}${hasReadme ? '- Documentation: README.md providing clarity\n' : ''}${hasPackage ? '- Dependencies: package.json tracking enabled' : ''}
|
|
1120
|
+
${s.populated}/${s.total} slots populated${s.nextTier ? ` · next: ${s.nextTier}` : ' · top tier'}
|
|
1246
1121
|
|
|
1247
1122
|
---
|
|
1248
1123
|
|
|
1249
|
-
|
|
1124
|
+
> Scored by faf-cli's real scorer — the same context your AI reads.
|
|
1125
|
+
> For slot-by-slot detail run \`faf score\` (faf-cli).
|
|
1250
1126
|
|
|
1251
|
-
|
|
1252
|
-
faf_bi_sync # Keep files synchronized
|
|
1253
|
-
faf_enhance # AI-powered improvements
|
|
1254
|
-
faf_score --save # Save this scorecard
|
|
1255
|
-
\`\`\`
|
|
1256
|
-
|
|
1257
|
-
---
|
|
1258
|
-
|
|
1259
|
-
> "Context is everything. FAF delivers it."
|
|
1260
|
-
|
|
1261
|
-
---
|
|
1262
|
-
|
|
1263
|
-
**AI-Readiness: ${score}%** ${score >= 99 ? '🏆' : ''}`;
|
|
1264
|
-
// 🍫 CHOCOLATE ORANGE - UNWRAPPED AND READY!
|
|
1265
|
-
// Use DisplayProtocol for consistent global rendering
|
|
1127
|
+
**AI-Readiness: ${s.score}%**`;
|
|
1266
1128
|
return display_protocol_js_1.DisplayProtocol.createResponse(output, {
|
|
1267
1129
|
tool: 'faf_show',
|
|
1268
|
-
command
|
|
1269
|
-
timestamp: new Date().toISOString()
|
|
1130
|
+
command,
|
|
1270
1131
|
});
|
|
1271
1132
|
}
|
|
1272
1133
|
// Default fallback - also use DisplayProtocol
|
|
@@ -1276,249 +1137,52 @@ faf_score --save # Save this scorecard
|
|
|
1276
1137
|
const targetDir = args?.directory || process.cwd();
|
|
1277
1138
|
const saveCard = args?.save === true;
|
|
1278
1139
|
const format = args?.format || 'markdown';
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
let hasFaf = false;
|
|
1283
|
-
let hasClaude = false;
|
|
1284
|
-
let hasReadme = false;
|
|
1285
|
-
let hasPackage = false;
|
|
1286
|
-
try {
|
|
1287
|
-
this.fafEngine.setWorkingDirectory(targetDir);
|
|
1288
|
-
const result = await this.fafEngine.callEngine('score', ['--json']);
|
|
1289
|
-
if (result.success && result.data) {
|
|
1290
|
-
// Extract score from engine response
|
|
1291
|
-
if (typeof result.data.score === 'number') {
|
|
1292
|
-
score = result.data.score;
|
|
1293
|
-
// Engine should tell us what files contributed
|
|
1294
|
-
hasFaf = result.data.files?.faf || false;
|
|
1295
|
-
hasClaude = result.data.files?.claude || false;
|
|
1296
|
-
hasReadme = result.data.files?.readme || false;
|
|
1297
|
-
hasPackage = result.data.files?.package || false;
|
|
1298
|
-
}
|
|
1299
|
-
else {
|
|
1300
|
-
// Parse text output for score
|
|
1301
|
-
const outputText = result.data.output || '';
|
|
1302
|
-
const scoreMatch = outputText.match(/(\d+)%/);
|
|
1303
|
-
if (scoreMatch) {
|
|
1304
|
-
score = parseInt(scoreMatch[1]);
|
|
1305
|
-
}
|
|
1306
|
-
}
|
|
1307
|
-
}
|
|
1308
|
-
}
|
|
1309
|
-
catch (engineError) {
|
|
1310
|
-
console.warn('FAF Engine score failed, using native:', engineError);
|
|
1140
|
+
// HTML delegates to the single-sourced renderer (faf-cli's project.html).
|
|
1141
|
+
if (format === 'html') {
|
|
1142
|
+
return await this.handleDisplay({ directory: targetDir, output: path.join(targetDir, 'SCORE-CARD.html') });
|
|
1311
1143
|
}
|
|
1312
|
-
|
|
1313
|
-
if (
|
|
1314
|
-
|
|
1315
|
-
hasFaf = fafResult !== null;
|
|
1316
|
-
if (hasFaf)
|
|
1317
|
-
score += 40;
|
|
1318
|
-
hasClaude = await this.fileExists(path.join(targetDir, 'CLAUDE.md'));
|
|
1319
|
-
if (hasClaude)
|
|
1320
|
-
score += 30;
|
|
1321
|
-
hasReadme = await this.fileExists(path.join(targetDir, 'README.md'));
|
|
1322
|
-
if (hasReadme)
|
|
1323
|
-
score += 15;
|
|
1324
|
-
hasPackage = await this.fileExists(path.join(targetDir, 'package.json'));
|
|
1325
|
-
if (hasPackage)
|
|
1326
|
-
score += 14;
|
|
1144
|
+
const sc = this.getFafScore(targetDir);
|
|
1145
|
+
if (!sc.found) {
|
|
1146
|
+
return await this.formatResult('🏎️ FAF Score', `No \`.faf\` found in \`${targetDir}\`.\n\nRun \`faf_init\` to create one.`, undefined, targetDir);
|
|
1327
1147
|
}
|
|
1328
|
-
|
|
1148
|
+
const barWidth = 24;
|
|
1149
|
+
const filled = Math.round((sc.score / 100) * barWidth);
|
|
1150
|
+
const progressBar = '█'.repeat(filled) + '░'.repeat(barWidth - filled);
|
|
1329
1151
|
let result = '';
|
|
1330
1152
|
if (format === 'json') {
|
|
1331
|
-
// JSON format
|
|
1332
1153
|
result = JSON.stringify({
|
|
1333
1154
|
project: path.basename(targetDir),
|
|
1334
|
-
score: score,
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
readme: { exists: hasReadme, points: hasReadme ? 15 : 0 },
|
|
1341
|
-
package: { exists: hasPackage, points: hasPackage ? 14 : 0 }
|
|
1342
|
-
},
|
|
1343
|
-
ai_readiness: score,
|
|
1344
|
-
timestamp: new Date().toISOString(),
|
|
1345
|
-
version: version_js_1.VERSION
|
|
1155
|
+
score: sc.score,
|
|
1156
|
+
tier: sc.tierName,
|
|
1157
|
+
slots: { populated: sc.populated, total: sc.total },
|
|
1158
|
+
next_tier: sc.nextTier,
|
|
1159
|
+
source: 'faf-cli',
|
|
1160
|
+
version: version_js_1.VERSION,
|
|
1346
1161
|
}, null, 2);
|
|
1347
1162
|
}
|
|
1348
|
-
else if (format === 'html') {
|
|
1349
|
-
// HTML format (delegate to display handler)
|
|
1350
|
-
return await this.handleDisplay({ directory: targetDir, output: path.join(targetDir, 'SCORE-CARD.html') });
|
|
1351
|
-
}
|
|
1352
1163
|
else if (format === 'ascii') {
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
const progressBar = '█'.repeat(filled) + '░'.repeat(empty);
|
|
1358
|
-
result = `FAF Score: ${score}/100\n`;
|
|
1359
|
-
result += `${progressBar} ${score}%\n`;
|
|
1360
|
-
result += `[.faf: ${hasFaf ? '✓' : 'x'}] [CLAUDE.md: ${hasClaude ? '✓' : 'x'}] [README: ${hasReadme ? '✓' : 'x'}] [package.json: ${hasPackage ? '✓' : 'x'}]`;
|
|
1361
|
-
}
|
|
1362
|
-
else if (showFull) {
|
|
1363
|
-
// Podium Edition: Full Championship Scorecard with detailed metrics
|
|
1364
|
-
const _projectName = path.basename(targetDir);
|
|
1365
|
-
// Calculate section scores based on files present
|
|
1366
|
-
const coreIntelligence = Math.round(((hasFaf ? 25 : 0) +
|
|
1367
|
-
(hasFaf && hasClaude ? 25 : 0) + // Architecture Map (requires both)
|
|
1368
|
-
(hasFaf ? 25 : 0) + // Domain Model
|
|
1369
|
-
(hasFaf ? 25 : 0) // Version Tracking
|
|
1370
|
-
));
|
|
1371
|
-
const contextDelivery = Math.round((25 + // MCP Protocol (always active)
|
|
1372
|
-
25 + // 50 Native Tools (always active)
|
|
1373
|
-
25 + // IANA Format (always active)
|
|
1374
|
-
(hasFaf && hasClaude ? 25 : hasFaf ? 15 : hasClaude ? 10 : 0) // Universal Context
|
|
1375
|
-
));
|
|
1376
|
-
const _performance = 100; // Static for MCP server itself
|
|
1377
|
-
const _standalone = 100; // Static for MCP server itself
|
|
1378
|
-
// Determine status tier
|
|
1379
|
-
// Mk3 Canonical Tier System
|
|
1380
|
-
const { medal: tierEmoji, status: statusTier } = this.getScoreMedal(score);
|
|
1381
|
-
result = ``;
|
|
1382
|
-
result += `# FAF AI-Readiness Score: ${score}/100 ${tierEmoji}\n\n`;
|
|
1383
|
-
result += `**The closer you get to 100% the better AI can assist you.**\n\n`;
|
|
1384
|
-
result += `At 55% you are building your project with half a blueprint and basically flipping a coin with AI. .FAF defines, and AI becomes optimized for Context with the project.faf file.\n\n`;
|
|
1385
|
-
result += `\`\`\`\n`;
|
|
1386
|
-
result += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
|
|
1387
|
-
result += `${tierEmoji} FAF AI-READINESS SCORE: ${score}/100 — ${statusTier}\n`;
|
|
1388
|
-
result += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n`;
|
|
1389
|
-
// Core Intelligence section
|
|
1390
|
-
result += `📊 CORE INTELLIGENCE 🎯 CONTEXT DELIVERY\n`;
|
|
1391
|
-
const bar100 = '[██████] 100%';
|
|
1392
|
-
const _barCore = `[${('█'.repeat(Math.round(coreIntelligence / 100 * 6)) + '░'.repeat(6 - Math.round(coreIntelligence / 100 * 6)))}] ${coreIntelligence}%`;
|
|
1393
|
-
const barContext = `[${('█'.repeat(Math.round(contextDelivery / 100 * 6)) + '░'.repeat(6 - Math.round(contextDelivery / 100 * 6)))}] ${contextDelivery}%`;
|
|
1394
|
-
result += `├─ Project DNA ${hasFaf ? bar100 : '[░░░░░░] 0%'} ├─ MCP Protocol ${bar100}\n`;
|
|
1395
|
-
result += `├─ Architecture Map ${hasFaf && hasClaude ? bar100 : '[░░░░░░] 0%'} ├─ 50 Native Tools ${bar100}\n`;
|
|
1396
|
-
result += `├─ Domain Model ${hasFaf ? bar100 : '[░░░░░░] 0%'} ├─ IANA Format ${bar100}\n`;
|
|
1397
|
-
result += `└─ Version Tracking ${hasFaf ? bar100 : '[░░░░░░] 0%'} └─ Universal Context ${barContext}\n\n`;
|
|
1398
|
-
// Performance section
|
|
1399
|
-
result += `🚀 PERFORMANCE ⚡ STANDALONE OPERATION\n`;
|
|
1400
|
-
result += `├─ 16.2x CLI Speedup ${bar100} ├─ Zero Dependencies ${bar100}\n`;
|
|
1401
|
-
result += `├─ 19ms Avg Execution ${bar100} ├─ Bundled Engine ${bar100}\n`;
|
|
1402
|
-
result += `├─ 50/50 Tools Active ${bar100} ├─ Direct Function ${bar100}\n`;
|
|
1403
|
-
result += `└─ Zero Memory Leaks ${bar100} └─ 14 Bundled Cmds ${bar100}\n\n`;
|
|
1404
|
-
result += `${tierEmoji} project.faf score: ${statusTier}\n`;
|
|
1405
|
-
result += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
|
|
1406
|
-
result += `\`\`\`\n\n`;
|
|
1407
|
-
// Next steps
|
|
1408
|
-
result += `## ⚡ Next Steps\n\n`;
|
|
1409
|
-
if (!hasFaf) {
|
|
1410
|
-
result += `🚀 **Initialize FAF**: Run \`faf_init\` to create project.faf (+40 points)\n\n`;
|
|
1411
|
-
}
|
|
1412
|
-
if (!hasClaude) {
|
|
1413
|
-
result += `📝 **Generate CLAUDE.md**: Run \`faf_sync\` to create AI documentation (+30 points)\n\n`;
|
|
1414
|
-
}
|
|
1415
|
-
if (hasFaf && hasClaude) {
|
|
1416
|
-
result += `🎯 **You're at 100%!** Run \`faf_bi_sync\` to keep files synchronized.\n\n`;
|
|
1417
|
-
}
|
|
1418
|
-
result += `---\n\n`;
|
|
1419
|
-
result += `*Generated by FAF v${version_js_1.VERSION}*\n\n`;
|
|
1420
|
-
result += `*"It's so logical if it didn't exist, AI would have built it itself" — Claude*`;
|
|
1164
|
+
result =
|
|
1165
|
+
`FAF Score: ${sc.score}/100 ${sc.tierDisplay}\n` +
|
|
1166
|
+
`${progressBar} ${sc.score}%\n` +
|
|
1167
|
+
`${sc.populated}/${sc.total} slots${sc.nextTier ? ` next: ${sc.nextTier}` : ' top tier'}`;
|
|
1421
1168
|
}
|
|
1422
1169
|
else {
|
|
1423
|
-
//
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
// Build the scorecard
|
|
1436
|
-
result = `# FAF Score Card\n\n`;
|
|
1437
|
-
result += `## **Project Score: ${score}/100** ${statusEmoji}\n\n`;
|
|
1438
|
-
result += `${progressBar} ${score}%\n\n`;
|
|
1439
|
-
result += `### ${statusEmoji} **Status: ${statusText}**\n\n`;
|
|
1440
|
-
result += `---\n\n`;
|
|
1441
|
-
// Performance Breakdown Table
|
|
1442
|
-
result += `## 📊 Performance Breakdown\n\n`;
|
|
1443
|
-
result += `| Component | Status | Points | Performance |\n`;
|
|
1444
|
-
result += `|-----------|--------|--------|-------------|\n`;
|
|
1445
|
-
result += `| **.faf** | ${hasFaf ? '✅ **ACTIVE**' : '⚠️ **MISSING**'} | ${hasFaf ? '40' : '0'}pts | ${hasFaf ? 'Core config synchronized' : '*Create with `faf_init`*'} |\n`;
|
|
1446
|
-
result += `| **CLAUDE.md** | ${hasClaude ? '✅ **SYNCED**' : '⚠️ **MISSING**'} | ${hasClaude ? '30' : '0'}pts | ${hasClaude ? 'AI documentation live' : '*Generate with `faf_sync`*'} |\n`;
|
|
1447
|
-
result += `| **README.md** | ${hasReadme ? '✅ **READY**' : '⚠️ **MISSING**'} | ${hasReadme ? '15' : '0'}pts | ${hasReadme ? 'Project docs complete' : '*Add for extra points*'} |\n`;
|
|
1448
|
-
result += `| **package.json** | ${hasPackage ? '✅ **FOUND**' : '⚠️ **MISSING**'} | ${hasPackage ? '14' : '0'}pts | ${hasPackage ? 'Dependencies tracked' : '*Add for full score*'} |\n`;
|
|
1449
|
-
result += `\n---\n\n`;
|
|
1450
|
-
// Context Status Section
|
|
1451
|
-
result += `## Context Status\n\n`;
|
|
1452
|
-
// Strengths
|
|
1453
|
-
const strengths = [];
|
|
1454
|
-
if (hasFaf && hasClaude)
|
|
1455
|
-
strengths.push('Bi-directional sync active');
|
|
1456
|
-
if (hasClaude)
|
|
1457
|
-
strengths.push('AI-Ready Documentation: Full CLAUDE.md integration');
|
|
1458
|
-
if (hasFaf)
|
|
1459
|
-
strengths.push('Core Systems: FAF foundation in place');
|
|
1460
|
-
if (hasReadme)
|
|
1461
|
-
strengths.push('Documentation: README.md providing clarity');
|
|
1462
|
-
if (hasPackage)
|
|
1463
|
-
strengths.push('Dependencies: package.json tracking enabled');
|
|
1464
|
-
if (strengths.length > 0) {
|
|
1465
|
-
result += `### **Strengths** 💚\n`;
|
|
1466
|
-
strengths.forEach(s => result += `- ${s}\n`);
|
|
1467
|
-
result += `\n`;
|
|
1468
|
-
}
|
|
1469
|
-
// Improvements needed
|
|
1470
|
-
const improvements = [];
|
|
1471
|
-
if (!hasFaf)
|
|
1472
|
-
improvements.push('Initialize with `faf_init` for +40 points');
|
|
1473
|
-
if (!hasClaude)
|
|
1474
|
-
improvements.push('Create CLAUDE.md with `faf_sync` for +30 points');
|
|
1475
|
-
if (!hasReadme)
|
|
1476
|
-
improvements.push('Add README.md for +15 points → better documentation');
|
|
1477
|
-
if (!hasPackage)
|
|
1478
|
-
improvements.push('Add package.json for +14 points → ${score + 14}% score');
|
|
1479
|
-
if (improvements.length > 0) {
|
|
1480
|
-
result += `### **Improvements Needed** 🔧\n`;
|
|
1481
|
-
improvements.forEach(i => result += `- ${i}\n`);
|
|
1482
|
-
result += `\n`;
|
|
1483
|
-
}
|
|
1484
|
-
// Quick Commands
|
|
1485
|
-
result += `---\n\n`;
|
|
1486
|
-
result += `## ⚡ Quick Commands\n\n`;
|
|
1487
|
-
result += `\`\`\`bash\n`;
|
|
1488
|
-
if (!hasFaf)
|
|
1489
|
-
result += `faf_init # Initialize FAF (+40 pts)\n`;
|
|
1490
|
-
if (!hasClaude)
|
|
1491
|
-
result += `faf_sync # Generate CLAUDE.md (+30 pts)\n`;
|
|
1492
|
-
if (hasFaf && hasClaude)
|
|
1493
|
-
result += `faf_bi_sync # Keep files synchronized\n`;
|
|
1494
|
-
result += `faf_enhance # AI-powered improvements\n`;
|
|
1495
|
-
result += `faf_score --save # Save this scorecard\n`;
|
|
1496
|
-
result += `\`\`\`\n\n`;
|
|
1497
|
-
// FAF Quote
|
|
1498
|
-
const quotes = [
|
|
1499
|
-
'"Context is everything. FAF delivers it."',
|
|
1500
|
-
'"Stop FAFfing about - get to 100%!"',
|
|
1501
|
-
'"The best time to FAF was yesterday. The second best time is now."',
|
|
1502
|
-
'"AI without context is just guessing."',
|
|
1503
|
-
'"One file. Complete understanding."'
|
|
1504
|
-
];
|
|
1505
|
-
const randomQuote = quotes[Math.floor(Math.random() * quotes.length)];
|
|
1506
|
-
result += `---\n\n`;
|
|
1507
|
-
result += `> ${randomQuote}\n\n`;
|
|
1508
|
-
// Footer
|
|
1509
|
-
result += `---\n\n`;
|
|
1510
|
-
result += `*Generated by FAF v${version_js_1.VERSION}* ⚡\n`;
|
|
1511
|
-
result += `*${new Date().toISOString()}*`;
|
|
1512
|
-
// NOTE: AI-Readiness footer is added by formatResult() - don't duplicate!
|
|
1513
|
-
}
|
|
1514
|
-
// Save scorecard if requested
|
|
1515
|
-
if (saveCard) {
|
|
1170
|
+
// markdown (default). `full` adds nothing fabricated — the real
|
|
1171
|
+
// score IS the full truth; the old championship theater is dead.
|
|
1172
|
+
result =
|
|
1173
|
+
`# FAF Score Card\n\n` +
|
|
1174
|
+
`## **${sc.score}/100** ${sc.tierDisplay}\n\n` +
|
|
1175
|
+
`${progressBar} ${sc.score}%\n\n` +
|
|
1176
|
+
`**${sc.populated}/${sc.total} slots populated**${sc.nextTier ? ` · next: ${sc.nextTier}` : ' · top tier'}\n\n` +
|
|
1177
|
+
`---\n\n` +
|
|
1178
|
+
`> Scored by faf-cli's real scorer — the same context your AI reads.\n` +
|
|
1179
|
+
`> Slot-by-slot detail: \`faf score\` (faf-cli).`;
|
|
1180
|
+
}
|
|
1181
|
+
if (saveCard && format !== 'json') {
|
|
1516
1182
|
const scoreCardPath = path.join(targetDir, 'SCORE-CARD.md');
|
|
1517
|
-
await fs.writeFile(scoreCardPath, result
|
|
1183
|
+
await fs.writeFile(scoreCardPath, result);
|
|
1518
1184
|
result += `\n\n✅ **Score card saved to:** \`${scoreCardPath}\``;
|
|
1519
1185
|
}
|
|
1520
|
-
// ✅ FIXED - Route through formatResult for metadata!
|
|
1521
|
-
// formatResult will add the universal AI-Readiness footer
|
|
1522
1186
|
return await this.formatResult('🏎️ FAF Score', result, undefined, targetDir);
|
|
1523
1187
|
}
|
|
1524
1188
|
async handleSync(args) {
|
|
@@ -2011,7 +1675,7 @@ faf_score --save # Save this scorecard
|
|
|
2011
1675
|
answer = `🔧 TROUBLESHOOTING\n\n` +
|
|
2012
1676
|
`"EROFS: read-only file system"\n` +
|
|
2013
1677
|
`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n` +
|
|
2014
|
-
`You're in
|
|
1678
|
+
`You're in your MCP host sandbox!\n` +
|
|
2015
1679
|
`Solution: Use real path\n` +
|
|
2016
1680
|
`Example: faf_auto ~/Documents/project\n\n` +
|
|
2017
1681
|
`"Stack: Unknown"\n` +
|
|
@@ -2069,11 +1733,11 @@ faf_score --save # Save this scorecard
|
|
|
2069
1733
|
return await this.formatResult('💡 FAF HELP', answer);
|
|
2070
1734
|
}
|
|
2071
1735
|
async handleGuide(_args) {
|
|
2072
|
-
const guide = `# FAF MCP -
|
|
1736
|
+
const guide = `# FAF MCP - your MCP host Guide
|
|
2073
1737
|
|
|
2074
1738
|
## Core Principle: Local Path First
|
|
2075
1739
|
|
|
2076
|
-
|
|
1740
|
+
your MCP host has full filesystem access with local paths. All 52 MCP tools work perfectly when you provide local project paths.
|
|
2077
1741
|
|
|
2078
1742
|
**The Pattern:**
|
|
2079
1743
|
1. Ask: "What's your project path?"
|
|
@@ -2139,106 +1803,50 @@ Simple, fast, championship-grade.`;
|
|
|
2139
1803
|
// Developer Tool Handlers
|
|
2140
1804
|
async handleStatus(args) {
|
|
2141
1805
|
const targetDir = args?.directory || process.cwd();
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
const { medal } = this.getScoreMedal(score);
|
|
2146
|
-
// Get next target info
|
|
2147
|
-
const tierInfo = this.getTierInfo(score);
|
|
2148
|
-
// Build status output
|
|
2149
|
-
let output = `🏎️ FAF Status\n━━━━━━━━━━━━\n`;
|
|
2150
|
-
output += `Score: ${score}% ${medal} ${tierInfo.current}\n`;
|
|
2151
|
-
if (tierInfo.next && tierInfo.nextTarget && tierInfo.nextMedal) {
|
|
2152
|
-
const pointsToGo = tierInfo.nextTarget - score;
|
|
2153
|
-
output += `Next: ${tierInfo.nextTarget}% ${tierInfo.nextMedal} ${tierInfo.next} (${pointsToGo}% to go!)`;
|
|
1806
|
+
const s = this.getFafScore(targetDir);
|
|
1807
|
+
if (!s.found) {
|
|
1808
|
+
return await this.formatResult('📊 FAF Status', `No \`.faf\` in \`${targetDir}\`. Run \`faf_init\` to create one.`, undefined, targetDir);
|
|
2154
1809
|
}
|
|
1810
|
+
let output = `🏎️ FAF Status\n━━━━━━━━━━━━\n`;
|
|
1811
|
+
output += `Score: ${s.score}% ${s.tierDisplay}\n`;
|
|
1812
|
+
output += `Slots: ${s.populated}/${s.total} populated\n`;
|
|
1813
|
+
output += s.nextTier ? `Next: ${s.nextTier}` : `Top tier — nothing above.`;
|
|
2155
1814
|
return await this.formatResult('📊 FAF Status', output, undefined, targetDir);
|
|
2156
1815
|
}
|
|
2157
1816
|
/**
|
|
2158
|
-
*
|
|
2159
|
-
*
|
|
1817
|
+
* Single source of truth for score + tier: faf-cli's real scorer and
|
|
1818
|
+
* canonical tier ladder (~/FAF/cli/src/core/tiers.ts). NEVER reimplement
|
|
1819
|
+
* scoring or the tier symbols here — the old file-presence pseudo-score
|
|
1820
|
+
* (40/30/15/14) and the banned medal/colored-circle ladder are dead.
|
|
2160
1821
|
*/
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
if (score >= 95)
|
|
2167
|
-
return { medal: '🥈', status: 'Silver' };
|
|
2168
|
-
if (score >= 85)
|
|
2169
|
-
return { medal: '🥉', status: 'Bronze' };
|
|
2170
|
-
if (score >= 70)
|
|
2171
|
-
return { medal: '🟢', status: 'Green - Solid foundation' };
|
|
2172
|
-
if (score >= 55)
|
|
2173
|
-
return { medal: '🟡', status: 'Yellow - Needs improvement' };
|
|
2174
|
-
if (score > 0)
|
|
2175
|
-
return { medal: '🔴', status: 'Red - AI working blind' };
|
|
2176
|
-
return { medal: '🤍', status: 'White - Empty' };
|
|
2177
|
-
}
|
|
2178
|
-
/**
|
|
2179
|
-
* Get tier progression info
|
|
2180
|
-
* Shows current tier and next target
|
|
2181
|
-
*/
|
|
2182
|
-
getTierInfo(score) {
|
|
2183
|
-
if (score >= 100) {
|
|
2184
|
-
return { current: 'Trophy - Championship' };
|
|
2185
|
-
}
|
|
2186
|
-
else if (score >= 99) {
|
|
1822
|
+
getFafScore(dir) {
|
|
1823
|
+
// eslint-disable-next-line no-control-regex
|
|
1824
|
+
const strip = (s) => s.replace(/\u001b\[[0-9;]*m/g, "").trim();
|
|
1825
|
+
const fafPath = (0, faf_cli_1.findFafFile)(dir);
|
|
1826
|
+
if (!fafPath) {
|
|
2187
1827
|
return {
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
current: 'Silver',
|
|
2197
|
-
next: 'Gold',
|
|
2198
|
-
nextTarget: 99,
|
|
2199
|
-
nextMedal: '🥇'
|
|
2200
|
-
};
|
|
2201
|
-
}
|
|
2202
|
-
else if (score >= 85) {
|
|
2203
|
-
return {
|
|
2204
|
-
current: 'Bronze',
|
|
2205
|
-
next: 'Silver',
|
|
2206
|
-
nextTarget: 95,
|
|
2207
|
-
nextMedal: '🥈'
|
|
2208
|
-
};
|
|
2209
|
-
}
|
|
2210
|
-
else if (score >= 70) {
|
|
2211
|
-
return {
|
|
2212
|
-
current: 'Green - Solid foundation',
|
|
2213
|
-
next: 'Bronze',
|
|
2214
|
-
nextTarget: 85,
|
|
2215
|
-
nextMedal: '🥉'
|
|
2216
|
-
};
|
|
2217
|
-
}
|
|
2218
|
-
else if (score >= 55) {
|
|
2219
|
-
return {
|
|
2220
|
-
current: 'Yellow - Needs improvement',
|
|
2221
|
-
next: 'Green - Solid foundation',
|
|
2222
|
-
nextTarget: 70,
|
|
2223
|
-
nextMedal: '🟢'
|
|
2224
|
-
};
|
|
2225
|
-
}
|
|
2226
|
-
else if (score > 0) {
|
|
2227
|
-
return {
|
|
2228
|
-
current: 'Red - AI working blind',
|
|
2229
|
-
next: 'Yellow - Needs improvement',
|
|
2230
|
-
nextTarget: 55,
|
|
2231
|
-
nextMedal: '🟡'
|
|
2232
|
-
};
|
|
2233
|
-
}
|
|
2234
|
-
else {
|
|
2235
|
-
return {
|
|
2236
|
-
current: 'White - Empty',
|
|
2237
|
-
next: 'Red - AI working blind',
|
|
2238
|
-
nextTarget: 1,
|
|
2239
|
-
nextMedal: '🔴'
|
|
1828
|
+
found: false,
|
|
1829
|
+
score: 0,
|
|
1830
|
+
tierName: 'White',
|
|
1831
|
+
tierGlyph: '♡',
|
|
1832
|
+
tierDisplay: '♡ no .faf',
|
|
1833
|
+
nextTier: null,
|
|
1834
|
+
populated: 0,
|
|
1835
|
+
total: 0,
|
|
2240
1836
|
};
|
|
2241
1837
|
}
|
|
1838
|
+
const r = (0, faf_cli_1.scoreFafYaml)((0, faf_cli_1.readFafRaw)(fafPath));
|
|
1839
|
+
const next = (0, faf_cli_1.getNextTier)(r.score);
|
|
1840
|
+
return {
|
|
1841
|
+
found: true,
|
|
1842
|
+
score: r.score,
|
|
1843
|
+
tierName: r.tier.name,
|
|
1844
|
+
tierGlyph: strip(r.tier.indicator).split(' ')[0] || '',
|
|
1845
|
+
tierDisplay: strip(r.tier.indicator),
|
|
1846
|
+
nextTier: next ? `${strip(next.indicator)} (${next.threshold}%)` : null,
|
|
1847
|
+
populated: r.populated,
|
|
1848
|
+
total: r.total,
|
|
1849
|
+
};
|
|
2242
1850
|
}
|
|
2243
1851
|
async handleCheck(_args) {
|
|
2244
1852
|
return await this.formatResult('✅ FAF Check', 'All systems operational!');
|
|
@@ -2526,7 +2134,7 @@ Performance: <50ms per operation
|
|
|
2526
2134
|
const startTime = Date.now();
|
|
2527
2135
|
try {
|
|
2528
2136
|
// Find the skill file in the npm package
|
|
2529
|
-
// It should be at: node_modules/
|
|
2137
|
+
// It should be at: node_modules/faf-mcp/skill/SKILL.md
|
|
2530
2138
|
const homeDir = process.env.HOME || process.env.USERPROFILE || '/';
|
|
2531
2139
|
const claudeSkillsDir = path.join(homeDir, '.claude', 'skills', 'faf-expert');
|
|
2532
2140
|
const targetSkillPath = path.join(claudeSkillsDir, 'SKILL.md');
|
|
@@ -2539,14 +2147,14 @@ Performance: <50ms per operation
|
|
|
2539
2147
|
}
|
|
2540
2148
|
// Location 2: In node_modules (global or local)
|
|
2541
2149
|
if (!sourceSkillPath) {
|
|
2542
|
-
const globalNodeModules = path.join(homeDir, '.npm', 'lib', 'node_modules', '
|
|
2150
|
+
const globalNodeModules = path.join(homeDir, '.npm', 'lib', 'node_modules', 'faf-mcp', 'skill', 'SKILL.md');
|
|
2543
2151
|
if (await this.fileExists(globalNodeModules)) {
|
|
2544
2152
|
sourceSkillPath = globalNodeModules;
|
|
2545
2153
|
}
|
|
2546
2154
|
}
|
|
2547
2155
|
// Location 3: In current working directory node_modules
|
|
2548
2156
|
if (!sourceSkillPath) {
|
|
2549
|
-
const localNodeModules = path.join(process.cwd(), 'node_modules', '
|
|
2157
|
+
const localNodeModules = path.join(process.cwd(), 'node_modules', 'faf-mcp', 'skill', 'SKILL.md');
|
|
2550
2158
|
if (await this.fileExists(localNodeModules)) {
|
|
2551
2159
|
sourceSkillPath = localNodeModules;
|
|
2552
2160
|
}
|
|
@@ -2554,7 +2162,7 @@ Performance: <50ms per operation
|
|
|
2554
2162
|
// Location 4: Search using require.resolve
|
|
2555
2163
|
if (!sourceSkillPath) {
|
|
2556
2164
|
try {
|
|
2557
|
-
const mcpPackageRoot = path.dirname(require.resolve('
|
|
2165
|
+
const mcpPackageRoot = path.dirname(require.resolve('faf-mcp/package.json'));
|
|
2558
2166
|
const resolvedPath = path.join(mcpPackageRoot, 'skill', 'SKILL.md');
|
|
2559
2167
|
if (await this.fileExists(resolvedPath)) {
|
|
2560
2168
|
sourceSkillPath = resolvedPath;
|
|
@@ -2566,7 +2174,7 @@ Performance: <50ms per operation
|
|
|
2566
2174
|
}
|
|
2567
2175
|
if (!sourceSkillPath) {
|
|
2568
2176
|
const duration = Date.now() - startTime;
|
|
2569
|
-
return await this.formatResult('🏆 Install faf-expert Skill', `❌ Could not locate skill file in npm package.\n\nSearched:\n- ${devRepoPath}\n- Global node_modules\n- Local node_modules\n\nPlease ensure
|
|
2177
|
+
return await this.formatResult('🏆 Install faf-expert Skill', `❌ Could not locate skill file in npm package.\n\nSearched:\n- ${devRepoPath}\n- Global node_modules\n- Local node_modules\n\nPlease ensure faf-mcp is installed.`, duration);
|
|
2570
2178
|
}
|
|
2571
2179
|
// Create directory if needed
|
|
2572
2180
|
await fs.mkdir(claudeSkillsDir, { recursive: true });
|
|
@@ -2577,10 +2185,10 @@ Performance: <50ms per operation
|
|
|
2577
2185
|
return await this.formatResult('🏆 Install faf-expert Skill', `✅ faf-expert skill installed successfully!\n\n` +
|
|
2578
2186
|
`📍 Location: ${targetSkillPath}\n\n` +
|
|
2579
2187
|
`🔄 RESTART REQUIRED:\n` +
|
|
2580
|
-
` Please restart
|
|
2188
|
+
` Please restart your MCP host to activate the skill.\n\n` +
|
|
2581
2189
|
`🎯 Once restarted, invoke the faf-expert skill to reach\n` +
|
|
2582
2190
|
` 99/100 AI-readiness with championship-grade guidance!\n\n` +
|
|
2583
|
-
`💡 Usage: Just say "Invoke faf-expert skill" in
|
|
2191
|
+
`💡 Usage: Just say "Invoke faf-expert skill" in your MCP host`, duration);
|
|
2584
2192
|
}
|
|
2585
2193
|
catch (error) {
|
|
2586
2194
|
const duration = Date.now() - startTime;
|
|
@@ -2616,8 +2224,8 @@ Performance: <50ms per operation
|
|
|
2616
2224
|
const hasPackage = await this.fileExists(path.join(fullPath, 'package.json'));
|
|
2617
2225
|
const hasFaf = await (0, faf_file_finder_js_1.hasFafFile)(fullPath);
|
|
2618
2226
|
if (hasPackage || hasFaf) {
|
|
2619
|
-
//
|
|
2620
|
-
const score =
|
|
2227
|
+
// Real faf-cli score (0 when the project has no .faf — honest)
|
|
2228
|
+
const score = this.getFafScore(fullPath).score;
|
|
2621
2229
|
projects.push({
|
|
2622
2230
|
name: dir.name,
|
|
2623
2231
|
path: fullPath,
|
|
@@ -2707,22 +2315,6 @@ Performance: <50ms per operation
|
|
|
2707
2315
|
timestamp: new Date().toISOString()
|
|
2708
2316
|
});
|
|
2709
2317
|
}
|
|
2710
|
-
/**
|
|
2711
|
-
* Calculate current AI-Readiness score quietly
|
|
2712
|
-
*/
|
|
2713
|
-
async calculateScore(dir) {
|
|
2714
|
-
const targetDir = dir || process.cwd();
|
|
2715
|
-
let score = 0;
|
|
2716
|
-
if (await (0, faf_file_finder_js_1.hasFafFile)(targetDir))
|
|
2717
|
-
score += 40;
|
|
2718
|
-
if (await this.fileExists(path.join(targetDir, 'CLAUDE.md')))
|
|
2719
|
-
score += 30;
|
|
2720
|
-
if (await this.fileExists(path.join(targetDir, 'README.md')))
|
|
2721
|
-
score += 15;
|
|
2722
|
-
if (await this.fileExists(path.join(targetDir, 'package.json')))
|
|
2723
|
-
score += 14;
|
|
2724
|
-
return score;
|
|
2725
|
-
}
|
|
2726
2318
|
}
|
|
2727
2319
|
exports.ChampionshipToolHandler = ChampionshipToolHandler;
|
|
2728
2320
|
//# sourceMappingURL=championship-tools.js.map
|