faf-cli 4.4.2 → 4.4.4
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/README.md +6 -0
- package/dist/cli.js +12 -8
- package/dist/cli.js.map +1 -1
- package/dist/commands/index.d.ts +12 -0
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +5 -4
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/readme.js +16 -10
- package/dist/commands/readme.js.map +1 -1
- package/dist/core-extraction-fixes.js +9 -5
- package/dist/core-extraction-fixes.js.map +1 -1
- package/dist/engines/c-mirror/broadcast/terminal-display.js +2 -1
- package/dist/engines/c-mirror/broadcast/terminal-display.js.map +1 -1
- package/dist/engines/c-mirror/core/claude-to-faf.js +12 -6
- package/dist/engines/c-mirror/core/claude-to-faf.js.map +1 -1
- package/dist/engines/c-mirror/core/events/event-emitter.js +12 -6
- package/dist/engines/c-mirror/core/events/event-emitter.js.map +1 -1
- package/dist/engines/v252-hybrid-engine.js +0 -1
- package/dist/engines/v252-hybrid-engine.js.map +1 -1
- package/dist/framework-detector.js +14 -7
- package/dist/framework-detector.js.map +1 -1
- package/dist/github/faf-git-generator.d.ts +16 -22
- package/dist/github/faf-git-generator.d.ts.map +1 -1
- package/dist/github/faf-git-generator.js +290 -320
- package/dist/github/faf-git-generator.js.map +1 -1
- package/dist/github/github-extractor.d.ts.map +1 -1
- package/dist/github/github-extractor.js +19 -20
- package/dist/github/github-extractor.js.map +1 -1
- package/dist/github/repo-selector.js +13 -7
- package/dist/github/repo-selector.js.map +1 -1
- package/dist/smart-faf.js +3 -2
- package/dist/smart-faf.js.map +1 -1
- package/package.json +6 -4
- package/project.faf +5 -5
|
@@ -1,35 +1,33 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* FAF Generation for `faf git`
|
|
4
4
|
*
|
|
5
|
-
* Generates
|
|
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
|
|
21
|
+
* Extract 6 Ws from README content
|
|
24
22
|
*/
|
|
25
23
|
function extract6WsFromReadme(readme, metadata) {
|
|
26
24
|
const result = {
|
|
27
|
-
who:
|
|
25
|
+
who: DEFAULT_WHO,
|
|
28
26
|
what: metadata.description || 'Software project',
|
|
29
|
-
why: '
|
|
30
|
-
where: 'GitHub
|
|
31
|
-
when: '
|
|
32
|
-
how:
|
|
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
|
|
71
|
-
/(?:Built for|Designed for|Perfect for)\s+([^.\n]{
|
|
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
|
-
|
|
77
|
-
|
|
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:
|
|
82
|
-
if (readme.includes('
|
|
83
|
-
result.how = '
|
|
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.
|
|
87
|
-
result.how = 'See
|
|
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
|
|
95
|
-
*
|
|
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
|
-
//
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
analysis.
|
|
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
|
-
|
|
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 (
|
|
160
|
+
else if (allLangs.includes('makefile')) {
|
|
145
161
|
analysis.buildTool = 'Make';
|
|
146
162
|
}
|
|
147
|
-
else if (
|
|
163
|
+
else if (allLangs.includes('gradle')) {
|
|
148
164
|
analysis.buildTool = 'Gradle';
|
|
149
165
|
}
|
|
150
|
-
else if (
|
|
166
|
+
else if (allLangs.includes('maven')) {
|
|
151
167
|
analysis.buildTool = 'Maven';
|
|
152
168
|
}
|
|
153
|
-
|
|
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
|
-
|
|
188
|
+
}
|
|
189
|
+
else if (deps.vue) {
|
|
171
190
|
analysis.frontend = 'Vue';
|
|
172
|
-
|
|
191
|
+
}
|
|
192
|
+
else if (deps.svelte) {
|
|
173
193
|
analysis.frontend = 'Svelte';
|
|
174
|
-
|
|
194
|
+
}
|
|
195
|
+
else if (deps['@angular/core']) {
|
|
175
196
|
analysis.frontend = 'Angular';
|
|
176
|
-
|
|
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
|
-
|
|
204
|
+
}
|
|
205
|
+
else if (deps.fastify) {
|
|
182
206
|
analysis.backend = 'Fastify';
|
|
183
|
-
|
|
207
|
+
}
|
|
208
|
+
else if (deps.koa) {
|
|
184
209
|
analysis.backend = 'Koa';
|
|
185
|
-
|
|
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
|
-
|
|
217
|
+
}
|
|
218
|
+
else if (deps.pg || deps.postgres) {
|
|
191
219
|
analysis.database = 'PostgreSQL';
|
|
192
|
-
|
|
220
|
+
}
|
|
221
|
+
else if (deps.mysql || deps.mysql2) {
|
|
193
222
|
analysis.database = 'MySQL';
|
|
194
|
-
|
|
223
|
+
}
|
|
224
|
+
else if (deps.sqlite3 || deps['better-sqlite3']) {
|
|
195
225
|
analysis.database = 'SQLite';
|
|
196
|
-
|
|
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
|
-
|
|
233
|
+
}
|
|
234
|
+
else if (deps.vitest) {
|
|
202
235
|
analysis.testing = 'Vitest';
|
|
203
|
-
|
|
236
|
+
}
|
|
237
|
+
else if (deps.mocha) {
|
|
204
238
|
analysis.testing = 'Mocha';
|
|
205
|
-
|
|
206
|
-
|
|
239
|
+
}
|
|
240
|
+
// Build tools
|
|
241
|
+
if (deps.vite) {
|
|
207
242
|
analysis.buildTool = 'Vite';
|
|
208
|
-
|
|
243
|
+
}
|
|
244
|
+
else if (deps.webpack) {
|
|
209
245
|
analysis.buildTool = 'Webpack';
|
|
210
|
-
|
|
246
|
+
}
|
|
247
|
+
else if (deps.rollup) {
|
|
211
248
|
analysis.buildTool = 'Rollup';
|
|
212
|
-
|
|
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 (
|
|
220
|
-
analysis.
|
|
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
|
-
|
|
259
|
+
}
|
|
260
|
+
if (analysis.backend) {
|
|
227
261
|
analysis.frameworks.push(analysis.backend);
|
|
262
|
+
}
|
|
228
263
|
return analysis;
|
|
229
264
|
}
|
|
230
265
|
/**
|
|
231
|
-
*
|
|
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:
|
|
286
|
+
who: DEFAULT_WHO,
|
|
298
287
|
what: metadata.description || 'Software project',
|
|
299
|
-
why: '
|
|
288
|
+
why: '',
|
|
300
289
|
where: 'GitHub',
|
|
301
|
-
when: '
|
|
302
|
-
how:
|
|
290
|
+
when: 'Active',
|
|
291
|
+
how: DEFAULT_HOW,
|
|
303
292
|
confidence: 25
|
|
304
293
|
};
|
|
305
|
-
// Extract stack from
|
|
294
|
+
// Extract stack from GitHub API languages (source of truth)
|
|
306
295
|
const langStack = extractFromLanguages(metadata);
|
|
307
|
-
// Analyze stack from package.json (
|
|
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
|
|
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:
|
|
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 ||
|
|
328
|
+
hosting: stackAnalysis.hosting || 'slotignored',
|
|
340
329
|
cicd: 'slotignored',
|
|
341
330
|
cssFramework: 'slotignored'
|
|
342
331
|
});
|
|
343
332
|
const score = slotCount.score;
|
|
344
|
-
//
|
|
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
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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
|
-
//
|
|
460
|
-
const
|
|
461
|
-
|
|
462
|
-
#
|
|
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
|
-
|
|
453
|
+
}
|
|
454
|
+
if (score >= 99) {
|
|
499
455
|
return '🥇 Gold';
|
|
500
|
-
|
|
456
|
+
}
|
|
457
|
+
if (score >= 95) {
|
|
501
458
|
return '🥈 Silver';
|
|
502
|
-
|
|
459
|
+
}
|
|
460
|
+
if (score >= 85) {
|
|
503
461
|
return '🥉 Bronze';
|
|
504
|
-
|
|
462
|
+
}
|
|
463
|
+
if (score >= 70) {
|
|
505
464
|
return '🟢 Green';
|
|
506
|
-
|
|
465
|
+
}
|
|
466
|
+
if (score >= 55) {
|
|
507
467
|
return '🟡 Yellow';
|
|
508
|
-
|
|
468
|
+
}
|
|
469
|
+
if (score > 0) {
|
|
509
470
|
return '🔴 Red';
|
|
471
|
+
}
|
|
510
472
|
return '🤍 White';
|
|
511
473
|
}
|
|
512
474
|
function determineProjectType(metadata, stack, packageJson) {
|
|
513
|
-
|
|
475
|
+
// CLI tools
|
|
476
|
+
if (packageJson?.bin) {
|
|
514
477
|
return 'cli';
|
|
515
|
-
|
|
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
|
-
|
|
492
|
+
}
|
|
493
|
+
if (stack.frontend) {
|
|
518
494
|
return 'frontend';
|
|
519
|
-
|
|
495
|
+
}
|
|
496
|
+
if (stack.backend) {
|
|
520
497
|
return 'backend';
|
|
521
|
-
|
|
498
|
+
}
|
|
499
|
+
if (stack.database) {
|
|
522
500
|
return 'database';
|
|
523
|
-
|
|
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
|