faf-cli 4.4.2 → 4.5.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 (89) hide show
  1. package/README.md +32 -16
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +75 -10
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/agents.d.ts +14 -0
  6. package/dist/commands/agents.d.ts.map +1 -0
  7. package/dist/commands/agents.js +353 -0
  8. package/dist/commands/agents.js.map +1 -0
  9. package/dist/commands/bi-sync.d.ts +3 -0
  10. package/dist/commands/bi-sync.d.ts.map +1 -1
  11. package/dist/commands/bi-sync.js +38 -0
  12. package/dist/commands/bi-sync.js.map +1 -1
  13. package/dist/commands/cursor.d.ts +13 -0
  14. package/dist/commands/cursor.d.ts.map +1 -0
  15. package/dist/commands/cursor.js +310 -0
  16. package/dist/commands/cursor.js.map +1 -0
  17. package/dist/commands/go.d.ts.map +1 -1
  18. package/dist/commands/go.js +22 -19
  19. package/dist/commands/go.js.map +1 -1
  20. package/dist/commands/index.d.ts +12 -0
  21. package/dist/commands/index.d.ts.map +1 -1
  22. package/dist/commands/index.js +5 -4
  23. package/dist/commands/index.js.map +1 -1
  24. package/dist/commands/readme.js +16 -10
  25. package/dist/commands/readme.js.map +1 -1
  26. package/dist/commands/taf-log.d.ts +1 -0
  27. package/dist/commands/taf-log.d.ts.map +1 -1
  28. package/dist/commands/taf-log.js +53 -5
  29. package/dist/commands/taf-log.js.map +1 -1
  30. package/dist/commands/taf-stars.d.ts +8 -0
  31. package/dist/commands/taf-stars.d.ts.map +1 -0
  32. package/dist/commands/taf-stars.js +105 -0
  33. package/dist/commands/taf-stars.js.map +1 -0
  34. package/dist/commands/taf.d.ts +1 -0
  35. package/dist/commands/taf.d.ts.map +1 -1
  36. package/dist/commands/taf.js +9 -0
  37. package/dist/commands/taf.js.map +1 -1
  38. package/dist/compiler/faf-compiler.d.ts.map +1 -1
  39. package/dist/compiler/faf-compiler.js +24 -2
  40. package/dist/compiler/faf-compiler.js.map +1 -1
  41. package/dist/core-extraction-fixes.js +9 -5
  42. package/dist/core-extraction-fixes.js.map +1 -1
  43. package/dist/engines/c-mirror/broadcast/terminal-display.js +2 -1
  44. package/dist/engines/c-mirror/broadcast/terminal-display.js.map +1 -1
  45. package/dist/engines/c-mirror/core/claude-to-faf.js +12 -6
  46. package/dist/engines/c-mirror/core/claude-to-faf.js.map +1 -1
  47. package/dist/engines/c-mirror/core/events/event-emitter.js +12 -6
  48. package/dist/engines/c-mirror/core/events/event-emitter.js.map +1 -1
  49. package/dist/engines/v252-hybrid-engine.js +0 -1
  50. package/dist/engines/v252-hybrid-engine.js.map +1 -1
  51. package/dist/framework-detector.js +14 -7
  52. package/dist/framework-detector.js.map +1 -1
  53. package/dist/github/faf-git-generator.d.ts +16 -22
  54. package/dist/github/faf-git-generator.d.ts.map +1 -1
  55. package/dist/github/faf-git-generator.js +290 -320
  56. package/dist/github/faf-git-generator.js.map +1 -1
  57. package/dist/github/github-extractor.d.ts.map +1 -1
  58. package/dist/github/github-extractor.js +19 -20
  59. package/dist/github/github-extractor.js.map +1 -1
  60. package/dist/github/repo-selector.js +17 -11
  61. package/dist/github/repo-selector.js.map +1 -1
  62. package/dist/smart-faf.js +3 -2
  63. package/dist/smart-faf.js.map +1 -1
  64. package/dist/taf/index.d.ts +4 -0
  65. package/dist/taf/index.d.ts.map +1 -1
  66. package/dist/taf/index.js +16 -1
  67. package/dist/taf/index.js.map +1 -1
  68. package/dist/taf/star-badge.d.ts +32 -0
  69. package/dist/taf/star-badge.d.ts.map +1 -0
  70. package/dist/taf/star-badge.js +158 -0
  71. package/dist/taf/star-badge.js.map +1 -0
  72. package/dist/taf/star-rating.d.ts +30 -0
  73. package/dist/taf/star-rating.d.ts.map +1 -0
  74. package/dist/taf/star-rating.js +79 -0
  75. package/dist/taf/star-rating.js.map +1 -0
  76. package/dist/taf/test-output-parser.d.ts +42 -0
  77. package/dist/taf/test-output-parser.d.ts.map +1 -0
  78. package/dist/taf/test-output-parser.js +114 -0
  79. package/dist/taf/test-output-parser.js.map +1 -0
  80. package/dist/utils/agents-parser.d.ts +60 -0
  81. package/dist/utils/agents-parser.d.ts.map +1 -0
  82. package/dist/utils/agents-parser.js +325 -0
  83. package/dist/utils/agents-parser.js.map +1 -0
  84. package/dist/utils/cursorrules-parser.d.ts +56 -0
  85. package/dist/utils/cursorrules-parser.d.ts.map +1 -0
  86. package/dist/utils/cursorrules-parser.js +315 -0
  87. package/dist/utils/cursorrules-parser.js.map +1 -0
  88. package/package.json +6 -4
  89. package/project.faf +5 -5
