faf-cli
Version:
😽 TURBO-CAT: The Rapid Catalytic Converter • Project DNA ✨ for ANY AI • Fully Integrated with React, Next.js, Svelte, TypeScript, Vite & n8n • FREE FOREVER • 10,000+ developers • Championship Edition
520 lines • 20.2 kB
JavaScript
;
/**
* 📊 .faf Scoring Calculator
* Championship scoring with FAB-FORMATS Power Unit
* 150+ file handlers delivering 86%+ scores!
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.calculateFafScore = calculateFafScore;
const fab_formats_processor_1 = require("../engines/fab-formats-processor");
const path_1 = __importDefault(require("path"));
// NO WEIGHTS - UI uses direct slot counting
// Total slots = PC (15) + PD (6) + FILE (dynamic)
// const UI_STRUCTURE = {
// project_components: 15, // The 15 technical fields
// project_details: 6, // The 6 W's
// files: 0, // Dynamic based on file intelligence
// };
/**
* Calculate .faf score for v2.5.0 nested_snake format with fab-formats discovery
*/
async function calculateFafScore(fafData, projectPath) {
// 🚨 CRITICAL: Handle null/undefined data safely
if (!fafData || typeof fafData !== 'object') {
fafData = {};
}
// CHAMPIONSHIP ENGINE: FAB-FORMATS Power Unit from web (86% scores!)
// This is the REAL engine, not simplified version
// 150+ file handlers with deep intelligence extraction
let discoveredContext = {};
if (projectPath) {
try {
// Fire up the championship engine! 🏎️
const projectDir = path_1.default.dirname(projectPath);
const fabProcessor = new fab_formats_processor_1.FabFormatsProcessor();
const analysis = await fabProcessor.processFiles(projectDir);
// Championship Context Extraction - Fill ALL the slots!
if (analysis.context) {
const ctx = analysis.context;
// Project slots (core identity)
if (ctx.projectName && !fafData.project?.name) {
if (!fafData.project)
fafData.project = {};
fafData.project.name = ctx.projectName;
}
if (ctx.projectGoal && !fafData.project?.goal) {
if (!fafData.project)
fafData.project = {};
fafData.project.goal = ctx.projectGoal;
}
if (ctx.mainLanguage && !fafData.project?.main_language) {
if (!fafData.project)
fafData.project = {};
fafData.project.main_language = ctx.mainLanguage;
}
// Stack slots (technical architecture)
if (!fafData.stack)
fafData.stack = {};
if (ctx.framework && !fafData.stack.frontend) {
fafData.stack.frontend = ctx.framework;
}
if (ctx.backend && !fafData.stack.backend) {
fafData.stack.backend = ctx.backend;
}
if (ctx.server && !fafData.stack.server) {
fafData.stack.server = ctx.server;
}
if (ctx.database && !fafData.stack.database) {
fafData.stack.database = ctx.database;
}
if (ctx.hosting && !fafData.stack.hosting) {
fafData.stack.hosting = ctx.hosting;
}
// Build slots (development tools)
if (!fafData.build)
fafData.build = {};
if (ctx.buildTool && !fafData.build.tool) {
fafData.build.tool = ctx.buildTool;
}
if (ctx.packageManager && !fafData.build.package_manager) {
fafData.build.package_manager = ctx.packageManager;
}
if (ctx.testFramework && !fafData.build.test_framework) {
fafData.build.test_framework = ctx.testFramework;
}
if (ctx.linter && !fafData.build.linter) {
fafData.build.linter = ctx.linter;
}
// Human context slots (6 W's)
if (!fafData.human)
fafData.human = {};
if (ctx.targetUser && !fafData.human.who) {
fafData.human.who = ctx.targetUser;
}
if (ctx.coreProblem && !fafData.human.what) {
fafData.human.what = ctx.coreProblem;
}
if (ctx.missionPurpose && !fafData.human.why) {
fafData.human.why = ctx.missionPurpose;
}
if (ctx.deploymentMarket && !fafData.human.where) {
fafData.human.where = ctx.deploymentMarket;
}
if (ctx.timeline && !fafData.human.when) {
fafData.human.when = ctx.timeline;
}
if (ctx.approach && !fafData.human.how) {
fafData.human.how = ctx.approach;
}
}
// Process intelligence from each file result
if (analysis.results && analysis.results.length > 0) {
// Track quality for bonus scoring
const hasExceptional = analysis.results.some((r) => r.metadata?.grade === 'EXCEPTIONAL');
const hasProfessional = analysis.results.some((r) => r.metadata?.grade === 'PROFESSIONAL');
// Apply quality bonuses
if (hasExceptional) {
// EXCEPTIONAL quality = major score boost
discoveredContext.qualityBonus = 30;
}
else if (hasProfessional) {
discoveredContext.qualityBonus = 20;
}
// Store discovery metadata
discoveredContext.filesAnalyzed = analysis.results.length;
discoveredContext.totalIntelligence = analysis.totalBonus;
}
}
catch (error) {
// Log the error so we can see what's wrong with TURBO-CAT
console.error('TURBO-CAT Discovery failed:', error);
}
}
// LEGACY/FALLBACK: Only calculate if no embedded score (older formats)
let pcSlots = 0;
// Map v2.5.0 nested fields to scoring logic (now enhanced with fab-formats discovery!)
const projectName = fafData.project?.name || fafData.projectName || discoveredContext.projectName;
const projectGoal = fafData.project?.goal || fafData.instant_context?.what_building || fafData.projectGoal || discoveredContext.projectGoal;
const mainLanguage = fafData.instant_context?.main_language || fafData.project?.main_language || fafData.mainLanguage || discoveredContext.mainLanguage;
const framework = fafData.stack?.frontend || fafData.framework;
const cssFramework = fafData.stack?.css_framework || fafData.cssFramework;
const uiLibrary = fafData.stack?.ui_library || fafData.uiLibrary;
const stateManagement = fafData.stack?.state_management || fafData.stateManagement;
const backend = fafData.stack?.backend || fafData.backend;
const apiType = fafData.stack?.api_type || fafData.apiType;
const server = fafData.stack?.runtime || fafData.server;
const database = fafData.stack?.database || fafData.database;
const connection = fafData.stack?.connection || fafData.connection;
const hosting = fafData.stack?.hosting || fafData.instant_context?.deployment || fafData.hosting;
const buildTool = fafData.stack?.build || fafData.buildTool;
const cicd = fafData.stack?.cicd || fafData.cicd;
if (projectName?.length >= 3) {
pcSlots++;
}
if (projectGoal && projectGoal !== 'Project development and deployment') {
pcSlots++;
}
if (mainLanguage && mainLanguage !== 'Unknown') {
pcSlots++;
}
if (framework && framework !== 'None') {
pcSlots++;
}
if (cssFramework && cssFramework !== 'None') {
pcSlots++;
}
if (uiLibrary && uiLibrary !== 'None') {
pcSlots++;
}
if (stateManagement && stateManagement !== 'None') {
pcSlots++;
}
if (backend && backend !== 'None') {
pcSlots++;
}
if (apiType && apiType !== 'REST API') {
pcSlots++;
}
if (server && server !== 'None') {
pcSlots++;
}
if (database && database !== 'None') {
pcSlots++;
}
if (connection && connection !== 'None') {
pcSlots++;
}
if (hosting && hosting !== 'None') {
pcSlots++;
}
if (buildTool && buildTool !== 'None') {
pcSlots++;
}
if (cicd && cicd !== 'None') {
pcSlots++;
}
// Count PD slots (6 max) - Updated for v2.5.0 nested format
let pdSlots = 0;
// Map v2.5.0 human_context fields to scoring logic (handle nested objects)
const hasWho = fafData.human_context?.who &&
(typeof fafData.human_context.who === 'object' ||
(typeof fafData.human_context.who === 'string' && fafData.human_context.who !== 'Not specified'));
const hasWhat = fafData.human_context?.what &&
(typeof fafData.human_context.what === 'object' ||
(typeof fafData.human_context.what === 'string' && fafData.human_context.what !== 'Not specified'));
const hasWhy = fafData.human_context?.why &&
(typeof fafData.human_context.why === 'object' ||
(typeof fafData.human_context.why === 'string' && fafData.human_context.why !== 'Not specified'));
const hasWhere = fafData.human_context?.where &&
(typeof fafData.human_context.where === 'object' ||
(typeof fafData.human_context.where === 'string' && fafData.human_context.where !== 'Not specified'));
const hasWhen = fafData.human_context?.when &&
(typeof fafData.human_context.when === 'object' ||
(typeof fafData.human_context.when === 'string' && fafData.human_context.when !== 'Not specified'));
const hasHow = fafData.human_context?.how &&
(typeof fafData.human_context.how === 'object' ||
(typeof fafData.human_context.how === 'string' && fafData.human_context.how !== 'Not specified'));
if (hasWho) {
pdSlots++;
}
if (hasWhat) {
pdSlots++;
}
if (hasWhy) {
pdSlots++;
}
if (hasWhere) {
pdSlots++;
}
if (hasWhen) {
pdSlots++;
}
if (hasHow) {
pdSlots++;
}
// Count FILE slots (if files are present) - simplified for CLI
let fileSlots = 0;
if (fafData.files && Array.isArray(fafData.files)) {
// Basic file intelligence scoring for CLI
const totalIntelligence = fafData.files.reduce((sum, file) => sum + (file.intelligenceBonus || 0), 0);
if (totalIntelligence >= 150) {
fileSlots += 15;
}
else if (totalIntelligence >= 100) {
fileSlots += 10;
}
else if (totalIntelligence >= 70) {
fileSlots += 7;
}
else if (totalIntelligence >= 50) {
fileSlots += 5;
}
else if (totalIntelligence >= 30) {
fileSlots += 3;
}
else if (totalIntelligence > 0) {
fileSlots += Math.ceil(totalIntelligence / 10);
}
}
const filledSlots = pcSlots + pdSlots + fileSlots;
const totalSlots = 15 + 6 + fileSlots; // PC + PD + dynamic FILE
// Calculate percentage EXACTLY like UI
const percentage = totalSlots > 0 ? Math.min(100, Math.round((filledSlots / totalSlots) * 100)) : 0;
// Generate section scores for detailed view
const sectionScores = {
project: {
percentage: Math.round((pcSlots / 15) * 100),
filled: pcSlots,
total: 15,
missing: getProjectComponentsMissing(fafData),
},
human_context: {
percentage: Math.round((pdSlots / 6) * 100),
filled: pdSlots,
total: 6,
missing: getProjectDetailsMissing(fafData),
},
};
// Add AI instructions section if present
if (fafData.ai_instructions) {
const aiSlots = calculateAIInstructionsScore(fafData.ai_instructions);
sectionScores.ai_instructions = aiSlots;
}
// Add technical context section if present
if (fafData.technical_context) {
const techSlots = calculateTechnicalContextScore(fafData.technical_context);
sectionScores.technical_context = techSlots;
}
// Generate suggestions
const suggestions = [];
if (pcSlots < 15 && sectionScores.project) {
const missing = sectionScores.project.missing.slice(0, 2);
if (missing.length > 0) {
suggestions.push(`Add ${missing.join(" and ")} to project components`);
}
}
if (pdSlots < 6 && sectionScores.human_context) {
const missing = sectionScores.human_context.missing.slice(0, 2);
if (missing.length > 0) {
suggestions.push(`Add ${missing.join(" and ")} to human context (6 W's)`);
}
}
// Quality indicators - simplified
const qualityIndicators = {
hasAiInstructions: false, // Not used in UI format
hasHumanContext: pdSlots > 0,
hasFreshTimestamp: isTimestampFresh(fafData.generated),
hasQualityPreferences: false, // Not used in UI format
};
return {
totalScore: percentage,
filledSlots,
totalSlots,
sectionScores,
suggestions: suggestions.slice(0, 10),
qualityIndicators,
};
}
/**
* Get missing Project Components fields (v2.5.0 format)
*/
function getProjectComponentsMissing(fafData) {
const missing = [];
// Use same mapping logic as scoring
const projectName = fafData.project?.name || fafData.projectName;
const projectGoal = fafData.project?.goal || fafData.instant_context?.what_building || fafData.projectGoal;
const mainLanguage = fafData.instant_context?.main_language || fafData.project?.main_language || fafData.mainLanguage;
const framework = fafData.stack?.frontend || fafData.framework;
const cssFramework = fafData.stack?.css_framework || fafData.cssFramework;
const uiLibrary = fafData.stack?.ui_library || fafData.uiLibrary;
const stateManagement = fafData.stack?.state_management || fafData.stateManagement;
const backend = fafData.stack?.backend || fafData.backend;
const apiType = fafData.stack?.api_type || fafData.apiType;
const server = fafData.stack?.runtime || fafData.server;
const database = fafData.stack?.database || fafData.database;
const connection = fafData.stack?.connection || fafData.connection;
const hosting = fafData.stack?.hosting || fafData.instant_context?.deployment || fafData.hosting;
const buildTool = fafData.stack?.build || fafData.buildTool;
const cicd = fafData.stack?.cicd || fafData.cicd;
if (!projectName || projectName.length < 3) {
missing.push("project.name");
}
if (!projectGoal || projectGoal === 'Project development and deployment') {
missing.push("project.goal");
}
if (!mainLanguage || mainLanguage === 'Unknown') {
missing.push("main_language");
}
if (!framework || framework === 'None') {
missing.push("stack.frontend");
}
if (!cssFramework || cssFramework === 'None') {
missing.push("stack.css_framework");
}
if (!uiLibrary || uiLibrary === 'None') {
missing.push("stack.ui_library");
}
if (!stateManagement || stateManagement === 'None') {
missing.push("stack.state_management");
}
if (!backend || backend === 'None') {
missing.push("stack.backend");
}
if (!apiType || apiType === 'REST API') {
missing.push("stack.api_type");
}
if (!server || server === 'None') {
missing.push("stack.runtime");
}
if (!database || database === 'None') {
missing.push("stack.database");
}
if (!connection || connection === 'None') {
missing.push("stack.connection");
}
if (!hosting || hosting === 'None') {
missing.push("stack.hosting");
}
if (!buildTool || buildTool === 'None') {
missing.push("stack.build");
}
if (!cicd || cicd === 'None') {
missing.push("stack.cicd");
}
return missing;
}
/**
* Get missing Project Details (6 W's) fields (v2.5.0 format)
*/
function getProjectDetailsMissing(fafData) {
const missing = [];
// Use same mapping logic as scoring
const targetUser = fafData.human_context?.who || fafData.targetUser;
const coreProblem = fafData.human_context?.what || fafData.coreProblem;
const missionPurpose = fafData.human_context?.why || fafData.missionPurpose;
const deploymentMarket = fafData.human_context?.where || fafData.deploymentMarket;
const timeline = fafData.human_context?.when || fafData.timeline;
const approach = fafData.human_context?.how || fafData.approach;
if (!targetUser || targetUser === 'Not specified') {
missing.push("human_context.who (WHO)");
}
if (!coreProblem || coreProblem === 'Not specified') {
missing.push("human_context.what (WHAT)");
}
if (!missionPurpose || missionPurpose === 'Not specified') {
missing.push("human_context.why (WHY)");
}
if (!deploymentMarket || deploymentMarket === 'Not specified') {
missing.push("human_context.where (WHERE)");
}
if (!timeline || timeline === 'Not specified') {
missing.push("human_context.when (WHEN)");
}
if (!approach || approach === 'Not specified') {
missing.push("human_context.how (HOW)");
}
return missing;
}
/**
* Calculate AI Instructions section score
*/
function calculateAIInstructionsScore(aiInstructions) {
let filled = 0;
const total = 5; // 5 key AI instruction elements
const missing = [];
if (aiInstructions.priority) {
filled++;
}
else {
missing.push('ai_instructions.priority');
}
if (aiInstructions.message) {
filled++;
}
else {
missing.push('ai_instructions.message');
}
if (aiInstructions.guidelines && Array.isArray(aiInstructions.guidelines) && aiInstructions.guidelines.length > 0) {
filled++;
}
else {
missing.push('ai_instructions.guidelines');
}
if (aiInstructions.priority_order) {
filled++;
}
else {
missing.push('ai_instructions.priority_order');
}
if (aiInstructions.working_style) {
filled++;
}
else {
missing.push('ai_instructions.working_style');
}
return {
percentage: Math.round((filled / total) * 100),
filled,
total,
missing
};
}
/**
* Calculate Technical Context section score
*/
function calculateTechnicalContextScore(techContext) {
let filled = 0;
const total = 4; // 4 key technical context elements
const missing = [];
if (techContext.architecture) {
filled++;
}
else {
missing.push('technical_context.architecture');
}
if (techContext.tech_stack) {
filled++;
}
else {
missing.push('technical_context.tech_stack');
}
if (techContext.key_files && Array.isArray(techContext.key_files) && techContext.key_files.length > 0) {
filled++;
}
else {
missing.push('technical_context.key_files');
}
if (techContext.patterns || techContext.infrastructure) {
filled++;
}
else {
missing.push('technical_context.patterns');
}
return {
percentage: Math.round((filled / total) * 100),
filled,
total,
missing
};
}
/**
* Check if timestamp is fresh (within 30 days)
*/
function isTimestampFresh(timestamp) {
if (!timestamp) {
return false;
}
try {
const date = new Date(timestamp);
const now = new Date();
const daysDiff = Math.abs(now.getTime() - date.getTime()) / (1000 * 60 * 60 * 24);
return daysDiff <= 30;
}
catch {
return false;
}
}
//# sourceMappingURL=score-calculator.js.map