@@ -1,35 +1,33 @@
1
1
  "use strict";
2
2
  /**
3
- * 🏆 Enhanced FAF Generation for `faf git`
3
+ * FAF Generation for `faf git`
4
4
  *
5
- * Generates 90%+ AI-ready .faf files from GitHub repos WITHOUT cloning
6
- *
7
- * Strategy:
8
- * 1. Fetch README.md → Extract 6 Ws (+30%)
9
- * 2. Fetch package.json → Deep stack analysis (+15%)
10
- * 3. Full FAF schema → Match faf-cli init (+20%)
11
- * 4. Smart defaults → AI instructions, project type (+15%)
12
- * 5. Enhanced scoring → Championship grade (+10%)
5
+ * Generates clean, accurate .faf files from GitHub repos without cloning.
6
+ * Output is compact and PR-ready — every line adds value.
13
7
  */
14
8
  Object.defineProperty(exports, "__esModule", { value: true });
15
9
  exports.extract6WsFromReadme = extract6WsFromReadme;
16
10
  exports.extractFromLanguages = extractFromLanguages;
17
11
  exports.analyzePackageJson = analyzePackageJson;
18
- exports.calculateEnhancedScore = calculateEnhancedScore;
19
12
  exports.generateEnhancedFaf = generateEnhancedFaf;
13
+ exports.getScoreTier = getScoreTier;
20
14
  const github_extractor_1 = require("./github-extractor");
21
15
  const slot_counter_1 = require("../utils/slot-counter");
16
+ // Default sentinel values — if unchanged, we omit from output
17
+ const DEFAULT_WHO = 'Open source contributors';
18
+ const DEFAULT_WHY = '';
19
+ const DEFAULT_HOW = 'See README for usage';
22
20
  /**
23
- * Extract 6 Ws from README content (simplified version for git command)
21
+ * Extract 6 Ws from README content
24
22
  */
25
23
  function extract6WsFromReadme(readme, metadata) {
26
24
  const result = {
27
- who: 'Open source contributors',
25
+ who: DEFAULT_WHO,
28
26
  what: metadata.description || 'Software project',
29
- why: 'Solve problems with code',
30
- where: 'GitHub + npm registry',
31
- when: 'Production/Stable',
32
- how: 'See README for usage',
27
+ why: '',
28
+ where: 'GitHub',
29
+ when: 'Active',
30
+ how: DEFAULT_HOW,
33
31
  confidence: 40
34
32
  };
35
33
  let confidenceBoost = 0;
@@ -67,32 +65,58 @@ function extract6WsFromReadme(readme, metadata) {
67
65
  }
68
66
  // === WHO: Target audience ===
69
67
  const whoPatterns = [
70
- /##\s*(?:Who|Target Audience|For)[^#\n]*\n+([\s\S]{10,150})(?=\n##|$)/i,
71
- /(?:Built for|Designed for|Perfect for)\s+([^.\n]{10,100})/i
68
+ /##\s*(?:Who|Target Audience)[^#\n]*\n+([\s\S]{10,150})(?=\n##|$)/i,
69
+ /(?:Built for|Designed for|Perfect for)\s+([^.\n]{15,100})/i
72
70
  ];
73
71
  for (const pattern of whoPatterns) {
74
72
  const match = readme.match(pattern);
75
73
  if (match?.[1]) {
76
- result.who = cleanText(match[1]);
77
- confidenceBoost += 10;
74
+ const extracted = cleanText(match[1]);
75
+ // Only use if it looks like a real audience description
76
+ if (extracted.length >= 15 && extracted.length <= 120
77
+ && !extracted.includes(':') && !extracted.endsWith('|')
78
+ && !extracted.includes('```') && !extracted.includes('[')) {
79
+ result.who = extracted;
80
+ confidenceBoost += 10;
81
+ }
78
82
  break;
79
83
  }
80
84
  }
81
- // === HOW: Usage/installation ===
82
- if (readme.includes('npm install') || readme.includes('Installation')) {
83
- result.how = 'npm install + usage guide in README';
85
+ // === HOW: Installation/usage (language-aware) ===
86
+ if (readme.includes('pip install') || readme.includes('pip3 install')) {
87
+ result.how = 'pip install (see README)';
88
+ confidenceBoost += 5;
89
+ }
90
+ else if (readme.includes('cargo install') || readme.includes('cargo add')) {
91
+ result.how = 'cargo install (see README)';
92
+ confidenceBoost += 5;
93
+ }
94
+ else if (readme.includes('go install') || readme.includes('go get')) {
95
+ result.how = 'go install (see README)';
96
+ confidenceBoost += 5;
97
+ }
98
+ else if (readme.includes('npm install') || readme.includes('npx ') || readme.includes('yarn add')) {
99
+ result.how = 'npm install (see README)';
100
+ confidenceBoost += 5;
101
+ }
102
+ else if (readme.includes('brew install')) {
103
+ result.how = 'brew install (see README)';
104
+ confidenceBoost += 5;
105
+ }
106
+ else if (readme.includes('docker pull') || readme.includes('docker run')) {
107
+ result.how = 'Docker (see README)';
84
108
  confidenceBoost += 5;
85
109
  }
86
- else if (readme.includes('Quick Start') || readme.includes('Getting Started')) {
87
- result.how = 'See Quick Start guide in README';
110
+ else if (readme.match(/##\s*(Quick Start|Getting Started|Installation)/i)) {
111
+ result.how = 'See Getting Started in README';
88
112
  confidenceBoost += 5;
89
113
  }
90
114
  result.confidence = Math.min(100, result.confidence + confidenceBoost);
91
115
  return result;
92
116
  }
93
117
  /**
94
- * Extract stack from GH API languages array
95
- * This is the SOURCE OF TRUTH from GitHub - trust it!
118
+ * Extract stack from GitHub API languages array (source of truth)
119
+ * Languages are sorted by percentage descending first entry is primary.
96
120
  */
97
121
  function extractFromLanguages(metadata) {
98
122
  const analysis = {
@@ -101,61 +125,55 @@ function extractFromLanguages(metadata) {
101
125
  if (!metadata.languages || metadata.languages.length === 0) {
102
126
  return analysis;
103
127
  }
104
- // Convert to lowercase for easier matching
105
- const langs = metadata.languages.map(l => l.toLowerCase());
106
- // Runtime detection (from primary language)
107
- const primaryLang = metadata.languages[0]?.split(' ')[0];
108
- if (langs.some(l => l.startsWith('c++'))) {
109
- analysis.language = 'C++';
110
- analysis.runtime = 'C++';
111
- }
112
- else if (langs.some(l => l.startsWith('rust'))) {
113
- analysis.language = 'Rust';
114
- analysis.runtime = 'Rust';
115
- }
116
- else if (langs.some(l => l.startsWith('go'))) {
117
- analysis.language = 'Go';
118
- analysis.runtime = 'Go';
119
- }
120
- else if (langs.some(l => l.startsWith('python'))) {
121
- analysis.language = 'Python';
122
- analysis.runtime = 'Python';
123
- }
124
- else if (langs.some(l => l.startsWith('java'))) {
125
- analysis.language = 'Java';
126
- analysis.runtime = 'JVM';
127
- }
128
- else if (langs.some(l => l.startsWith('c ('))) {
129
- analysis.language = 'C';
130
- analysis.runtime = 'C';
131
- }
132
- else if (langs.some(l => l.startsWith('typescript'))) {
133
- analysis.language = 'TypeScript';
134
- analysis.runtime = 'Node.js';
135
- }
136
- else if (langs.some(l => l.startsWith('javascript'))) {
137
- analysis.language = 'JavaScript';
138
- analysis.runtime = 'Node.js';
128
+ // Use PRIMARY language (highest percentage, first in sorted array)
129
+ const primaryLangName = metadata.languages[0]?.split(' ')[0];
130
+ const langRuntimeMap = {
131
+ 'C++': { language: 'C++', runtime: 'C++' },
132
+ 'Rust': { language: 'Rust', runtime: 'Rust' },
133
+ 'Go': { language: 'Go', runtime: 'Go' },
134
+ 'Python': { language: 'Python', runtime: 'Python' },
135
+ 'Java': { language: 'Java', runtime: 'JVM' },
136
+ 'C': { language: 'C', runtime: 'C' },
137
+ 'TypeScript': { language: 'TypeScript', runtime: 'Node.js' },
138
+ 'JavaScript': { language: 'JavaScript', runtime: 'Node.js' },
139
+ 'Ruby': { language: 'Ruby', runtime: 'Ruby' },
140
+ 'Swift': { language: 'Swift', runtime: 'Swift' },
141
+ 'Kotlin': { language: 'Kotlin', runtime: 'JVM' },
142
+ 'Zig': { language: 'Zig', runtime: 'Zig' },
143
+ 'Lua': { language: 'Lua', runtime: 'Lua' },
144
+ 'Dart': { language: 'Dart', runtime: 'Dart' },
145
+ 'PHP': { language: 'PHP', runtime: 'PHP' },
146
+ 'Scala': { language: 'Scala', runtime: 'JVM' },
147
+ 'Elixir': { language: 'Elixir', runtime: 'BEAM' },
148
+ 'Haskell': { language: 'Haskell', runtime: 'GHC' },
149
+ };
150
+ if (primaryLangName && langRuntimeMap[primaryLangName]) {
151
+ const match = langRuntimeMap[primaryLangName];
152
+ analysis.language = match.language;
153
+ analysis.runtime = match.runtime;
139
154
  }
140
- // Build system detection
141
- if (langs.some(l => l.startsWith('cmake'))) {
155
+ // Build system + Docker detection from all languages
156
+ const allLangs = metadata.languages.map(l => l.split(' ')[0].toLowerCase());
157
+ if (allLangs.includes('cmake')) {
142
158
  analysis.buildTool = 'CMake';
143
159
  }
144
- else if (langs.some(l => l.startsWith('makefile'))) {
160
+ else if (allLangs.includes('makefile')) {
145
161
  analysis.buildTool = 'Make';
146
162
  }
147
- else if (langs.some(l => l.startsWith('gradle'))) {
163
+ else if (allLangs.includes('gradle')) {
148
164
  analysis.buildTool = 'Gradle';
149
165
  }
150
- else if (langs.some(l => l.startsWith('maven'))) {
166
+ else if (allLangs.includes('maven')) {
151
167
  analysis.buildTool = 'Maven';
152
168
  }
153
- // Hosting detection (from Dockerfile)
154
- if (langs.some(l => l.startsWith('dockerfile'))) {
169
+ if (allLangs.includes('dockerfile')) {
155
170
  analysis.hosting = 'Docker';
156
171
  }
157
172
  return analysis;
158
173
  }
174
+ /**
175
+ * Analyze package.json for npm ecosystem details
176
+ */
159
177
  function analyzePackageJson(packageJson, metadata) {
160
178
  const analysis = {
161
179
  frameworks: []
@@ -165,118 +183,89 @@ function analyzePackageJson(packageJson, metadata) {
165
183
  ...packageJson.devDependencies
166
184
  };
167
185
  // Frontend
168
- if (deps.react)
186
+ if (deps.react) {
169
187
  analysis.frontend = 'React';
170
- else if (deps.vue)
188
+ }
189
+ else if (deps.vue) {
171
190
  analysis.frontend = 'Vue';
172
- else if (deps.svelte)
191
+ }
192
+ else if (deps.svelte) {
173
193
  analysis.frontend = 'Svelte';
174
- else if (deps['@angular/core'])
194
+ }
195
+ else if (deps['@angular/core']) {
175
196
  analysis.frontend = 'Angular';
176
- else if (deps.next)
197
+ }
198
+ else if (deps.next) {
177
199
  analysis.frontend = 'Next.js';
200
+ }
178
201
  // Backend
179
- if (deps.express)
202
+ if (deps.express) {
180
203
  analysis.backend = 'Express';
181
- else if (deps.fastify)
204
+ }
205
+ else if (deps.fastify) {
182
206
  analysis.backend = 'Fastify';
183
- else if (deps.koa)
207
+ }
208
+ else if (deps.koa) {
184
209
  analysis.backend = 'Koa';
185
- else if (deps['@nestjs/core'])
210
+ }
211
+ else if (deps['@nestjs/core']) {
186
212
  analysis.backend = 'NestJS';
213
+ }
187
214
  // Database
188
- if (deps.mongoose)
215
+ if (deps.mongoose) {
189
216
  analysis.database = 'MongoDB';
190
- else if (deps.pg || deps.postgres)
217
+ }
218
+ else if (deps.pg || deps.postgres) {
191
219
  analysis.database = 'PostgreSQL';
192
- else if (deps.mysql || deps.mysql2)
220
+ }
221
+ else if (deps.mysql || deps.mysql2) {
193
222
  analysis.database = 'MySQL';
194
- else if (deps.sqlite3 || deps['better-sqlite3'])
223
+ }
224
+ else if (deps.sqlite3 || deps['better-sqlite3']) {
195
225
  analysis.database = 'SQLite';
196
- else if (deps.redis)
226
+ }
227
+ else if (deps.redis) {
197
228
  analysis.database = 'Redis';
229
+ }
198
230
  // Testing
199
- if (deps.jest)
231
+ if (deps.jest) {
200
232
  analysis.testing = 'Jest';
201
- else if (deps.vitest)
233
+ }
234
+ else if (deps.vitest) {
202
235
  analysis.testing = 'Vitest';
203
- else if (deps.mocha)
236
+ }
237
+ else if (deps.mocha) {
204
238
  analysis.testing = 'Mocha';
205
- // Build tools (npm ecosystem)
206
- if (deps.vite)
239
+ }
240
+ // Build tools
241
+ if (deps.vite) {
207
242
  analysis.buildTool = 'Vite';
208
- else if (deps.webpack)
243
+ }
244
+ else if (deps.webpack) {
209
245
  analysis.buildTool = 'Webpack';
210
- else if (deps.rollup)
246
+ }
247
+ else if (deps.rollup) {
211
248
  analysis.buildTool = 'Rollup';
212
- else if (deps.esbuild)
249
+ }
250
+ else if (deps.esbuild) {
213
251
  analysis.buildTool = 'esbuild';
214
- // Runtime (npm ecosystem - override if detected from GH languages)
215
- if (metadata.languages?.some(l => l.startsWith('TypeScript'))) {
216
- analysis.language = 'TypeScript';
217
- analysis.runtime = 'Node.js';
218
252
  }
219
- else if (metadata.languages?.some(l => l.startsWith('JavaScript'))) {
220
- analysis.language = 'JavaScript';
221
- analysis.runtime = 'Node.js';
253
+ else if (deps.turbo) {
254
+ analysis.buildTool = 'Turborepo';
222
255
  }
223
256
  // Collect frameworks
224
- if (analysis.frontend)
257
+ if (analysis.frontend) {
225
258
  analysis.frameworks.push(analysis.frontend);
226
- if (analysis.backend)
259
+ }
260
+ if (analysis.backend) {
227
261
  analysis.frameworks.push(analysis.backend);
262
+ }
228
263
  return analysis;
229
264
  }
230
265
  /**
231
- * Calculate enhanced quality score (targeting 90%+)
232
- */
233
- function calculateEnhancedScore(metadata, has6Ws, hasPackageJson, hasReadme) {
234
- let score = 0;
235
- // Base metadata (25%)
236
- if (metadata.description)
237
- score += 5;
238
- if (metadata.stars && parseInt(metadata.stars.replace(/[KM]/g, '')) > 0)
239
- score += 5;
240
- if (metadata.license)
241
- score += 5;
242
- if (metadata.topics && metadata.topics.length > 0)
243
- score += 5;
244
- if (metadata.languages && metadata.languages.length > 0)
245
- score += 5;
246
- // README extraction (30%)
247
- if (hasReadme) {
248
- score += 10; // Has README
249
- if (has6Ws)
250
- score += 20; // Extracted 6 Ws
251
- }
252
- // package.json analysis (15%)
253
- if (hasPackageJson) {
254
- score += 15; // Deep dependency analysis
255
- }
256
- // Stack detection (10%)
257
- if (metadata.hasPackageJson || metadata.hasTsConfig)
258
- score += 5;
259
- if (metadata.hasDockerfile)
260
- score += 5;
261
- // Recent activity (10%)
262
- if (metadata.lastUpdated) {
263
- const daysSince = (Date.now() - new Date(metadata.lastUpdated).getTime()) / (1000 * 60 * 60 * 24);
264
- if (daysSince < 90)
265
- score += 10;
266
- else if (daysSince < 180)
267
- score += 5;
268
- }
269
- // File structure (5%)
270
- score += 5;
271
- // AI-ready structure (5%)
272
- score += 5; // Full FAF schema
273
- return Math.min(100, score);
274
- }
275
- /**
276
- * Generate full FAF structure (matching faf-cli init output)
266
+ * Generate clean .faf file from GitHub repo metadata
277
267
  */
278
268
  async function generateEnhancedFaf(metadata, files) {
279
- const timestamp = new Date().toISOString();
280
269
  // Fetch README.md
281
270
  const readme = await (0, github_extractor_1.fetchGitHubFileContent)(metadata.owner, metadata.repo, 'README.md', metadata.defaultBranch);
282
271
  // Fetch package.json
@@ -294,40 +283,40 @@ async function generateEnhancedFaf(metadata, files) {
294
283
  const sixWs = readme
295
284
  ? extract6WsFromReadme(readme, metadata)
296
285
  : {
297
- who: 'Open source contributors',
286
+ who: DEFAULT_WHO,
298
287
  what: metadata.description || 'Software project',
299
- why: 'Solve problems with code',
288
+ why: '',
300
289
  where: 'GitHub',
301
- when: 'Production',
302
- how: 'See repository',
290
+ when: 'Active',
291
+ how: DEFAULT_HOW,
303
292
  confidence: 25
304
293
  };
305
- // Extract stack from GH API languages (SOURCE OF TRUTH)
294
+ // Extract stack from GitHub API languages (source of truth)
306
295
  const langStack = extractFromLanguages(metadata);
307
- // Analyze stack from package.json (if exists - adds npm-specific detail)
296
+ // Analyze stack from package.json (adds npm-specific detail)
308
297
  const npmStack = packageJsonContent
309
298
  ? analyzePackageJson(packageJsonContent, metadata)
310
299
  : { frameworks: [] };
311
300
  // Merge: npm takes priority for fields it detects (more specific)
312
301
  const stackAnalysis = { ...langStack, ...npmStack, frameworks: npmStack.frameworks || [] };
302
+ // Determine main language
303
+ const mainLanguage = stackAnalysis.language
304
+ || metadata.languages?.[0]?.split(' ')[0]
305
+ || null;
313
306
  // Determine project type
314
307
  const projectType = determineProjectType(metadata, stackAnalysis, packageJsonContent);
315
- // Calculate REAL score using slot-counting (not hardcoded formula)
316
- // IMPORTANT: Values must match what's written to .faf file (apply same defaults)
308
+ // Calculate score internally for CLI display (slot-counting)
317
309
  const slotCount = (0, slot_counter_1.countSlots)({
318
- // Project (4)
319
310
  projectName: metadata.repo,
320
311
  projectGoal: metadata.description || null,
321
- mainLanguage: stackAnalysis.language || metadata.languages?.[0]?.split(' ')[0] || 'Unknown',
312
+ mainLanguage: mainLanguage || 'Unknown',
322
313
  projectType: projectType,
323
- // Human context (6)
324
314
  who: sixWs.who,
325
315
  what: sixWs.what,
326
- why: sixWs.why,
316
+ why: sixWs.why || 'slotignored',
327
317
  where: sixWs.where,
328
318
  when: sixWs.when,
329
319
  how: sixWs.how,
330
- // Stack (11) - Apply same defaults as written to .faf (MUST MATCH!)
331
320
  frontend: stackAnalysis.frontend || 'slotignored',
332
321
  uiLibrary: 'slotignored',
333
322
  backend: stackAnalysis.backend || 'slotignored',
@@ -336,148 +325,111 @@ async function generateEnhancedFaf(metadata, files) {
336
325
  build: stackAnalysis.buildTool || 'slotignored',
337
326
  packageManager: packageJsonContent ? 'npm' : 'slotignored',
338
327
  apiType: 'slotignored',
339
- hosting: stackAnalysis.hosting || (metadata.topics?.includes('vercel') || metadata.topics?.includes('netlify') ? 'Cloud' : 'slotignored'),
328
+ hosting: stackAnalysis.hosting || 'slotignored',
340
329
  cicd: 'slotignored',
341
330
  cssFramework: 'slotignored'
342
331
  });
343
332
  const score = slotCount.score;
344
- // Generate full FAF structure
333
+ // === Build clean .faf output ===
334
+ // Project section
335
+ const project = {
336
+ name: metadata.repo,
337
+ };
338
+ if (metadata.description) {
339
+ project.description = metadata.description;
340
+ }
341
+ if (mainLanguage) {
342
+ project.language = mainLanguage;
343
+ }
344
+ project.type = projectType;
345
+ if (metadata.license && metadata.license !== 'NOASSERTION') {
346
+ project.license = metadata.license;
347
+ }
348
+ // Metadata section
349
+ const metadataSection = {
350
+ repository: metadata.url,
351
+ owner: metadata.owner,
352
+ };
353
+ if (metadata.stars && metadata.stars !== '0') {
354
+ metadataSection.stars = metadata.stars;
355
+ }
356
+ if (metadata.forks && metadata.forks !== '0') {
357
+ metadataSection.forks = metadata.forks;
358
+ }
359
+ if (metadata.topics && metadata.topics.length > 0) {
360
+ metadataSection.topics = metadata.topics;
361
+ }
362
+ if (metadata.languages && metadata.languages.length > 0) {
363
+ // Top 6 languages — enough to understand the stack, not noise
364
+ metadataSection.languages = metadata.languages.slice(0, 6);
365
+ }
366
+ metadataSection.default_branch = metadata.defaultBranch || 'main';
367
+ // Stack section — only populated fields, no slotignored
368
+ const stack = {};
369
+ if (stackAnalysis.frontend) {
370
+ stack.frontend = stackAnalysis.frontend;
371
+ }
372
+ if (stackAnalysis.backend) {
373
+ stack.backend = stackAnalysis.backend;
374
+ }
375
+ if (stackAnalysis.runtime && stackAnalysis.runtime !== mainLanguage) {
376
+ stack.runtime = stackAnalysis.runtime;
377
+ }
378
+ if (stackAnalysis.database) {
379
+ stack.database = stackAnalysis.database;
380
+ }
381
+ if (stackAnalysis.buildTool) {
382
+ stack.build = stackAnalysis.buildTool;
383
+ }
384
+ if (stackAnalysis.testing) {
385
+ stack.testing = stackAnalysis.testing;
386
+ }
387
+ if (packageJsonContent) {
388
+ stack.package_manager = 'npm';
389
+ }
390
+ if (stackAnalysis.hosting) {
391
+ stack.hosting = stackAnalysis.hosting;
392
+ }
393
+ // Context section — only non-default, actually extracted values
394
+ const context = {};
395
+ if (sixWs.what && sixWs.what !== metadata.description) {
396
+ // Only include if different from description (avoids duplication)
397
+ context.what = sixWs.what;
398
+ }
399
+ if (sixWs.who && sixWs.who !== DEFAULT_WHO) {
400
+ context.who = sixWs.who;
401
+ }
402
+ if (sixWs.why) {
403
+ context.why = sixWs.why;
404
+ }
405
+ if (sixWs.how && sixWs.how !== DEFAULT_HOW) {
406
+ context.how = sixWs.how;
407
+ }
408
+ // Assemble the data
345
409
  const fafData = {
346
410
  faf_version: '2.5.0',
347
- generated: timestamp,
348
- ai_scoring_system: '2025-09-20',
349
- ai_score: `${score}%`,
350
- ai_confidence: score >= 80 ? 'HIGH' : score >= 60 ? 'MEDIUM' : 'LOW',
351
- ai_value: '30_seconds_replaces_20_minutes_of_questions',
352
- ai_tldr: {
353
- project: metadata.repo,
354
- stack: stackAnalysis.frameworks.join('/') || 'See stack section',
355
- quality_bar: 'production_ready',
356
- current_focus: 'See README for details',
357
- your_role: 'Build features with perfect context'
358
- },
359
- instant_context: {
360
- tech_stack: stackAnalysis.frameworks.join('/') || 'See metadata',
361
- main_language: stackAnalysis.language || metadata.languages?.[0]?.split(' ')[0] || 'Unknown',
362
- key_files: getKeyFiles(files),
363
- },
364
- context_quality: {
365
- slots_filled: `${slotCount.filled + slotCount.ignored}/21 (${score}%)`,
366
- ai_confidence: score >= 80 ? 'HIGH' : score >= 60 ? 'MEDIUM' : 'LOW',
367
- handoff_ready: score >= 85,
368
- missing_context: slotCount.missingSlots.length > 0 ? slotCount.missingSlots : ['None - fully specified!']
369
- },
370
- project: {
371
- name: metadata.repo,
372
- goal: metadata.description || null,
373
- main_language: stackAnalysis.language || metadata.languages?.[0]?.split(' ')[0] || 'Unknown',
374
- type: projectType
375
- },
376
- ai_instructions: {
377
- priority_order: [
378
- '1. Read THIS .faf file first',
379
- '2. Check README.md for usage details',
380
- '3. Review package.json for dependencies'
381
- ],
382
- working_style: {
383
- code_first: true,
384
- explanations: 'minimal',
385
- quality_bar: 'zero_errors',
386
- testing: 'required'
387
- },
388
- warnings: [
389
- 'Check README for project-specific guidelines',
390
- 'Review existing code style before modifying'
391
- ]
392
- },
393
- stack: {
394
- frontend: stackAnalysis.frontend || 'slotignored',
395
- ui_library: 'slotignored', // TODO: Detect from package.json
396
- backend: stackAnalysis.backend || 'slotignored',
397
- runtime: stackAnalysis.runtime || 'slotignored',
398
- database: stackAnalysis.database || 'slotignored',
399
- build: stackAnalysis.buildTool || 'slotignored',
400
- package_manager: packageJsonContent ? 'npm' : 'slotignored',
401
- api_type: 'slotignored', // TODO: Detect API type
402
- hosting: stackAnalysis.hosting || (metadata.topics?.includes('vercel') || metadata.topics?.includes('netlify') ? 'Cloud' : 'slotignored'),
403
- cicd: 'slotignored', // TODO: Detect from .github/workflows
404
- css_framework: 'slotignored' // TODO: Detect from package.json
405
- },
406
- metadata: {
407
- url: metadata.url,
408
- owner: metadata.owner,
409
- repository: metadata.repo,
410
- description: metadata.description || 'No description',
411
- stars: metadata.stars || '0',
412
- forks: metadata.forks || '0',
413
- license: metadata.license || 'Not specified',
414
- topics: metadata.topics || [],
415
- languages: metadata.languages || [],
416
- last_updated: metadata.lastUpdated,
417
- default_branch: metadata.defaultBranch
418
- },
419
- human_context: {
420
- who: sixWs.who,
421
- what: sixWs.what,
422
- why: sixWs.why,
423
- where: sixWs.where,
424
- when: sixWs.when,
425
- how: sixWs.how,
426
- additional_context: readme ? 'See README.md for full details' : null,
427
- context_score: sixWs.confidence,
428
- total_prd_score: score,
429
- success_rate: `${Math.min(100, sixWs.confidence)}%`
430
- },
431
- scores: {
432
- faf_score: score,
433
- slot_based_percentage: score,
434
- total_slots: 21
435
- },
436
- tags: {
437
- auto_generated: [
438
- metadata.repo,
439
- ...(metadata.topics || []).slice(0, 5)
440
- ],
441
- smart_defaults: [
442
- '.faf',
443
- 'ai-ready',
444
- new Date().getFullYear().toString(),
445
- 'github',
446
- 'open-source'
447
- ]
448
- },
449
- generated_by: {
450
- tool: 'faf-cli',
451
- command: `faf git ${metadata.owner}/${metadata.repo}`,
452
- version: '4.3.0',
453
- source: 'github-api'
454
- }
411
+ project,
412
+ metadata: metadataSection,
413
+ };
414
+ if (Object.keys(stack).length > 0) {
415
+ fafData.stack = stack;
416
+ }
417
+ if (Object.keys(context).length > 0) {
418
+ fafData.context = context;
419
+ }
420
+ fafData.generated_by = {
421
+ tool: 'faf-cli',
422
+ version: '4.4.3',
423
+ command: `faf git ${metadata.owner}/${metadata.repo}`,
455
424
  };
456
425
  // Convert to YAML
457
426
  const { stringify: stringifyYAML } = require('../fix-once/yaml');
458
427
  const yamlContent = stringifyYAML(fafData);
459
- // Add header
460
- const tier = getScoreTier(score);
461
- const header = `# ========================================
462
- # 🏎️ FAF GIT - ENHANCED GENERATION
463
- # ========================================
464
- #
465
- # Repository: ${metadata.owner}/${metadata.repo}
466
- # URL: ${metadata.url}
467
- # Description: ${metadata.description || 'No description'}
468
- # Stars: ${metadata.stars || '0'} | Forks: ${metadata.forks || '0'}
469
- # License: ${metadata.license || 'Not specified'}
470
- #
471
- # Quality Score: ${score}% ${tier}
472
- # 6 Ws Confidence: ${sixWs.confidence}%
473
- #
474
- # Generated: ${timestamp}
475
- # Command: npx faf-cli git ${metadata.owner}/${metadata.repo}
476
- # Strategy: README extraction + package.json analysis + smart defaults
477
- #
478
- # ========================================
479
- # AI-READY CONTEXT (No Cloning Required!)
480
- # ========================================
428
+ // Clean, informative header
429
+ const header = `# project.faf — Machine-readable project context for AI tools
430
+ # ${metadata.url}
431
+ # Spec: https://faf.dev | MIME: application/vnd.faf+yaml
432
+ # Generated: npx faf-cli git ${metadata.owner}/${metadata.repo}
481
433
 
482
434
  `;
483
435
  return {
@@ -485,43 +437,75 @@ async function generateEnhancedFaf(metadata, files) {
485
437
  score
486
438
  };
487
439
  }
488
- // Helper functions
440
+ // === Helper functions ===
489
441
  function cleanText(text) {
490
442
  return text
491
443
  .replace(/\*\*/g, '')
492
444
  .replace(/\n+/g, ' ')
493
445
  .trim();
494
446
  }
447
+ /**
448
+ * Score tier for CLI display (not written to file)
449
+ */
495
450
  function getScoreTier(score) {
496
- if (score >= 100)
451
+ if (score >= 100) {
497
452
  return '🏆 Trophy';
498
- if (score >= 99)
453
+ }
454
+ if (score >= 99) {
499
455
  return '🥇 Gold';
500
- if (score >= 95)
456
+ }
457
+ if (score >= 95) {
501
458
  return '🥈 Silver';
502
- if (score >= 85)
459
+ }
460
+ if (score >= 85) {
503
461
  return '🥉 Bronze';
504
- if (score >= 70)
462
+ }
463
+ if (score >= 70) {
505
464
  return '🟢 Green';
506
- if (score >= 55)
465
+ }
466
+ if (score >= 55) {
507
467
  return '🟡 Yellow';
508
- if (score > 0)
468
+ }
469
+ if (score > 0) {
509
470
  return '🔴 Red';
471
+ }
510
472
  return '🤍 White';
511
473
  }
512
474
  function determineProjectType(metadata, stack, packageJson) {
513
- if (packageJson?.bin)
475
+ // CLI tools
476
+ if (packageJson?.bin) {
514
477
  return 'cli';
515
- if (stack.frontend && stack.backend)
478
+ }
479
+ // Detect from topics
480
+ const topics = (metadata.topics || []).map(t => t.toLowerCase());
481
+ if (topics.includes('framework') || topics.includes('library')) {
482
+ return 'library';
483
+ }
484
+ // Language/runtime repos
485
+ const languageRepos = ['cpython', 'rust', 'go', 'swift', 'deno', 'bun', 'node'];
486
+ if (languageRepos.includes(metadata.repo.toLowerCase())) {
487
+ return 'runtime';
488
+ }
489
+ // Full-stack / frontend / backend
490
+ if (stack.frontend && stack.backend) {
516
491
  return 'full-stack';
517
- if (stack.frontend)
492
+ }
493
+ if (stack.frontend) {
518
494
  return 'frontend';
519
- if (stack.backend)
495
+ }
496
+ if (stack.backend) {
520
497
  return 'backend';
521
- if (stack.database)
498
+ }
499
+ if (stack.database) {
522
500
  return 'database';
523
- if (packageJson?.name?.includes('lib'))
501
+ }
502
+ // Library detection
503
+ if (packageJson?.name?.includes('lib') || packageJson?.name?.includes('sdk')) {
504
+ return 'library';
505
+ }
506
+ if (topics.includes('sdk') || topics.includes('api')) {
524
507
  return 'library';
508
+ }
525
509
  return 'application';
526
510
  }
527
511
  function getKeyFiles(files) {
@@ -540,18 +524,4 @@ function getKeyFiles(files) {
540
524
  .map(f => f.path)
541
525
  .slice(0, 10);
542
526
  }
543
- function getMissingContext(stack, score) {
544
- const missing = [];
545
- if (!stack.database && score < 90)
546
- missing.push('Database');
547
- if (!stack.testing && score < 90)
548
- missing.push('Testing framework');
549
- if (!stack.buildTool && score < 85)
550
- missing.push('Build tool');
551
- if (score < 80)
552
- missing.push('Deployment info');
553
- if (score < 75)
554
- missing.push('CI/CD pipeline');
555
- return missing;
556
- }
557
527
  //# sourceMappingURL=faf-git-generator.js.map