faf-mcp 1.3.1 → 2.0.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.
- package/CHANGELOG.md +32 -0
- package/CLAUDE.md +5 -5
- package/README.md +70 -14
- package/assets/thumbnail.png +0 -0
- package/dist/src/faf-core/commands/agents.d.ts +29 -0
- package/dist/src/faf-core/commands/agents.js +140 -0
- package/dist/src/faf-core/commands/agents.js.map +1 -0
- package/dist/src/faf-core/commands/bi-sync.d.ts +9 -5
- package/dist/src/faf-core/commands/bi-sync.js +89 -85
- package/dist/src/faf-core/commands/bi-sync.js.map +1 -1
- package/dist/src/faf-core/commands/conductor.d.ts +25 -0
- package/dist/src/faf-core/commands/conductor.js +157 -0
- package/dist/src/faf-core/commands/conductor.js.map +1 -0
- package/dist/src/faf-core/commands/cursor.d.ts +29 -0
- package/dist/src/faf-core/commands/cursor.js +134 -0
- package/dist/src/faf-core/commands/cursor.js.map +1 -0
- package/dist/src/faf-core/commands/gemini.d.ts +29 -0
- package/dist/src/faf-core/commands/gemini.js +133 -0
- package/dist/src/faf-core/commands/gemini.js.map +1 -0
- package/dist/src/faf-core/commands/git-context.d.ts +23 -0
- package/dist/src/faf-core/commands/git-context.js +66 -0
- package/dist/src/faf-core/commands/git-context.js.map +1 -0
- package/dist/src/faf-core/commands/human.d.ts +27 -0
- package/dist/src/faf-core/commands/human.js +183 -0
- package/dist/src/faf-core/commands/human.js.map +1 -0
- package/dist/src/faf-core/commands/readme.d.ts +47 -0
- package/dist/src/faf-core/commands/readme.js +361 -0
- package/dist/src/faf-core/commands/readme.js.map +1 -0
- package/dist/src/faf-core/engines/fab-formats-processor.js +3 -3
- package/dist/src/faf-core/engines/fab-formats-processor.js.map +1 -1
- package/dist/src/faf-core/engines/faf-dna.d.ts +1 -1
- package/dist/src/faf-core/engines/faf-dna.js +4 -3
- package/dist/src/faf-core/engines/faf-dna.js.map +1 -1
- package/dist/src/faf-core/generators/faf-generator-championship.js +9 -8
- package/dist/src/faf-core/generators/faf-generator-championship.js.map +1 -1
- package/dist/src/faf-core/parsers/agents-parser.d.ts +59 -0
- package/dist/src/faf-core/parsers/agents-parser.js +324 -0
- package/dist/src/faf-core/parsers/agents-parser.js.map +1 -0
- package/dist/src/faf-core/parsers/conductor-parser.d.ts +85 -0
- package/dist/src/faf-core/parsers/conductor-parser.js +293 -0
- package/dist/src/faf-core/parsers/conductor-parser.js.map +1 -0
- package/dist/src/faf-core/parsers/cursorrules-parser.d.ts +57 -0
- package/dist/src/faf-core/parsers/cursorrules-parser.js +317 -0
- package/dist/src/faf-core/parsers/cursorrules-parser.js.map +1 -0
- package/dist/src/faf-core/parsers/faf-git-generator.d.ts +53 -0
- package/dist/src/faf-core/parsers/faf-git-generator.js +512 -0
- package/dist/src/faf-core/parsers/faf-git-generator.js.map +1 -0
- package/dist/src/faf-core/parsers/gemini-parser.d.ts +57 -0
- package/dist/src/faf-core/parsers/gemini-parser.js +263 -0
- package/dist/src/faf-core/parsers/gemini-parser.js.map +1 -0
- package/dist/src/faf-core/parsers/github-extractor.d.ts +61 -0
- package/dist/src/faf-core/parsers/github-extractor.js +374 -0
- package/dist/src/faf-core/parsers/github-extractor.js.map +1 -0
- package/dist/src/faf-core/parsers/index.d.ts +12 -0
- package/dist/src/{compiler → faf-core/parsers}/index.js +10 -5
- package/dist/src/faf-core/parsers/index.js.map +1 -0
- package/dist/src/faf-core/parsers/slot-counter.d.ts +55 -0
- package/dist/src/faf-core/parsers/slot-counter.js +100 -0
- package/dist/src/faf-core/parsers/slot-counter.js.map +1 -0
- package/dist/src/faf-core/utils/balance-visualizer.js +3 -3
- package/dist/src/faf-core/utils/balance-visualizer.js.map +1 -1
- package/dist/src/handlers/championship-tools.d.ts +10 -5
- package/dist/src/handlers/championship-tools.js +259 -232
- package/dist/src/handlers/championship-tools.js.map +1 -1
- package/dist/src/handlers/engine-adapter.js +238 -5
- package/dist/src/handlers/engine-adapter.js.map +1 -1
- package/dist/src/handlers/tools.d.ts +61 -0
- package/dist/src/handlers/tools.js +2103 -102
- package/dist/src/handlers/tools.js.map +1 -1
- package/dist/src/index.js +0 -0
- package/dist/src/types/tool-visibility.js +51 -10
- package/dist/src/types/tool-visibility.js.map +1 -1
- package/dist/src/utils/championship-format.js +11 -9
- package/dist/src/utils/championship-format.js.map +1 -1
- package/dist/src/utils/path-resolver.js +56 -2
- package/dist/src/utils/path-resolver.js.map +1 -1
- package/dist/src/utils/visual-style.js +7 -5
- package/dist/src/utils/visual-style.js.map +1 -1
- package/dist/src/version.js +24 -11
- package/dist/src/version.js.map +1 -1
- package/package.json +5 -3
- package/project.faf +14 -18
- package/dist/index.json +0 -1
- package/dist/src/compiler/index.d.ts +0 -7
- package/dist/src/compiler/index.js.map +0 -1
- package/dist/src/compiler/scorer.d.ts +0 -53
- package/dist/src/compiler/scorer.js +0 -189
- package/dist/src/compiler/scorer.js.map +0 -1
- package/dist/src/compiler/slot-validator.d.ts +0 -32
- package/dist/src/compiler/slot-validator.js +0 -293
- package/dist/src/compiler/slot-validator.js.map +0 -1
- package/dist/src/compiler/type-detector.d.ts +0 -62
- package/dist/src/compiler/type-detector.js +0 -388
- package/dist/src/compiler/type-detector.js.map +0 -1
- package/dist/src/types/project-types.d.ts +0 -22
- package/dist/src/types/project-types.js +0 -85
- package/dist/src/types/project-types.js.map +0 -1
- package/dist/src/types/slots.d.ts +0 -39
- package/dist/src/types/slots.js +0 -162
- package/dist/src/types/slots.js.map +0 -1
|
@@ -36,7 +36,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.FafToolHandler = void 0;
|
|
37
37
|
const fileHandler_1 = require("./fileHandler");
|
|
38
38
|
const fs = __importStar(require("fs"));
|
|
39
|
-
const
|
|
39
|
+
const os = __importStar(require("os"));
|
|
40
|
+
const pathModule = __importStar(require("path"));
|
|
40
41
|
const fuzzy_detector_1 = require("../utils/fuzzy-detector");
|
|
41
42
|
const faf_file_finder_js_1 = require("../utils/faf-file-finder.js");
|
|
42
43
|
const version_1 = require("../version");
|
|
@@ -46,105 +47,200 @@ class FafToolHandler {
|
|
|
46
47
|
constructor(engineAdapter) {
|
|
47
48
|
this.engineAdapter = engineAdapter;
|
|
48
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Get the project path - uses explicit path if provided, otherwise returns current context
|
|
52
|
+
* If an explicit path is provided, it also sets the session context for subsequent calls
|
|
53
|
+
*/
|
|
54
|
+
getProjectPath(explicitPath) {
|
|
55
|
+
if (explicitPath) {
|
|
56
|
+
// Expand tilde
|
|
57
|
+
const expandedPath = explicitPath.startsWith('~')
|
|
58
|
+
? pathModule.join(os.homedir(), explicitPath.slice(1))
|
|
59
|
+
: explicitPath;
|
|
60
|
+
// Resolve to absolute path
|
|
61
|
+
const resolvedPath = pathModule.resolve(expandedPath);
|
|
62
|
+
// If it's a file path, get the directory
|
|
63
|
+
const projectDir = fs.existsSync(resolvedPath) && fs.statSync(resolvedPath).isFile()
|
|
64
|
+
? pathModule.dirname(resolvedPath)
|
|
65
|
+
: resolvedPath;
|
|
66
|
+
// Set as the new session context
|
|
67
|
+
if (fs.existsSync(projectDir)) {
|
|
68
|
+
this.engineAdapter.setWorkingDirectory(projectDir);
|
|
69
|
+
}
|
|
70
|
+
return projectDir;
|
|
71
|
+
}
|
|
72
|
+
return this.engineAdapter.getWorkingDirectory();
|
|
73
|
+
}
|
|
49
74
|
async listTools() {
|
|
50
75
|
return {
|
|
51
76
|
tools: [
|
|
52
77
|
{
|
|
53
78
|
name: 'faf_about',
|
|
54
|
-
description: 'Learn what .faf format is -
|
|
79
|
+
description: 'Learn what .faf format is - project DNA for AI 🧡⚡️',
|
|
80
|
+
annotations: {
|
|
81
|
+
title: 'About FAF',
|
|
82
|
+
readOnlyHint: true,
|
|
83
|
+
openWorldHint: false
|
|
84
|
+
},
|
|
55
85
|
inputSchema: {
|
|
56
86
|
type: 'object',
|
|
57
87
|
properties: {},
|
|
88
|
+
additionalProperties: false
|
|
58
89
|
}
|
|
59
90
|
},
|
|
60
91
|
{
|
|
61
92
|
name: 'faf_what',
|
|
62
|
-
description: 'What is .faf format? Quick explanation of
|
|
93
|
+
description: 'What is .faf format? Quick explanation of project DNA for AI 🧡⚡️',
|
|
94
|
+
annotations: {
|
|
95
|
+
title: 'What is FAF',
|
|
96
|
+
readOnlyHint: true,
|
|
97
|
+
openWorldHint: false
|
|
98
|
+
},
|
|
63
99
|
inputSchema: {
|
|
64
100
|
type: 'object',
|
|
65
101
|
properties: {},
|
|
102
|
+
additionalProperties: false
|
|
66
103
|
}
|
|
67
104
|
},
|
|
68
105
|
{
|
|
69
106
|
name: 'faf_status',
|
|
70
|
-
description: 'Check if your project has project.faf (
|
|
107
|
+
description: 'Check if your project has project.faf (project DNA for AI) - Shows AI-readability status 🧡⚡️',
|
|
108
|
+
annotations: {
|
|
109
|
+
title: 'Project Status',
|
|
110
|
+
readOnlyHint: true,
|
|
111
|
+
openWorldHint: false
|
|
112
|
+
},
|
|
71
113
|
inputSchema: {
|
|
72
114
|
type: 'object',
|
|
73
|
-
properties: {
|
|
115
|
+
properties: {
|
|
116
|
+
path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
|
|
117
|
+
},
|
|
118
|
+
additionalProperties: false
|
|
74
119
|
}
|
|
75
120
|
},
|
|
76
121
|
{
|
|
77
122
|
name: 'faf_score',
|
|
78
|
-
description: 'Calculate your project\'s AI-readability from project.faf (
|
|
123
|
+
description: 'Calculate your project\'s AI-readability from project.faf (project DNA for AI) - F1-inspired metrics! 🧡⚡️',
|
|
124
|
+
annotations: {
|
|
125
|
+
title: 'AI-Readiness Score',
|
|
126
|
+
readOnlyHint: true,
|
|
127
|
+
openWorldHint: false
|
|
128
|
+
},
|
|
79
129
|
inputSchema: {
|
|
80
130
|
type: 'object',
|
|
81
131
|
properties: {
|
|
82
|
-
details: { type: 'boolean', description: 'Include detailed breakdown and improvement suggestions' }
|
|
132
|
+
details: { type: 'boolean', description: 'Include detailed breakdown and improvement suggestions' },
|
|
133
|
+
path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
|
|
83
134
|
},
|
|
135
|
+
additionalProperties: false
|
|
84
136
|
}
|
|
85
137
|
},
|
|
86
138
|
{
|
|
87
139
|
name: 'faf_init',
|
|
88
|
-
description: 'Create project.faf (
|
|
140
|
+
description: 'Create project.faf (project DNA for AI) - Makes your project instantly AI-readable 🧡⚡️. Just enter path or project name. Examples: ~/Projects/my-app, my-app, /full/path/to/project',
|
|
141
|
+
annotations: {
|
|
142
|
+
title: 'Initialize .faf',
|
|
143
|
+
readOnlyHint: false,
|
|
144
|
+
destructiveHint: false,
|
|
145
|
+
openWorldHint: false
|
|
146
|
+
},
|
|
89
147
|
inputSchema: {
|
|
90
148
|
type: 'object',
|
|
91
149
|
properties: {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
150
|
+
path: {
|
|
151
|
+
type: 'string',
|
|
152
|
+
description: 'Project path or name. Smart resolution: "my-app" finds ~/Projects/my-app OR ~/Code/my-app. Full paths like ~/Projects/app or /Users/me/code/app work too. Omit to use current directory.'
|
|
153
|
+
},
|
|
154
|
+
force: { type: 'boolean', description: 'Overwrite existing project.faf if it exists' }
|
|
96
155
|
},
|
|
156
|
+
additionalProperties: false
|
|
97
157
|
}
|
|
98
158
|
},
|
|
99
159
|
{
|
|
100
160
|
name: 'faf_trust',
|
|
101
|
-
description: 'Validate project.faf integrity - Trust metrics for
|
|
161
|
+
description: 'Validate project.faf integrity - Trust metrics for project DNA for AI 🧡⚡️',
|
|
162
|
+
annotations: {
|
|
163
|
+
title: 'Trust Score',
|
|
164
|
+
readOnlyHint: true,
|
|
165
|
+
openWorldHint: false
|
|
166
|
+
},
|
|
102
167
|
inputSchema: {
|
|
103
168
|
type: 'object',
|
|
104
169
|
properties: {},
|
|
170
|
+
additionalProperties: false
|
|
105
171
|
}
|
|
106
172
|
},
|
|
107
173
|
{
|
|
108
174
|
name: 'faf_sync',
|
|
109
|
-
description: 'Sync project.faf (
|
|
175
|
+
description: 'Sync project.faf (project DNA for AI) with CLAUDE.md - Bi-directional context 🧡⚡️',
|
|
176
|
+
annotations: {
|
|
177
|
+
title: 'Sync .faf to CLAUDE.md',
|
|
178
|
+
readOnlyHint: false,
|
|
179
|
+
destructiveHint: false,
|
|
180
|
+
openWorldHint: false
|
|
181
|
+
},
|
|
110
182
|
inputSchema: {
|
|
111
183
|
type: 'object',
|
|
112
|
-
properties: {
|
|
184
|
+
properties: {
|
|
185
|
+
path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
|
|
186
|
+
},
|
|
187
|
+
additionalProperties: false
|
|
113
188
|
}
|
|
114
189
|
},
|
|
115
190
|
{
|
|
116
191
|
name: 'faf_enhance',
|
|
117
|
-
description: 'Enhance project.faf (
|
|
192
|
+
description: 'Enhance project.faf (project DNA for AI) with AI optimization - SPEEDY AI you can TRUST! 🧡⚡️',
|
|
193
|
+
annotations: {
|
|
194
|
+
title: 'Enhance .faf',
|
|
195
|
+
readOnlyHint: false,
|
|
196
|
+
destructiveHint: false,
|
|
197
|
+
openWorldHint: false
|
|
198
|
+
},
|
|
118
199
|
inputSchema: {
|
|
119
200
|
type: 'object',
|
|
120
201
|
properties: {
|
|
121
202
|
model: { type: 'string', description: 'Target AI model: claude|chatgpt|gemini|universal (default: claude)' },
|
|
122
203
|
focus: { type: 'string', description: 'Enhancement focus: claude-optimal|human-context|ai-instructions|completeness' },
|
|
123
204
|
consensus: { type: 'boolean', description: 'Build consensus from multiple AI models' },
|
|
124
|
-
dryRun: { type: 'boolean', description: 'Preview enhancement without applying changes' }
|
|
205
|
+
dryRun: { type: 'boolean', description: 'Preview enhancement without applying changes' },
|
|
206
|
+
path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
|
|
125
207
|
},
|
|
208
|
+
additionalProperties: false
|
|
126
209
|
}
|
|
127
210
|
},
|
|
128
211
|
{
|
|
129
212
|
name: 'faf_bi_sync',
|
|
130
|
-
description: '
|
|
213
|
+
description: 'Bi-directional sync between project.faf and CLAUDE.md. v4.5.0: Also sync to AGENTS.md, .cursorrules, GEMINI.md!',
|
|
214
|
+
annotations: {
|
|
215
|
+
title: 'Bi-directional Sync',
|
|
216
|
+
readOnlyHint: false,
|
|
217
|
+
destructiveHint: false,
|
|
218
|
+
openWorldHint: false
|
|
219
|
+
},
|
|
131
220
|
inputSchema: {
|
|
132
221
|
type: 'object',
|
|
133
222
|
properties: {
|
|
134
223
|
auto: { type: 'boolean', description: 'Enable automatic synchronization' },
|
|
135
224
|
watch: { type: 'boolean', description: 'Start real-time file watching for changes' },
|
|
136
225
|
force: { type: 'boolean', description: 'Force overwrite conflicting changes' },
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
226
|
+
agents: { type: 'boolean', description: 'Also sync to AGENTS.md (OpenAI/Codex format)' },
|
|
227
|
+
cursor: { type: 'boolean', description: 'Also sync to .cursorrules (Cursor IDE format)' },
|
|
228
|
+
gemini: { type: 'boolean', description: 'Also sync to GEMINI.md (Google Gemini format)' },
|
|
229
|
+
all: { type: 'boolean', description: 'Sync to ALL formats: CLAUDE.md + AGENTS.md + .cursorrules + GEMINI.md' },
|
|
230
|
+
path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
|
|
142
231
|
},
|
|
232
|
+
additionalProperties: false
|
|
143
233
|
}
|
|
144
234
|
},
|
|
145
235
|
{
|
|
146
236
|
name: 'faf_clear',
|
|
147
237
|
description: 'Clear caches, temporary files, and reset FAF state for a fresh start',
|
|
238
|
+
annotations: {
|
|
239
|
+
title: 'Clear .faf Data',
|
|
240
|
+
readOnlyHint: false,
|
|
241
|
+
destructiveHint: true,
|
|
242
|
+
openWorldHint: false
|
|
243
|
+
},
|
|
148
244
|
inputSchema: {
|
|
149
245
|
type: 'object',
|
|
150
246
|
properties: {
|
|
@@ -153,19 +249,31 @@ class FafToolHandler {
|
|
|
153
249
|
backups: { type: 'boolean', description: 'Clear backup files only' },
|
|
154
250
|
all: { type: 'boolean', description: 'Clear everything (default)' }
|
|
155
251
|
},
|
|
252
|
+
additionalProperties: false
|
|
156
253
|
}
|
|
157
254
|
},
|
|
158
255
|
{
|
|
159
256
|
name: 'faf_debug',
|
|
160
257
|
description: 'Debug Claude FAF MCP environment - show working directory, permissions, and FAF CLI status',
|
|
258
|
+
annotations: {
|
|
259
|
+
title: 'Debug Info',
|
|
260
|
+
readOnlyHint: true,
|
|
261
|
+
openWorldHint: false
|
|
262
|
+
},
|
|
161
263
|
inputSchema: {
|
|
162
264
|
type: 'object',
|
|
163
265
|
properties: {},
|
|
266
|
+
additionalProperties: false
|
|
164
267
|
}
|
|
165
268
|
},
|
|
166
269
|
{
|
|
167
270
|
name: 'faf_read',
|
|
168
271
|
description: 'Read content from any file on the local filesystem',
|
|
272
|
+
annotations: {
|
|
273
|
+
title: 'Read .faf File',
|
|
274
|
+
readOnlyHint: true,
|
|
275
|
+
openWorldHint: false
|
|
276
|
+
},
|
|
169
277
|
inputSchema: {
|
|
170
278
|
type: 'object',
|
|
171
279
|
properties: {
|
|
@@ -175,11 +283,18 @@ class FafToolHandler {
|
|
|
175
283
|
}
|
|
176
284
|
},
|
|
177
285
|
required: ['path'],
|
|
286
|
+
additionalProperties: false
|
|
178
287
|
}
|
|
179
288
|
},
|
|
180
289
|
{
|
|
181
290
|
name: 'faf_write',
|
|
182
291
|
description: 'Write content to any file on the local filesystem',
|
|
292
|
+
annotations: {
|
|
293
|
+
title: 'Write .faf File',
|
|
294
|
+
readOnlyHint: false,
|
|
295
|
+
destructiveHint: false,
|
|
296
|
+
openWorldHint: false
|
|
297
|
+
},
|
|
183
298
|
inputSchema: {
|
|
184
299
|
type: 'object',
|
|
185
300
|
properties: {
|
|
@@ -193,11 +308,17 @@ class FafToolHandler {
|
|
|
193
308
|
}
|
|
194
309
|
},
|
|
195
310
|
required: ['path', 'content'],
|
|
311
|
+
additionalProperties: false
|
|
196
312
|
}
|
|
197
313
|
},
|
|
198
314
|
{
|
|
199
315
|
name: 'faf_list',
|
|
200
316
|
description: 'List directories and discover projects with project.faf files - Essential for FAF discovery workflow',
|
|
317
|
+
annotations: {
|
|
318
|
+
title: 'List .faf Files',
|
|
319
|
+
readOnlyHint: true,
|
|
320
|
+
openWorldHint: false
|
|
321
|
+
},
|
|
201
322
|
inputSchema: {
|
|
202
323
|
type: 'object',
|
|
203
324
|
properties: {
|
|
@@ -221,19 +342,31 @@ class FafToolHandler {
|
|
|
221
342
|
}
|
|
222
343
|
},
|
|
223
344
|
required: ['path'],
|
|
345
|
+
additionalProperties: false
|
|
224
346
|
}
|
|
225
347
|
},
|
|
226
348
|
{
|
|
227
349
|
name: 'faf_chat',
|
|
228
350
|
description: '🗣️ Natural language project.faf generation - Ask 6W questions (Who/What/Why/Where/When/How) to build complete human context 🧡⚡️',
|
|
351
|
+
annotations: {
|
|
352
|
+
title: 'Chat about FAF',
|
|
353
|
+
readOnlyHint: true,
|
|
354
|
+
openWorldHint: false
|
|
355
|
+
},
|
|
229
356
|
inputSchema: {
|
|
230
357
|
type: 'object',
|
|
231
358
|
properties: {},
|
|
359
|
+
additionalProperties: false
|
|
232
360
|
}
|
|
233
361
|
},
|
|
234
362
|
{
|
|
235
363
|
name: 'faf_friday',
|
|
236
364
|
description: '🎉 Friday Features - Chrome Extension detection, fuzzy matching & more! 🧡⚡️',
|
|
365
|
+
annotations: {
|
|
366
|
+
title: 'Fun FAF Facts',
|
|
367
|
+
readOnlyHint: true,
|
|
368
|
+
openWorldHint: false
|
|
369
|
+
},
|
|
237
370
|
inputSchema: {
|
|
238
371
|
type: 'object',
|
|
239
372
|
properties: {
|
|
@@ -242,16 +375,315 @@ class FafToolHandler {
|
|
|
242
375
|
description: 'Test fuzzy matching with typos like "raect" or "chr ext"'
|
|
243
376
|
}
|
|
244
377
|
},
|
|
378
|
+
additionalProperties: false
|
|
245
379
|
}
|
|
246
380
|
},
|
|
247
381
|
{
|
|
248
382
|
name: 'faf_guide',
|
|
249
383
|
description: 'FAF MCP usage guide for Claude Desktop - Projects convention, path resolution, and UX patterns',
|
|
384
|
+
annotations: {
|
|
385
|
+
title: 'Usage Guide',
|
|
386
|
+
readOnlyHint: true,
|
|
387
|
+
openWorldHint: false
|
|
388
|
+
},
|
|
250
389
|
inputSchema: {
|
|
251
390
|
type: 'object',
|
|
252
391
|
properties: {},
|
|
392
|
+
additionalProperties: false
|
|
253
393
|
}
|
|
254
|
-
}
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
name: 'faf_readme',
|
|
397
|
+
description: '📖 Extract 6 Ws (Who/What/Why/Where/When/How) from README.md into human_context - Smart pattern matching 🧡⚡️',
|
|
398
|
+
annotations: {
|
|
399
|
+
title: 'Extract from README',
|
|
400
|
+
readOnlyHint: false,
|
|
401
|
+
destructiveHint: false,
|
|
402
|
+
openWorldHint: false
|
|
403
|
+
},
|
|
404
|
+
inputSchema: {
|
|
405
|
+
type: 'object',
|
|
406
|
+
properties: {
|
|
407
|
+
apply: { type: 'boolean', description: 'Apply extracted content to project.faf (default: preview only)' },
|
|
408
|
+
force: { type: 'boolean', description: 'Overwrite existing human_context values (default: only fill empty slots)' },
|
|
409
|
+
path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
|
|
410
|
+
},
|
|
411
|
+
additionalProperties: false
|
|
412
|
+
}
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
name: 'faf_human_add',
|
|
416
|
+
description: '🧡 Add a human_context field (who/what/why/where/when/how) - Non-interactive for MCP 🧡⚡️',
|
|
417
|
+
annotations: {
|
|
418
|
+
title: 'Add Human Context',
|
|
419
|
+
readOnlyHint: false,
|
|
420
|
+
destructiveHint: false,
|
|
421
|
+
openWorldHint: false
|
|
422
|
+
},
|
|
423
|
+
inputSchema: {
|
|
424
|
+
type: 'object',
|
|
425
|
+
properties: {
|
|
426
|
+
field: {
|
|
427
|
+
type: 'string',
|
|
428
|
+
enum: ['who', 'what', 'why', 'where', 'when', 'how'],
|
|
429
|
+
description: 'The 6 W field to set'
|
|
430
|
+
},
|
|
431
|
+
value: { type: 'string', description: 'The value to set for the field' },
|
|
432
|
+
path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
|
|
433
|
+
},
|
|
434
|
+
required: ['field', 'value'],
|
|
435
|
+
additionalProperties: false
|
|
436
|
+
}
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
name: 'faf_check',
|
|
440
|
+
description: '🔍 Quality inspection for human_context fields + field protection - Shows empty/generic/good/excellent ratings 🧡⚡️',
|
|
441
|
+
annotations: {
|
|
442
|
+
title: 'Check .faf Health',
|
|
443
|
+
readOnlyHint: true,
|
|
444
|
+
openWorldHint: false
|
|
445
|
+
},
|
|
446
|
+
inputSchema: {
|
|
447
|
+
type: 'object',
|
|
448
|
+
properties: {
|
|
449
|
+
protect: { type: 'boolean', description: 'Lock good/excellent fields from being overwritten' },
|
|
450
|
+
unlock: { type: 'boolean', description: 'Remove all field protections' },
|
|
451
|
+
path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
|
|
452
|
+
},
|
|
453
|
+
additionalProperties: false
|
|
454
|
+
}
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
name: 'faf_context',
|
|
458
|
+
description: '📂 Set or view active project context - Path is remembered for subsequent faf_ calls 🧡⚡️',
|
|
459
|
+
annotations: {
|
|
460
|
+
title: 'View Context',
|
|
461
|
+
readOnlyHint: true,
|
|
462
|
+
openWorldHint: false
|
|
463
|
+
},
|
|
464
|
+
inputSchema: {
|
|
465
|
+
type: 'object',
|
|
466
|
+
properties: {
|
|
467
|
+
path: { type: 'string', description: 'Set active project path. If omitted, shows current context.' }
|
|
468
|
+
},
|
|
469
|
+
additionalProperties: false
|
|
470
|
+
}
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
name: 'faf_go',
|
|
474
|
+
description: '🎯 Guided interview to Gold Code - Claude asks questions till you hit 100%! Returns questions for missing fields, then apply answers to reach Gold Code 🧡⚡️',
|
|
475
|
+
annotations: {
|
|
476
|
+
title: 'Guided Setup',
|
|
477
|
+
readOnlyHint: false,
|
|
478
|
+
destructiveHint: false,
|
|
479
|
+
openWorldHint: false
|
|
480
|
+
},
|
|
481
|
+
inputSchema: {
|
|
482
|
+
type: 'object',
|
|
483
|
+
properties: {
|
|
484
|
+
path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' },
|
|
485
|
+
answers: {
|
|
486
|
+
type: 'object',
|
|
487
|
+
description: 'Answers to apply. Keys are field paths (e.g., "project.goal", "human_context.why"), values are the answers. If provided, applies answers and returns new score.',
|
|
488
|
+
additionalProperties: { type: 'string' }
|
|
489
|
+
}
|
|
490
|
+
},
|
|
491
|
+
additionalProperties: false
|
|
492
|
+
}
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
name: 'faf_auto',
|
|
496
|
+
description: '🏎️ ONE COMMAND TO RULE THEM ALL - Zero to Championship AI context instantly! Runs init + sync + formats + bi-sync + score in one go 🧡⚡️',
|
|
497
|
+
annotations: {
|
|
498
|
+
title: 'Auto-detect Context',
|
|
499
|
+
readOnlyHint: false,
|
|
500
|
+
destructiveHint: false,
|
|
501
|
+
openWorldHint: false
|
|
502
|
+
},
|
|
503
|
+
inputSchema: {
|
|
504
|
+
type: 'object',
|
|
505
|
+
properties: {
|
|
506
|
+
path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' },
|
|
507
|
+
force: { type: 'boolean', description: 'Force overwrite existing files' }
|
|
508
|
+
},
|
|
509
|
+
additionalProperties: false
|
|
510
|
+
}
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
name: 'faf_dna',
|
|
514
|
+
description: '🧬 Show your FAF DNA journey - See your evolution from birth to championship (22% → 85% → 99%) 🧡⚡️',
|
|
515
|
+
annotations: {
|
|
516
|
+
title: 'View Project DNA',
|
|
517
|
+
readOnlyHint: true,
|
|
518
|
+
openWorldHint: false
|
|
519
|
+
},
|
|
520
|
+
inputSchema: {
|
|
521
|
+
type: 'object',
|
|
522
|
+
properties: {
|
|
523
|
+
path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
|
|
524
|
+
},
|
|
525
|
+
additionalProperties: false
|
|
526
|
+
}
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
name: 'faf_formats',
|
|
530
|
+
description: '😽 TURBO-CAT format discovery - Discovers all formats in your project (154+ validated types!) and fills stack slots 🧡⚡️',
|
|
531
|
+
annotations: {
|
|
532
|
+
title: 'List Formats',
|
|
533
|
+
readOnlyHint: true,
|
|
534
|
+
openWorldHint: false
|
|
535
|
+
},
|
|
536
|
+
inputSchema: {
|
|
537
|
+
type: 'object',
|
|
538
|
+
properties: {
|
|
539
|
+
path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' },
|
|
540
|
+
json: { type: 'boolean', description: 'Return results as JSON' }
|
|
541
|
+
},
|
|
542
|
+
additionalProperties: false
|
|
543
|
+
}
|
|
544
|
+
},
|
|
545
|
+
{
|
|
546
|
+
name: 'faf_quick',
|
|
547
|
+
description: '⚡ Lightning-fast .faf creation - One-liner format: "name, description, language, framework, hosting" 🧡⚡️',
|
|
548
|
+
annotations: {
|
|
549
|
+
title: 'Quick Create',
|
|
550
|
+
readOnlyHint: false,
|
|
551
|
+
destructiveHint: false,
|
|
552
|
+
openWorldHint: false
|
|
553
|
+
},
|
|
554
|
+
inputSchema: {
|
|
555
|
+
type: 'object',
|
|
556
|
+
properties: {
|
|
557
|
+
path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' },
|
|
558
|
+
input: { type: 'string', description: 'Quick input: "project-name, description, language, framework, hosting" (minimum: name, description)' },
|
|
559
|
+
force: { type: 'boolean', description: 'Force overwrite existing .faf file' }
|
|
560
|
+
},
|
|
561
|
+
required: ['input'],
|
|
562
|
+
additionalProperties: false
|
|
563
|
+
}
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
name: 'faf_doctor',
|
|
567
|
+
description: '🏥 Health check for your .faf setup - Diagnose and fix common issues 🧡⚡️',
|
|
568
|
+
annotations: {
|
|
569
|
+
title: 'Diagnose Issues',
|
|
570
|
+
readOnlyHint: true,
|
|
571
|
+
openWorldHint: false
|
|
572
|
+
},
|
|
573
|
+
inputSchema: {
|
|
574
|
+
type: 'object',
|
|
575
|
+
properties: {
|
|
576
|
+
path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
|
|
577
|
+
},
|
|
578
|
+
additionalProperties: false
|
|
579
|
+
}
|
|
580
|
+
},
|
|
581
|
+
// ============================================================================
|
|
582
|
+
// v4.5.0 INTEROP TOOLS
|
|
583
|
+
// ============================================================================
|
|
584
|
+
{
|
|
585
|
+
name: 'faf_agents',
|
|
586
|
+
description: 'Import/Export/Sync between AGENTS.md (OpenAI/Codex) and project.faf - AI interop!',
|
|
587
|
+
annotations: {
|
|
588
|
+
title: 'Sync AGENTS.md',
|
|
589
|
+
readOnlyHint: false,
|
|
590
|
+
destructiveHint: false,
|
|
591
|
+
openWorldHint: false
|
|
592
|
+
},
|
|
593
|
+
inputSchema: {
|
|
594
|
+
type: 'object',
|
|
595
|
+
properties: {
|
|
596
|
+
action: { type: 'string', enum: ['import', 'export', 'sync'], description: 'Action: import (AGENTS.md -> .faf), export (.faf -> AGENTS.md), sync (bidirectional)' },
|
|
597
|
+
force: { type: 'boolean', description: 'Force overwrite existing files' },
|
|
598
|
+
merge: { type: 'boolean', description: 'Merge imported data with existing .faf instead of replacing' },
|
|
599
|
+
path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
|
|
600
|
+
},
|
|
601
|
+
required: ['action'],
|
|
602
|
+
additionalProperties: false
|
|
603
|
+
}
|
|
604
|
+
},
|
|
605
|
+
{
|
|
606
|
+
name: 'faf_cursor',
|
|
607
|
+
description: 'Import/Export/Sync between .cursorrules (Cursor IDE) and project.faf - AI interop!',
|
|
608
|
+
annotations: {
|
|
609
|
+
title: 'Sync .cursorrules',
|
|
610
|
+
readOnlyHint: false,
|
|
611
|
+
destructiveHint: false,
|
|
612
|
+
openWorldHint: false
|
|
613
|
+
},
|
|
614
|
+
inputSchema: {
|
|
615
|
+
type: 'object',
|
|
616
|
+
properties: {
|
|
617
|
+
action: { type: 'string', enum: ['import', 'export', 'sync'], description: 'Action: import (.cursorrules -> .faf), export (.faf -> .cursorrules), sync (bidirectional)' },
|
|
618
|
+
force: { type: 'boolean', description: 'Force overwrite existing files' },
|
|
619
|
+
merge: { type: 'boolean', description: 'Merge imported data with existing .faf instead of replacing' },
|
|
620
|
+
path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
|
|
621
|
+
},
|
|
622
|
+
required: ['action'],
|
|
623
|
+
additionalProperties: false
|
|
624
|
+
}
|
|
625
|
+
},
|
|
626
|
+
{
|
|
627
|
+
name: 'faf_gemini',
|
|
628
|
+
description: 'Import/Export/Sync between GEMINI.md (Google Gemini CLI) and project.faf - AI interop!',
|
|
629
|
+
annotations: {
|
|
630
|
+
title: 'Sync GEMINI.md',
|
|
631
|
+
readOnlyHint: false,
|
|
632
|
+
destructiveHint: false,
|
|
633
|
+
openWorldHint: false
|
|
634
|
+
},
|
|
635
|
+
inputSchema: {
|
|
636
|
+
type: 'object',
|
|
637
|
+
properties: {
|
|
638
|
+
action: { type: 'string', enum: ['import', 'export', 'sync'], description: 'Action: import (GEMINI.md -> .faf), export (.faf -> GEMINI.md), sync (bidirectional)' },
|
|
639
|
+
force: { type: 'boolean', description: 'Force overwrite existing files' },
|
|
640
|
+
merge: { type: 'boolean', description: 'Merge imported data with existing .faf instead of replacing' },
|
|
641
|
+
path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
|
|
642
|
+
},
|
|
643
|
+
required: ['action'],
|
|
644
|
+
additionalProperties: false
|
|
645
|
+
}
|
|
646
|
+
},
|
|
647
|
+
{
|
|
648
|
+
name: 'faf_conductor',
|
|
649
|
+
description: 'Import/Export between conductor/ directory (Google Conductor) and project.faf - AI interop!',
|
|
650
|
+
annotations: {
|
|
651
|
+
title: 'Sync Conductor',
|
|
652
|
+
readOnlyHint: false,
|
|
653
|
+
destructiveHint: false,
|
|
654
|
+
openWorldHint: false
|
|
655
|
+
},
|
|
656
|
+
inputSchema: {
|
|
657
|
+
type: 'object',
|
|
658
|
+
properties: {
|
|
659
|
+
action: { type: 'string', enum: ['import', 'export'], description: 'Action: import (conductor/ -> .faf), export (.faf -> conductor/)' },
|
|
660
|
+
force: { type: 'boolean', description: 'Force overwrite existing files' },
|
|
661
|
+
merge: { type: 'boolean', description: 'Merge imported data with existing .faf instead of replacing' },
|
|
662
|
+
path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
|
|
663
|
+
},
|
|
664
|
+
required: ['action'],
|
|
665
|
+
additionalProperties: false
|
|
666
|
+
}
|
|
667
|
+
},
|
|
668
|
+
{
|
|
669
|
+
name: 'faf_git',
|
|
670
|
+
description: 'Generate project.faf from any GitHub repo URL - 1-click context extraction!',
|
|
671
|
+
annotations: {
|
|
672
|
+
title: 'Extract from GitHub',
|
|
673
|
+
readOnlyHint: false,
|
|
674
|
+
destructiveHint: false,
|
|
675
|
+
openWorldHint: false
|
|
676
|
+
},
|
|
677
|
+
inputSchema: {
|
|
678
|
+
type: 'object',
|
|
679
|
+
properties: {
|
|
680
|
+
url: { type: 'string', description: 'GitHub repository URL (e.g., https://github.com/owner/repo or owner/repo)' },
|
|
681
|
+
path: { type: 'string', description: 'Output directory for generated project.faf. If omitted, returns content without writing.' }
|
|
682
|
+
},
|
|
683
|
+
required: ['url'],
|
|
684
|
+
additionalProperties: false
|
|
685
|
+
}
|
|
686
|
+
},
|
|
255
687
|
]
|
|
256
688
|
};
|
|
257
689
|
}
|
|
@@ -283,8 +715,15 @@ class FafToolHandler {
|
|
|
283
715
|
return await this.handleFafAbout(args);
|
|
284
716
|
case 'faf_what':
|
|
285
717
|
return await this.handleFafWhat(args);
|
|
286
|
-
case 'faf_read':
|
|
287
|
-
|
|
718
|
+
case 'faf_read': {
|
|
719
|
+
// Handle faf_read specially to set context when reading project.faf files
|
|
720
|
+
const readResult = await fileHandler_1.fileHandlers.faf_read(args);
|
|
721
|
+
// If reading a project.faf file, set the session context
|
|
722
|
+
if (args?.path && (args.path.includes('project.faf') || args.path.endsWith('.faf'))) {
|
|
723
|
+
this.getProjectPath(args.path);
|
|
724
|
+
}
|
|
725
|
+
return readResult;
|
|
726
|
+
}
|
|
288
727
|
case 'faf_chat':
|
|
289
728
|
return await this.handleFafChat(args);
|
|
290
729
|
case 'faf_friday':
|
|
@@ -295,13 +734,44 @@ class FafToolHandler {
|
|
|
295
734
|
return await this.handleFafList(args);
|
|
296
735
|
case 'faf_guide':
|
|
297
736
|
return await this.handleFafGuide(args);
|
|
737
|
+
case 'faf_readme':
|
|
738
|
+
return await this.handleFafReadme(args);
|
|
739
|
+
case 'faf_human_add':
|
|
740
|
+
return await this.handleFafHumanAdd(args);
|
|
741
|
+
case 'faf_check':
|
|
742
|
+
return await this.handleFafCheck(args);
|
|
743
|
+
case 'faf_context':
|
|
744
|
+
return await this.handleFafContext(args);
|
|
745
|
+
case 'faf_go':
|
|
746
|
+
return await this.handleFafGo(args);
|
|
747
|
+
case 'faf_auto':
|
|
748
|
+
return await this.handleFafAuto(args);
|
|
749
|
+
case 'faf_dna':
|
|
750
|
+
return await this.handleFafDna(args);
|
|
751
|
+
case 'faf_formats':
|
|
752
|
+
return await this.handleFafFormats(args);
|
|
753
|
+
case 'faf_quick':
|
|
754
|
+
return await this.handleFafQuick(args);
|
|
755
|
+
case 'faf_doctor':
|
|
756
|
+
return await this.handleFafDoctor(args);
|
|
757
|
+
// v4.5.0 Interop tools
|
|
758
|
+
case 'faf_agents':
|
|
759
|
+
return await this.handleFafAgents(args);
|
|
760
|
+
case 'faf_cursor':
|
|
761
|
+
return await this.handleFafCursor(args);
|
|
762
|
+
case 'faf_gemini':
|
|
763
|
+
return await this.handleFafGemini(args);
|
|
764
|
+
case 'faf_conductor':
|
|
765
|
+
return await this.handleFafConductor(args);
|
|
766
|
+
case 'faf_git':
|
|
767
|
+
return await this.handleFafGit(args);
|
|
298
768
|
default:
|
|
299
769
|
throw new Error(`Unknown tool: ${name}`);
|
|
300
770
|
}
|
|
301
771
|
}
|
|
302
|
-
async handleFafStatus(
|
|
772
|
+
async handleFafStatus(args) {
|
|
303
773
|
// Native implementation - no CLI needed!
|
|
304
|
-
const cwd = this.
|
|
774
|
+
const cwd = this.getProjectPath(args?.path);
|
|
305
775
|
try {
|
|
306
776
|
const fafResult = await (0, faf_file_finder_js_1.findFafFile)(cwd);
|
|
307
777
|
if (!fafResult) {
|
|
@@ -335,8 +805,8 @@ class FafToolHandler {
|
|
|
335
805
|
try {
|
|
336
806
|
const fs = await import('fs').then(m => m.promises);
|
|
337
807
|
const path = await import('path');
|
|
338
|
-
// Get current working directory
|
|
339
|
-
const cwd = this.
|
|
808
|
+
// Get current working directory - uses path param or session context
|
|
809
|
+
const cwd = this.getProjectPath(args?.path);
|
|
340
810
|
// Score calculation components
|
|
341
811
|
let score = 0;
|
|
342
812
|
const details = [];
|
|
@@ -405,44 +875,40 @@ class FafToolHandler {
|
|
|
405
875
|
output += `\n💡 Note: 🍊 Big Orange is a BADGE awarded separately for excellence beyond metrics.`;
|
|
406
876
|
}
|
|
407
877
|
}
|
|
408
|
-
else if (score >= 99) {
|
|
409
|
-
// Maximum technical score
|
|
410
|
-
output = `📊 FAF SCORE: 99%\n⚡ Maximum Technical\n🏁 Claude grants 100%\n\n`;
|
|
411
|
-
if (args?.details) {
|
|
412
|
-
output += details.join('\n');
|
|
413
|
-
output += `\n\n💡 Only Claude can grant the final 1% for perfect collaboration!`;
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
878
|
else {
|
|
417
|
-
// Regular score
|
|
418
|
-
const percentage = Math.min(score,
|
|
879
|
+
// Regular score - FAF standard tiers
|
|
880
|
+
const percentage = Math.min(score, 100);
|
|
419
881
|
let rating = '';
|
|
420
882
|
let emoji = '';
|
|
421
|
-
if (percentage >=
|
|
422
|
-
rating = '
|
|
423
|
-
emoji = '
|
|
883
|
+
if (percentage >= 99) {
|
|
884
|
+
rating = 'Gold';
|
|
885
|
+
emoji = '🥇';
|
|
886
|
+
}
|
|
887
|
+
else if (percentage >= 95) {
|
|
888
|
+
rating = 'Silver';
|
|
889
|
+
emoji = '🥈';
|
|
424
890
|
}
|
|
425
|
-
else if (percentage >=
|
|
426
|
-
rating = '
|
|
427
|
-
emoji = '
|
|
891
|
+
else if (percentage >= 85) {
|
|
892
|
+
rating = 'Bronze';
|
|
893
|
+
emoji = '🥉';
|
|
428
894
|
}
|
|
429
895
|
else if (percentage >= 70) {
|
|
430
|
-
rating = '
|
|
431
|
-
emoji = '
|
|
896
|
+
rating = 'Green';
|
|
897
|
+
emoji = '🟢';
|
|
432
898
|
}
|
|
433
|
-
else if (percentage >=
|
|
434
|
-
rating = '
|
|
435
|
-
emoji = '
|
|
899
|
+
else if (percentage >= 55) {
|
|
900
|
+
rating = 'Yellow';
|
|
901
|
+
emoji = '🟡';
|
|
436
902
|
}
|
|
437
903
|
else {
|
|
438
|
-
rating = '
|
|
439
|
-
emoji = '
|
|
904
|
+
rating = 'Red';
|
|
905
|
+
emoji = '🔴';
|
|
440
906
|
}
|
|
441
907
|
// The 3-line killer display
|
|
442
|
-
output = `📊 FAF SCORE: ${percentage}%\n${emoji} ${rating}\n🏁 AI-Ready: ${percentage >=
|
|
908
|
+
output = `📊 FAF SCORE: ${percentage}%\n${emoji} ${rating}\n🏁 AI-Ready: ${percentage >= 85 ? 'Yes' : 'Building'}\n`;
|
|
443
909
|
if (args?.details) {
|
|
444
910
|
output += `\n${details.join('\n')}`;
|
|
445
|
-
if (percentage <
|
|
911
|
+
if (percentage < 100) {
|
|
446
912
|
output += `\n\n💡 Tips to improve:\n`;
|
|
447
913
|
if (!hasFaf)
|
|
448
914
|
output += `- Create .faf file with project context\n`;
|
|
@@ -473,43 +939,18 @@ class FafToolHandler {
|
|
|
473
939
|
}
|
|
474
940
|
}
|
|
475
941
|
async handleFafInit(args) {
|
|
476
|
-
// Native implementation - creates project.faf with
|
|
942
|
+
// Native implementation - creates project.faf with Pomelli-simple path resolution!
|
|
477
943
|
try {
|
|
478
|
-
//
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
const
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
? path.join(require('os').homedir(), explicitPath.slice(1))
|
|
488
|
-
: explicitPath;
|
|
489
|
-
targetDir = path.resolve(expandedPath);
|
|
490
|
-
projectName = path.basename(targetDir);
|
|
491
|
-
// Ensure directory exists
|
|
492
|
-
if (!fs.existsSync(targetDir)) {
|
|
493
|
-
fs.mkdirSync(targetDir, { recursive: true });
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
else if (args?.projectName) {
|
|
497
|
-
// Use Projects convention with provided name
|
|
498
|
-
(0, path_resolver_1.ensureProjectsDirectory)();
|
|
499
|
-
const resolution = (0, path_resolver_1.resolveProjectPath)(args.projectName);
|
|
500
|
-
targetDir = resolution.projectPath;
|
|
501
|
-
projectName = resolution.projectName;
|
|
502
|
-
// Ensure project directory exists
|
|
503
|
-
if (!fs.existsSync(targetDir)) {
|
|
504
|
-
fs.mkdirSync(targetDir, { recursive: true });
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
else {
|
|
508
|
-
// Use current working directory (legacy behavior)
|
|
509
|
-
targetDir = this.engineAdapter.getWorkingDirectory();
|
|
510
|
-
projectName = path.basename(targetDir);
|
|
944
|
+
// Use smart path resolution (supports "my-app", "~/Projects/my-app", "/full/path")
|
|
945
|
+
const userInput = args?.path;
|
|
946
|
+
const resolution = (0, path_resolver_1.resolveProjectPath)(userInput);
|
|
947
|
+
const targetDir = resolution.projectPath;
|
|
948
|
+
const projectName = resolution.projectName;
|
|
949
|
+
const fafPath = resolution.fafFilePath;
|
|
950
|
+
// Ensure project directory exists
|
|
951
|
+
if (!fs.existsSync(targetDir)) {
|
|
952
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
511
953
|
}
|
|
512
|
-
const fafPath = path.join(targetDir, 'project.faf');
|
|
513
954
|
// Check if any FAF file exists and force flag
|
|
514
955
|
const existingFaf = await (0, faf_file_finder_js_1.findFafFile)(targetDir);
|
|
515
956
|
if (existingFaf && !args?.force) {
|
|
@@ -538,8 +979,7 @@ class FafToolHandler {
|
|
|
538
979
|
projectData = (0, fuzzy_detector_1.applyIntelFriday)(projectData);
|
|
539
980
|
}
|
|
540
981
|
// Create enhanced .faf content
|
|
541
|
-
const fafContent = `#
|
|
542
|
-
# FAF - Foundational AI Context
|
|
982
|
+
const fafContent = `# FAF - Foundational AI Context
|
|
543
983
|
project: ${projectData.project}
|
|
544
984
|
type: ${projectData.project_type}${chromeDetection.detected ? ' 🎯' : ''}
|
|
545
985
|
context: I⚡🍊
|
|
@@ -568,10 +1008,15 @@ build: ${projectData.build}
|
|
|
568
1008
|
package_manager: ${projectData.package_manager}` : ''}
|
|
569
1009
|
`;
|
|
570
1010
|
fs.writeFileSync(fafPath, fafContent);
|
|
1011
|
+
// Pomelli-style success confirmation with path resolution info
|
|
1012
|
+
const pathConfirmation = (0, path_resolver_1.formatPathConfirmation)(resolution);
|
|
1013
|
+
const sourceExplanation = resolution.source === 'user-name'
|
|
1014
|
+
? `\n\n💡 Smart resolution: "${userInput}" → ${targetDir}`
|
|
1015
|
+
: '';
|
|
571
1016
|
return {
|
|
572
1017
|
content: [{
|
|
573
1018
|
type: 'text',
|
|
574
|
-
text: `🚀 Claude FAF Initialization:\n\n✅ Created project.faf
|
|
1019
|
+
text: `🚀 Claude FAF Initialization:\n\n✅ Created project.faf\n\n${pathConfirmation}${sourceExplanation}\n\n🍊 Vitamin Context activated!\n⚡ FAFFLESS AI ready!${chromeDetection.detected ? '\n\n🎯 Friday Feature: Chrome Extension detected!\n📈 Auto-filled 7 slots for 90%+ score!' : ''}${chromeDetection.corrected ? `\n📝 Auto-corrected: "${args?.description}" → "${chromeDetection.corrected}"` : ''}\n\n🏁 Next steps:\n • Run faf_score for AI-readiness score\n • Run faf_sync to create CLAUDE.md\n • Run faf_enhance to improve context`
|
|
575
1020
|
}]
|
|
576
1021
|
};
|
|
577
1022
|
}
|
|
@@ -606,7 +1051,11 @@ package_manager: ${projectData.package_manager}` : ''}
|
|
|
606
1051
|
}]
|
|
607
1052
|
};
|
|
608
1053
|
}
|
|
609
|
-
async handleFafSync(
|
|
1054
|
+
async handleFafSync(args) {
|
|
1055
|
+
// Set project context if path provided
|
|
1056
|
+
if (args?.path) {
|
|
1057
|
+
this.getProjectPath(args.path);
|
|
1058
|
+
}
|
|
610
1059
|
const result = await this.engineAdapter.callEngine('sync');
|
|
611
1060
|
if (!result.success) {
|
|
612
1061
|
return {
|
|
@@ -628,6 +1077,10 @@ package_manager: ${projectData.package_manager}` : ''}
|
|
|
628
1077
|
};
|
|
629
1078
|
}
|
|
630
1079
|
async handleFafEnhance(args) {
|
|
1080
|
+
// Set project context if path provided
|
|
1081
|
+
if (args?.path) {
|
|
1082
|
+
this.getProjectPath(args.path);
|
|
1083
|
+
}
|
|
631
1084
|
const enhanceArgs = [];
|
|
632
1085
|
// Default to Claude optimization if no model specified
|
|
633
1086
|
const model = args?.model || 'claude';
|
|
@@ -653,7 +1106,7 @@ package_manager: ${projectData.package_manager}` : ''}
|
|
|
653
1106
|
}
|
|
654
1107
|
const output = typeof result.data === 'string'
|
|
655
1108
|
? result.data
|
|
656
|
-
: result.data?.output || JSON.stringify(result.data, null, 2);
|
|
1109
|
+
: result.data?.message || result.data?.output || JSON.stringify(result.data, null, 2);
|
|
657
1110
|
return {
|
|
658
1111
|
content: [{
|
|
659
1112
|
type: 'text',
|
|
@@ -662,6 +1115,10 @@ package_manager: ${projectData.package_manager}` : ''}
|
|
|
662
1115
|
};
|
|
663
1116
|
}
|
|
664
1117
|
async handleFafBiSync(args) {
|
|
1118
|
+
// Set project context if path provided
|
|
1119
|
+
if (args?.path) {
|
|
1120
|
+
this.getProjectPath(args.path);
|
|
1121
|
+
}
|
|
665
1122
|
const biSyncArgs = [];
|
|
666
1123
|
if (args?.auto) {
|
|
667
1124
|
biSyncArgs.push('--auto');
|
|
@@ -672,9 +1129,6 @@ package_manager: ${projectData.package_manager}` : ''}
|
|
|
672
1129
|
if (args?.force) {
|
|
673
1130
|
biSyncArgs.push('--force');
|
|
674
1131
|
}
|
|
675
|
-
if (args?.target) {
|
|
676
|
-
biSyncArgs.push(`--target=${args.target}`);
|
|
677
|
-
}
|
|
678
1132
|
const result = await this.engineAdapter.callEngine('bi-sync', biSyncArgs);
|
|
679
1133
|
if (!result.success) {
|
|
680
1134
|
return {
|
|
@@ -740,7 +1194,7 @@ package_manager: ${projectData.package_manager}` : ''}
|
|
|
740
1194
|
npm: 'https://www.npmjs.com/package/claude-faf-mcp'
|
|
741
1195
|
};
|
|
742
1196
|
const aboutText = `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
743
|
-
🤖 .faf =
|
|
1197
|
+
🤖 .faf = project DNA for AI
|
|
744
1198
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
745
1199
|
|
|
746
1200
|
WHAT IS .FAF?
|
|
@@ -773,7 +1227,7 @@ HOW IT WORKS:
|
|
|
773
1227
|
};
|
|
774
1228
|
}
|
|
775
1229
|
async handleFafWhat(_args) {
|
|
776
|
-
const whatText = `.faf =
|
|
1230
|
+
const whatText = `.faf = project DNA for AI
|
|
777
1231
|
|
|
778
1232
|
WHAT: .faf = Foundational AI-context Format
|
|
779
1233
|
(The dot means it's a file format, like .jpg or .pdf)
|
|
@@ -800,7 +1254,7 @@ REMEMBER: Always use ".faf" with the dot - it's a FORMAT!
|
|
|
800
1254
|
const path = await import('path');
|
|
801
1255
|
const { exec } = await import('child_process');
|
|
802
1256
|
const { promisify } = await import('util');
|
|
803
|
-
const
|
|
1257
|
+
const _execAsync = promisify(exec);
|
|
804
1258
|
const cwd = this.engineAdapter.getWorkingDirectory();
|
|
805
1259
|
const debugInfo = {
|
|
806
1260
|
workingDirectory: cwd,
|
|
@@ -1032,7 +1486,7 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
|
|
|
1032
1486
|
const showHidden = args?.showHidden || false;
|
|
1033
1487
|
// Expand tilde
|
|
1034
1488
|
const expandedPath = targetPath.startsWith('~')
|
|
1035
|
-
? path.join(
|
|
1489
|
+
? path.join(os.homedir(), targetPath.slice(1))
|
|
1036
1490
|
: targetPath;
|
|
1037
1491
|
const resolvedPath = path.resolve(expandedPath);
|
|
1038
1492
|
// Check if directory exists
|
|
@@ -1137,6 +1591,1553 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
|
|
|
1137
1591
|
};
|
|
1138
1592
|
}
|
|
1139
1593
|
}
|
|
1594
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
1595
|
+
// NEW: Human Context Tools (v3.2.0 parity)
|
|
1596
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
1597
|
+
async handleFafReadme(args) {
|
|
1598
|
+
try {
|
|
1599
|
+
const path = await import('path');
|
|
1600
|
+
const cwd = this.getProjectPath(args?.path);
|
|
1601
|
+
// Find README.md
|
|
1602
|
+
const readmePath = path.join(cwd, 'README.md');
|
|
1603
|
+
if (!fs.existsSync(readmePath)) {
|
|
1604
|
+
return {
|
|
1605
|
+
content: [{
|
|
1606
|
+
type: 'text',
|
|
1607
|
+
text: `📖 FAF README Extraction:\n\n❌ No README.md found in ${cwd}\n💡 Create a README.md first`
|
|
1608
|
+
}],
|
|
1609
|
+
isError: true
|
|
1610
|
+
};
|
|
1611
|
+
}
|
|
1612
|
+
// Find project.faf
|
|
1613
|
+
const fafResult = await (0, faf_file_finder_js_1.findFafFile)(cwd);
|
|
1614
|
+
if (!fafResult) {
|
|
1615
|
+
return {
|
|
1616
|
+
content: [{
|
|
1617
|
+
type: 'text',
|
|
1618
|
+
text: `📖 FAF README Extraction:\n\n❌ No project.faf found in ${cwd}\n💡 Run faf_init first`
|
|
1619
|
+
}],
|
|
1620
|
+
isError: true
|
|
1621
|
+
};
|
|
1622
|
+
}
|
|
1623
|
+
// Read README content
|
|
1624
|
+
const readmeContent = fs.readFileSync(readmePath, 'utf-8');
|
|
1625
|
+
// Extract 6 Ws using simple pattern matching
|
|
1626
|
+
const extracted = this.extractSixWsFromReadme(readmeContent);
|
|
1627
|
+
if (!args?.apply) {
|
|
1628
|
+
// Preview mode
|
|
1629
|
+
let output = `📖 FAF README Extraction (Preview)\n\n`;
|
|
1630
|
+
output += `Found in README.md:\n`;
|
|
1631
|
+
for (const [field, value] of Object.entries(extracted)) {
|
|
1632
|
+
if (value) {
|
|
1633
|
+
output += ` ${field.toUpperCase()}: ${value}\n`;
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
output += `\n💡 Use apply: true to save to project.faf`;
|
|
1637
|
+
return { content: [{ type: 'text', text: output }] };
|
|
1638
|
+
}
|
|
1639
|
+
// Apply mode - update project.faf
|
|
1640
|
+
const fafContent = fs.readFileSync(fafResult.path, 'utf-8');
|
|
1641
|
+
const yaml = await import('yaml');
|
|
1642
|
+
const fafData = yaml.parse(fafContent) || {};
|
|
1643
|
+
if (!fafData.human_context) {
|
|
1644
|
+
fafData.human_context = {};
|
|
1645
|
+
}
|
|
1646
|
+
let appliedCount = 0;
|
|
1647
|
+
for (const [field, value] of Object.entries(extracted)) {
|
|
1648
|
+
if (value) {
|
|
1649
|
+
const existingValue = fafData.human_context[field];
|
|
1650
|
+
if (!existingValue || args?.force) {
|
|
1651
|
+
fafData.human_context[field] = value;
|
|
1652
|
+
appliedCount++;
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
fs.writeFileSync(fafResult.path, yaml.stringify(fafData), 'utf-8');
|
|
1657
|
+
return {
|
|
1658
|
+
content: [{
|
|
1659
|
+
type: 'text',
|
|
1660
|
+
text: `📖 FAF README Extraction:\n\n✅ Applied ${appliedCount} field(s) to human_context\n📁 Updated: ${fafResult.filename}`
|
|
1661
|
+
}]
|
|
1662
|
+
};
|
|
1663
|
+
}
|
|
1664
|
+
catch (error) {
|
|
1665
|
+
return {
|
|
1666
|
+
content: [{ type: 'text', text: `📖 FAF README Extraction:\n\n❌ Error: ${error.message}` }],
|
|
1667
|
+
isError: true
|
|
1668
|
+
};
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
extractSixWsFromReadme(content) {
|
|
1672
|
+
const result = {
|
|
1673
|
+
who: null, what: null, why: null, where: null, when: null, how: null
|
|
1674
|
+
};
|
|
1675
|
+
// WHAT: First paragraph after title
|
|
1676
|
+
const whatMatch = content.match(/^#\s+[^\n]+\n+(?:\*\*[^*]+\*\*\n+)?([A-Z][^#\n]{30,})/m);
|
|
1677
|
+
if (whatMatch)
|
|
1678
|
+
result.what = whatMatch[1].trim().substring(0, 200);
|
|
1679
|
+
// WHO: Look for "team", "company", "by", "for"
|
|
1680
|
+
const whoMatch = content.match(/(?:built by|created by|maintained by|for|team)\s+([^\n.]{10,50})/i);
|
|
1681
|
+
if (whoMatch)
|
|
1682
|
+
result.who = whoMatch[1].trim();
|
|
1683
|
+
// WHY: Look for "because", "to", benefits
|
|
1684
|
+
const whyMatch = content.match(/(?:because|to help|enables|allows|makes it)\s+([^\n.]{15,100})/i);
|
|
1685
|
+
if (whyMatch)
|
|
1686
|
+
result.why = whyMatch[1].trim();
|
|
1687
|
+
// WHERE: Look for deployment/runtime mentions
|
|
1688
|
+
const whereMatch = content.match(/(?:runs on|deployed to|works with|for)\s+(browser|server|edge|npm|cargo|cloud|local)/i);
|
|
1689
|
+
if (whereMatch)
|
|
1690
|
+
result.where = whereMatch[0].trim();
|
|
1691
|
+
// WHEN: Look for version, date
|
|
1692
|
+
const whenMatch = content.match(/(?:version|v)\s*(\d+\.\d+(?:\.\d+)?)/i);
|
|
1693
|
+
if (whenMatch)
|
|
1694
|
+
result.when = `v${whenMatch[1]}`;
|
|
1695
|
+
// HOW: Look for install/run commands
|
|
1696
|
+
const howMatch = content.match(/(?:npm install|cargo|pip install|brew install)\s+[^\n]+/i);
|
|
1697
|
+
if (howMatch)
|
|
1698
|
+
result.how = howMatch[0].trim();
|
|
1699
|
+
return result;
|
|
1700
|
+
}
|
|
1701
|
+
async handleFafHumanAdd(args) {
|
|
1702
|
+
try {
|
|
1703
|
+
const { field, value } = args;
|
|
1704
|
+
if (!field || !value) {
|
|
1705
|
+
return {
|
|
1706
|
+
content: [{
|
|
1707
|
+
type: 'text',
|
|
1708
|
+
text: `🧡 FAF Human Set:\n\n❌ Both field and value are required\n💡 Example: field="who", value="Development team"`
|
|
1709
|
+
}],
|
|
1710
|
+
isError: true
|
|
1711
|
+
};
|
|
1712
|
+
}
|
|
1713
|
+
const validFields = ['who', 'what', 'why', 'where', 'when', 'how'];
|
|
1714
|
+
if (!validFields.includes(field)) {
|
|
1715
|
+
return {
|
|
1716
|
+
content: [{
|
|
1717
|
+
type: 'text',
|
|
1718
|
+
text: `🧡 FAF Human Set:\n\n❌ Invalid field: ${field}\n💡 Valid fields: ${validFields.join(', ')}`
|
|
1719
|
+
}],
|
|
1720
|
+
isError: true
|
|
1721
|
+
};
|
|
1722
|
+
}
|
|
1723
|
+
const cwd = this.getProjectPath(args?.path);
|
|
1724
|
+
const fafResult = await (0, faf_file_finder_js_1.findFafFile)(cwd);
|
|
1725
|
+
if (!fafResult) {
|
|
1726
|
+
return {
|
|
1727
|
+
content: [{
|
|
1728
|
+
type: 'text',
|
|
1729
|
+
text: `🧡 FAF Human Add:\n\n❌ No project.faf found in ${cwd}\n💡 Run faf_init first`
|
|
1730
|
+
}],
|
|
1731
|
+
isError: true
|
|
1732
|
+
};
|
|
1733
|
+
}
|
|
1734
|
+
const fafContent = fs.readFileSync(fafResult.path, 'utf-8');
|
|
1735
|
+
const yaml = await import('yaml');
|
|
1736
|
+
const fafData = yaml.parse(fafContent) || {};
|
|
1737
|
+
if (!fafData.human_context) {
|
|
1738
|
+
fafData.human_context = {};
|
|
1739
|
+
}
|
|
1740
|
+
fafData.human_context[field] = value;
|
|
1741
|
+
fs.writeFileSync(fafResult.path, yaml.stringify(fafData), 'utf-8');
|
|
1742
|
+
return {
|
|
1743
|
+
content: [{
|
|
1744
|
+
type: 'text',
|
|
1745
|
+
text: `🧡 FAF Human Set:\n\n✅ Set ${field.toUpperCase()} = "${value}"\n📁 Updated: ${fafResult.filename}`
|
|
1746
|
+
}]
|
|
1747
|
+
};
|
|
1748
|
+
}
|
|
1749
|
+
catch (error) {
|
|
1750
|
+
return {
|
|
1751
|
+
content: [{ type: 'text', text: `🧡 FAF Human Set:\n\n❌ Error: ${error.message}` }],
|
|
1752
|
+
isError: true
|
|
1753
|
+
};
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
async handleFafCheck(args) {
|
|
1757
|
+
try {
|
|
1758
|
+
const cwd = this.getProjectPath(args?.path);
|
|
1759
|
+
const fafResult = await (0, faf_file_finder_js_1.findFafFile)(cwd);
|
|
1760
|
+
if (!fafResult) {
|
|
1761
|
+
return {
|
|
1762
|
+
content: [{
|
|
1763
|
+
type: 'text',
|
|
1764
|
+
text: `🔍 FAF Check:\n\n❌ No project.faf found in ${cwd}\n💡 Run faf_init first`
|
|
1765
|
+
}],
|
|
1766
|
+
isError: true
|
|
1767
|
+
};
|
|
1768
|
+
}
|
|
1769
|
+
const fafContent = fs.readFileSync(fafResult.path, 'utf-8');
|
|
1770
|
+
const yaml = await import('yaml');
|
|
1771
|
+
const fafData = yaml.parse(fafContent) || {};
|
|
1772
|
+
const humanContext = fafData.human_context || {};
|
|
1773
|
+
const protectedFields = fafData._protected_fields || [];
|
|
1774
|
+
const fields = ['who', 'what', 'why', 'where', 'when', 'how'];
|
|
1775
|
+
// Handle --unlock
|
|
1776
|
+
if (args?.unlock) {
|
|
1777
|
+
fafData._protected_fields = [];
|
|
1778
|
+
fs.writeFileSync(fafResult.path, yaml.stringify(fafData), 'utf-8');
|
|
1779
|
+
return {
|
|
1780
|
+
content: [{
|
|
1781
|
+
type: 'text',
|
|
1782
|
+
text: `🔓 FAF Check:\n\n✅ All fields unlocked\n📁 Updated: ${fafResult.filename}`
|
|
1783
|
+
}]
|
|
1784
|
+
};
|
|
1785
|
+
}
|
|
1786
|
+
// Assess quality
|
|
1787
|
+
const assessField = (value) => {
|
|
1788
|
+
if (!value || value.trim() === '')
|
|
1789
|
+
return 'empty';
|
|
1790
|
+
if (value.length < 10)
|
|
1791
|
+
return 'generic';
|
|
1792
|
+
if (value.length > 20)
|
|
1793
|
+
return 'good';
|
|
1794
|
+
return 'generic';
|
|
1795
|
+
};
|
|
1796
|
+
const qualities = {};
|
|
1797
|
+
for (const field of fields) {
|
|
1798
|
+
qualities[field] = assessField(humanContext[field]);
|
|
1799
|
+
}
|
|
1800
|
+
// Handle --protect
|
|
1801
|
+
if (args?.protect) {
|
|
1802
|
+
const toProtect = fields.filter(f => qualities[f] === 'good' || qualities[f] === 'excellent');
|
|
1803
|
+
if (toProtect.length === 0) {
|
|
1804
|
+
return {
|
|
1805
|
+
content: [{
|
|
1806
|
+
type: 'text',
|
|
1807
|
+
text: `🔒 FAF Check:\n\n⚠️ No fields qualify for protection (need good or excellent quality)`
|
|
1808
|
+
}]
|
|
1809
|
+
};
|
|
1810
|
+
}
|
|
1811
|
+
fafData._protected_fields = [...new Set([...protectedFields, ...toProtect])];
|
|
1812
|
+
fs.writeFileSync(fafResult.path, yaml.stringify(fafData), 'utf-8');
|
|
1813
|
+
return {
|
|
1814
|
+
content: [{
|
|
1815
|
+
type: 'text',
|
|
1816
|
+
text: `🔒 FAF Check:\n\n✅ Protected ${toProtect.length} field(s): ${toProtect.join(', ')}\n📁 Updated: ${fafResult.filename}`
|
|
1817
|
+
}]
|
|
1818
|
+
};
|
|
1819
|
+
}
|
|
1820
|
+
// Default: show quality report
|
|
1821
|
+
const icons = {
|
|
1822
|
+
empty: '⬜', generic: '🟡', good: '🟢', excellent: '💎'
|
|
1823
|
+
};
|
|
1824
|
+
let output = `🔍 FAF Human Context Quality\n\n`;
|
|
1825
|
+
for (const field of fields) {
|
|
1826
|
+
const q = qualities[field];
|
|
1827
|
+
const locked = protectedFields.includes(field) ? '🔒' : ' ';
|
|
1828
|
+
const value = humanContext[field] || '(empty)';
|
|
1829
|
+
const displayValue = value.length > 40 ? value.substring(0, 37) + '...' : value;
|
|
1830
|
+
output += `${icons[q]} ${locked} ${field.toUpperCase().padEnd(6)} ${displayValue}\n`;
|
|
1831
|
+
}
|
|
1832
|
+
const goodCount = fields.filter(f => qualities[f] === 'good' || qualities[f] === 'excellent').length;
|
|
1833
|
+
const emptyCount = fields.filter(f => qualities[f] === 'empty').length;
|
|
1834
|
+
output += `\n📊 Quality: ${Math.round((goodCount / fields.length) * 100)}%\n`;
|
|
1835
|
+
if (protectedFields.length > 0) {
|
|
1836
|
+
output += `🔒 Protected: ${protectedFields.join(', ')}\n`;
|
|
1837
|
+
}
|
|
1838
|
+
if (emptyCount > 0) {
|
|
1839
|
+
output += `\n💡 Use faf_readme or faf_human_add to fill empty slots`;
|
|
1840
|
+
}
|
|
1841
|
+
return { content: [{ type: 'text', text: output }] };
|
|
1842
|
+
}
|
|
1843
|
+
catch (error) {
|
|
1844
|
+
return {
|
|
1845
|
+
content: [{ type: 'text', text: `🔍 FAF Check:\n\n❌ Error: ${error.message}` }],
|
|
1846
|
+
isError: true
|
|
1847
|
+
};
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
async handleFafContext(args) {
|
|
1851
|
+
try {
|
|
1852
|
+
if (args?.path) {
|
|
1853
|
+
// Set the new context
|
|
1854
|
+
const newPath = this.getProjectPath(args.path);
|
|
1855
|
+
const fafResult = await (0, faf_file_finder_js_1.findFafFile)(newPath);
|
|
1856
|
+
return {
|
|
1857
|
+
content: [{
|
|
1858
|
+
type: 'text',
|
|
1859
|
+
text: `📂 FAF Context Set:\n\n✅ Active project: ${newPath}\n${fafResult ? `✅ project.faf found: ${fafResult.filename}` : '⚠️ No project.faf in this directory'}\n\n💡 Subsequent faf_* calls will use this context`
|
|
1860
|
+
}]
|
|
1861
|
+
};
|
|
1862
|
+
}
|
|
1863
|
+
else {
|
|
1864
|
+
// Show current context
|
|
1865
|
+
const currentPath = this.engineAdapter.getWorkingDirectory();
|
|
1866
|
+
const fafResult = await (0, faf_file_finder_js_1.findFafFile)(currentPath);
|
|
1867
|
+
return {
|
|
1868
|
+
content: [{
|
|
1869
|
+
type: 'text',
|
|
1870
|
+
text: `📂 FAF Current Context:\n\n📁 Active project: ${currentPath}\n${fafResult ? `✅ project.faf: ${fafResult.filename}` : '⚠️ No project.faf found'}\n\n💡 Use path parameter to change context`
|
|
1871
|
+
}]
|
|
1872
|
+
};
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
catch (error) {
|
|
1876
|
+
return {
|
|
1877
|
+
content: [{ type: 'text', text: `📂 FAF Context:\n\n❌ Error: ${error.message}` }],
|
|
1878
|
+
isError: true
|
|
1879
|
+
};
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
/**
|
|
1883
|
+
* faf_go - Guided interview to Gold Code
|
|
1884
|
+
*
|
|
1885
|
+
* Two-phase operation:
|
|
1886
|
+
* 1. Without answers: Returns questions for missing fields
|
|
1887
|
+
* 2. With answers: Applies answers to .faf file and returns new score
|
|
1888
|
+
*/
|
|
1889
|
+
async handleFafGo(args) {
|
|
1890
|
+
const yaml = await import('yaml');
|
|
1891
|
+
const cwd = this.getProjectPath(args?.path);
|
|
1892
|
+
try {
|
|
1893
|
+
// Find .faf file
|
|
1894
|
+
const fafResult = await (0, faf_file_finder_js_1.findFafFile)(cwd);
|
|
1895
|
+
if (!fafResult) {
|
|
1896
|
+
return {
|
|
1897
|
+
content: [{
|
|
1898
|
+
type: 'text',
|
|
1899
|
+
text: JSON.stringify({
|
|
1900
|
+
needsInit: true,
|
|
1901
|
+
context: 'faf_go',
|
|
1902
|
+
message: 'No project.faf found. Run faf_init first to create project DNA.',
|
|
1903
|
+
suggestion: 'Use faf_init to create project.faf, then use faf_go to reach Gold Code.'
|
|
1904
|
+
}, null, 2)
|
|
1905
|
+
}]
|
|
1906
|
+
};
|
|
1907
|
+
}
|
|
1908
|
+
const fafContent = fs.readFileSync(fafResult.path, 'utf-8');
|
|
1909
|
+
const fafData = yaml.parse(fafContent) || {};
|
|
1910
|
+
// Question registry - maps field paths to questions
|
|
1911
|
+
const QUESTION_REGISTRY = {
|
|
1912
|
+
'project.goal': {
|
|
1913
|
+
question: 'What does this project do? (one sentence)',
|
|
1914
|
+
header: 'Goal',
|
|
1915
|
+
type: 'text',
|
|
1916
|
+
required: true
|
|
1917
|
+
},
|
|
1918
|
+
'project.name': {
|
|
1919
|
+
question: 'What is the name of this project?',
|
|
1920
|
+
header: 'Name',
|
|
1921
|
+
type: 'text',
|
|
1922
|
+
required: true
|
|
1923
|
+
},
|
|
1924
|
+
'project.main_language': {
|
|
1925
|
+
question: 'What is the primary programming language?',
|
|
1926
|
+
header: 'Language',
|
|
1927
|
+
type: 'select',
|
|
1928
|
+
required: true,
|
|
1929
|
+
options: [
|
|
1930
|
+
{ label: 'TypeScript', value: 'TypeScript', description: 'JavaScript with types' },
|
|
1931
|
+
{ label: 'JavaScript', value: 'JavaScript', description: 'Vanilla JS or Node.js' },
|
|
1932
|
+
{ label: 'Python', value: 'Python', description: 'Python 3.x' },
|
|
1933
|
+
{ label: 'Rust', value: 'Rust', description: 'Systems programming' },
|
|
1934
|
+
{ label: 'Go', value: 'Go', description: 'Golang' },
|
|
1935
|
+
{ label: 'Other', value: 'Other', description: 'Specify manually' }
|
|
1936
|
+
]
|
|
1937
|
+
},
|
|
1938
|
+
'human_context.why': {
|
|
1939
|
+
question: 'Why does this project exist? (motivation)',
|
|
1940
|
+
header: 'Why',
|
|
1941
|
+
type: 'text',
|
|
1942
|
+
required: true
|
|
1943
|
+
},
|
|
1944
|
+
'human_context.who': {
|
|
1945
|
+
question: 'Who uses this project? (target audience)',
|
|
1946
|
+
header: 'Who',
|
|
1947
|
+
type: 'text',
|
|
1948
|
+
required: false
|
|
1949
|
+
},
|
|
1950
|
+
'human_context.what': {
|
|
1951
|
+
question: 'What problem does this solve?',
|
|
1952
|
+
header: 'What',
|
|
1953
|
+
type: 'text',
|
|
1954
|
+
required: false
|
|
1955
|
+
},
|
|
1956
|
+
'human_context.where': {
|
|
1957
|
+
question: 'Where does this run? (environment)',
|
|
1958
|
+
header: 'Where',
|
|
1959
|
+
type: 'text',
|
|
1960
|
+
required: false
|
|
1961
|
+
},
|
|
1962
|
+
'human_context.when': {
|
|
1963
|
+
question: 'When was this started or what phase is it in?',
|
|
1964
|
+
header: 'When',
|
|
1965
|
+
type: 'text',
|
|
1966
|
+
required: false
|
|
1967
|
+
},
|
|
1968
|
+
'human_context.how': {
|
|
1969
|
+
question: 'How should AI assist with this project?',
|
|
1970
|
+
header: 'How',
|
|
1971
|
+
type: 'text',
|
|
1972
|
+
required: false
|
|
1973
|
+
},
|
|
1974
|
+
'stack.frontend': {
|
|
1975
|
+
question: 'What frontend framework do you use?',
|
|
1976
|
+
header: 'Frontend',
|
|
1977
|
+
type: 'select',
|
|
1978
|
+
required: false,
|
|
1979
|
+
options: [
|
|
1980
|
+
{ label: 'React', value: 'React', description: 'React.js' },
|
|
1981
|
+
{ label: 'Vue', value: 'Vue', description: 'Vue.js' },
|
|
1982
|
+
{ label: 'Svelte', value: 'Svelte', description: 'Svelte/SvelteKit' },
|
|
1983
|
+
{ label: 'Next.js', value: 'Next.js', description: 'React framework' },
|
|
1984
|
+
{ label: 'None', value: 'None', description: 'No frontend' },
|
|
1985
|
+
{ label: 'Other', value: 'Other', description: 'Specify manually' }
|
|
1986
|
+
]
|
|
1987
|
+
},
|
|
1988
|
+
'stack.backend': {
|
|
1989
|
+
question: 'What backend framework do you use?',
|
|
1990
|
+
header: 'Backend',
|
|
1991
|
+
type: 'select',
|
|
1992
|
+
required: false,
|
|
1993
|
+
options: [
|
|
1994
|
+
{ label: 'Express', value: 'Express', description: 'Node.js Express' },
|
|
1995
|
+
{ label: 'Fastify', value: 'Fastify', description: 'Node.js Fastify' },
|
|
1996
|
+
{ label: 'Django', value: 'Django', description: 'Python Django' },
|
|
1997
|
+
{ label: 'FastAPI', value: 'FastAPI', description: 'Python FastAPI' },
|
|
1998
|
+
{ label: 'None', value: 'None', description: 'No backend' },
|
|
1999
|
+
{ label: 'Other', value: 'Other', description: 'Specify manually' }
|
|
2000
|
+
]
|
|
2001
|
+
},
|
|
2002
|
+
'stack.database': {
|
|
2003
|
+
question: 'What database do you use?',
|
|
2004
|
+
header: 'Database',
|
|
2005
|
+
type: 'select',
|
|
2006
|
+
required: false,
|
|
2007
|
+
options: [
|
|
2008
|
+
{ label: 'PostgreSQL', value: 'PostgreSQL', description: 'Relational database' },
|
|
2009
|
+
{ label: 'MongoDB', value: 'MongoDB', description: 'Document database' },
|
|
2010
|
+
{ label: 'SQLite', value: 'SQLite', description: 'File-based database' },
|
|
2011
|
+
{ label: 'Supabase', value: 'Supabase', description: 'Postgres + auth' },
|
|
2012
|
+
{ label: 'None', value: 'None', description: 'No database' },
|
|
2013
|
+
{ label: 'Other', value: 'Other', description: 'Specify manually' }
|
|
2014
|
+
]
|
|
2015
|
+
},
|
|
2016
|
+
'stack.hosting': {
|
|
2017
|
+
question: 'Where is this hosted/deployed?',
|
|
2018
|
+
header: 'Hosting',
|
|
2019
|
+
type: 'select',
|
|
2020
|
+
required: false,
|
|
2021
|
+
options: [
|
|
2022
|
+
{ label: 'Vercel', value: 'Vercel', description: 'Frontend/serverless' },
|
|
2023
|
+
{ label: 'AWS', value: 'AWS', description: 'Amazon Web Services' },
|
|
2024
|
+
{ label: 'Cloudflare', value: 'Cloudflare', description: 'Workers/Pages' },
|
|
2025
|
+
{ label: 'Railway', value: 'Railway', description: 'App hosting' },
|
|
2026
|
+
{ label: 'Local only', value: 'Local', description: 'Not deployed' },
|
|
2027
|
+
{ label: 'Other', value: 'Other', description: 'Specify manually' }
|
|
2028
|
+
]
|
|
2029
|
+
}
|
|
2030
|
+
};
|
|
2031
|
+
// Priority order for questions
|
|
2032
|
+
const priorityOrder = [
|
|
2033
|
+
'project.goal',
|
|
2034
|
+
'human_context.why',
|
|
2035
|
+
'human_context.who',
|
|
2036
|
+
'human_context.what',
|
|
2037
|
+
'project.name',
|
|
2038
|
+
'project.main_language',
|
|
2039
|
+
'stack.database',
|
|
2040
|
+
'stack.hosting',
|
|
2041
|
+
'stack.frontend',
|
|
2042
|
+
'stack.backend',
|
|
2043
|
+
'human_context.where',
|
|
2044
|
+
'human_context.when',
|
|
2045
|
+
'human_context.how'
|
|
2046
|
+
];
|
|
2047
|
+
// Helper to get nested value
|
|
2048
|
+
const getNestedValue = (obj, path) => {
|
|
2049
|
+
const parts = path.split('.');
|
|
2050
|
+
let value = obj;
|
|
2051
|
+
for (const part of parts) {
|
|
2052
|
+
if (value && typeof value === 'object' && part in value) {
|
|
2053
|
+
value = value[part];
|
|
2054
|
+
}
|
|
2055
|
+
else {
|
|
2056
|
+
return undefined;
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
return value;
|
|
2060
|
+
};
|
|
2061
|
+
// Helper to set nested value
|
|
2062
|
+
const setNestedValue = (obj, path, value) => {
|
|
2063
|
+
const parts = path.split('.');
|
|
2064
|
+
let current = obj;
|
|
2065
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
2066
|
+
const part = parts[i];
|
|
2067
|
+
if (!(part in current)) {
|
|
2068
|
+
current[part] = {};
|
|
2069
|
+
}
|
|
2070
|
+
current = current[part];
|
|
2071
|
+
}
|
|
2072
|
+
current[parts[parts.length - 1]] = value;
|
|
2073
|
+
};
|
|
2074
|
+
// Check if value is empty/placeholder
|
|
2075
|
+
const isEmpty = (value) => {
|
|
2076
|
+
return value === undefined ||
|
|
2077
|
+
value === null ||
|
|
2078
|
+
value === '' ||
|
|
2079
|
+
value === 'Unknown' ||
|
|
2080
|
+
value === 'TBD' ||
|
|
2081
|
+
value === 'None' ||
|
|
2082
|
+
(typeof value === 'string' && value.toLowerCase().includes('placeholder'));
|
|
2083
|
+
};
|
|
2084
|
+
// PHASE 2: Apply answers if provided
|
|
2085
|
+
if (args?.answers && typeof args.answers === 'object') {
|
|
2086
|
+
const answers = args.answers;
|
|
2087
|
+
let appliedCount = 0;
|
|
2088
|
+
for (const [fieldPath, answer] of Object.entries(answers)) {
|
|
2089
|
+
if (answer && answer.trim()) {
|
|
2090
|
+
setNestedValue(fafData, fieldPath, answer.trim());
|
|
2091
|
+
appliedCount++;
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
// Write updated file
|
|
2095
|
+
fs.writeFileSync(fafResult.path, yaml.stringify(fafData), 'utf-8');
|
|
2096
|
+
// Calculate new score (simple count-based)
|
|
2097
|
+
const totalFields = Object.keys(QUESTION_REGISTRY).length;
|
|
2098
|
+
const filledFields = Object.keys(QUESTION_REGISTRY).filter(field => !isEmpty(getNestedValue(fafData, field))).length;
|
|
2099
|
+
const newScore = Math.round((filledFields / totalFields) * 100);
|
|
2100
|
+
const celebration = newScore >= 100 ? '🏆 GOLD CODE ACHIEVED!' :
|
|
2101
|
+
newScore >= 85 ? '🥇 Championship grade!' :
|
|
2102
|
+
newScore >= 70 ? '🥈 Great progress!' : '📈 Keep going!';
|
|
2103
|
+
return {
|
|
2104
|
+
content: [{
|
|
2105
|
+
type: 'text',
|
|
2106
|
+
text: `🎯 FAF Go - Answers Applied!\n\n✅ Updated ${appliedCount} field(s) in ${fafResult.filename}\n📊 New Score: ${newScore}%\n${celebration}\n\n${newScore < 100 ? '💡 Run faf_go again to continue to Gold Code!' : '✨ Your AI now has complete context!'}`
|
|
2107
|
+
}]
|
|
2108
|
+
};
|
|
2109
|
+
}
|
|
2110
|
+
// PHASE 1: Analyze and return questions
|
|
2111
|
+
const missingFields = [];
|
|
2112
|
+
for (const fieldPath of Object.keys(QUESTION_REGISTRY)) {
|
|
2113
|
+
const value = getNestedValue(fafData, fieldPath);
|
|
2114
|
+
if (isEmpty(value)) {
|
|
2115
|
+
missingFields.push(fieldPath);
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
// Calculate current score
|
|
2119
|
+
const totalFields = Object.keys(QUESTION_REGISTRY).length;
|
|
2120
|
+
const filledFields = totalFields - missingFields.length;
|
|
2121
|
+
const currentScore = Math.round((filledFields / totalFields) * 100);
|
|
2122
|
+
// Already at 100%?
|
|
2123
|
+
if (currentScore >= 100) {
|
|
2124
|
+
return {
|
|
2125
|
+
content: [{
|
|
2126
|
+
type: 'text',
|
|
2127
|
+
text: JSON.stringify({
|
|
2128
|
+
complete: true,
|
|
2129
|
+
score: 100,
|
|
2130
|
+
message: '🏆 GOLD CODE ACHIEVED! Your project has 100% AI-Readiness.',
|
|
2131
|
+
context: 'faf_go'
|
|
2132
|
+
}, null, 2)
|
|
2133
|
+
}]
|
|
2134
|
+
};
|
|
2135
|
+
}
|
|
2136
|
+
// No missing fields but score < 100? Content quality issue
|
|
2137
|
+
if (missingFields.length === 0) {
|
|
2138
|
+
return {
|
|
2139
|
+
content: [{
|
|
2140
|
+
type: 'text',
|
|
2141
|
+
text: JSON.stringify({
|
|
2142
|
+
score: currentScore,
|
|
2143
|
+
message: `Score is ${currentScore}%. All fields filled but content may need enhancement.`,
|
|
2144
|
+
suggestion: 'Use faf_enhance to improve content quality.',
|
|
2145
|
+
context: 'faf_go'
|
|
2146
|
+
}, null, 2)
|
|
2147
|
+
}]
|
|
2148
|
+
};
|
|
2149
|
+
}
|
|
2150
|
+
// Sort by priority
|
|
2151
|
+
const prioritizedFields = missingFields.sort((a, b) => {
|
|
2152
|
+
const aIdx = priorityOrder.indexOf(a);
|
|
2153
|
+
const bIdx = priorityOrder.indexOf(b);
|
|
2154
|
+
if (aIdx === -1)
|
|
2155
|
+
return 1;
|
|
2156
|
+
if (bIdx === -1)
|
|
2157
|
+
return -1;
|
|
2158
|
+
return aIdx - bIdx;
|
|
2159
|
+
});
|
|
2160
|
+
// Build questions
|
|
2161
|
+
const questions = prioritizedFields.map(field => {
|
|
2162
|
+
const reg = QUESTION_REGISTRY[field];
|
|
2163
|
+
return {
|
|
2164
|
+
field,
|
|
2165
|
+
question: reg.question,
|
|
2166
|
+
header: reg.header,
|
|
2167
|
+
type: reg.type,
|
|
2168
|
+
required: reg.required,
|
|
2169
|
+
options: reg.options
|
|
2170
|
+
};
|
|
2171
|
+
});
|
|
2172
|
+
return {
|
|
2173
|
+
content: [{
|
|
2174
|
+
type: 'text',
|
|
2175
|
+
text: JSON.stringify({
|
|
2176
|
+
needsInput: true,
|
|
2177
|
+
context: 'faf_go - guided path to Gold Code',
|
|
2178
|
+
currentScore,
|
|
2179
|
+
targetScore: 100,
|
|
2180
|
+
questionsRemaining: questions.length,
|
|
2181
|
+
questions,
|
|
2182
|
+
instructions: 'Use AskUserQuestion to ask these questions, then call faf_go again with the answers parameter to apply them.'
|
|
2183
|
+
}, null, 2)
|
|
2184
|
+
}]
|
|
2185
|
+
};
|
|
2186
|
+
}
|
|
2187
|
+
catch (error) {
|
|
2188
|
+
return {
|
|
2189
|
+
content: [{ type: 'text', text: `🎯 FAF Go:\n\n❌ Error: ${error.message}` }],
|
|
2190
|
+
isError: true
|
|
2191
|
+
};
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
/**
|
|
2195
|
+
* faf_auto - ONE COMMAND TO RULE THEM ALL
|
|
2196
|
+
* Zero to Championship in one command
|
|
2197
|
+
* Runs: init + formats + sync + bi-sync + score
|
|
2198
|
+
*/
|
|
2199
|
+
async handleFafAuto(args) {
|
|
2200
|
+
const startTime = Date.now();
|
|
2201
|
+
const cwd = this.getProjectPath(args?.path);
|
|
2202
|
+
const yaml = await import('yaml');
|
|
2203
|
+
const path = await import('path');
|
|
2204
|
+
try {
|
|
2205
|
+
const steps = [];
|
|
2206
|
+
let currentScore = 0;
|
|
2207
|
+
// Step 1: Check/Create .faf file
|
|
2208
|
+
const fafResult = await (0, faf_file_finder_js_1.findFafFile)(cwd);
|
|
2209
|
+
let fafPath;
|
|
2210
|
+
if (!fafResult) {
|
|
2211
|
+
// Create .faf file
|
|
2212
|
+
const projectName = path.basename(cwd);
|
|
2213
|
+
fafPath = path.join(cwd, 'project.faf');
|
|
2214
|
+
const initFafContent = `# FAF - Foundational AI Context
|
|
2215
|
+
project: ${projectName}
|
|
2216
|
+
type: auto-detected
|
|
2217
|
+
context: I⚡🍊
|
|
2218
|
+
generated: ${new Date().toISOString()}
|
|
2219
|
+
version: ${version_1.VERSION}
|
|
2220
|
+
|
|
2221
|
+
# Quick Context
|
|
2222
|
+
working_directory: ${cwd}
|
|
2223
|
+
initialized_by: claude-faf-mcp-auto
|
|
2224
|
+
vitamin_context: true
|
|
2225
|
+
faffless: true
|
|
2226
|
+
`;
|
|
2227
|
+
fs.writeFileSync(fafPath, initFafContent);
|
|
2228
|
+
steps.push('✅ Created project.faf');
|
|
2229
|
+
}
|
|
2230
|
+
else {
|
|
2231
|
+
fafPath = fafResult.path;
|
|
2232
|
+
steps.push(`✅ Found ${fafResult.filename}`);
|
|
2233
|
+
}
|
|
2234
|
+
// Get initial score
|
|
2235
|
+
const fafContent = fs.readFileSync(fafPath, 'utf-8');
|
|
2236
|
+
const fafData = yaml.parse(fafContent) || {};
|
|
2237
|
+
currentScore = this.calculateSimpleScore(fafData);
|
|
2238
|
+
// Step 2: Run TURBO-CAT format discovery
|
|
2239
|
+
const formatsResult = await this.discoverFormatsInternal(cwd);
|
|
2240
|
+
if (formatsResult.discoveredFormats.length > 0) {
|
|
2241
|
+
// Apply slot fills to .faf
|
|
2242
|
+
if (!fafData.stack)
|
|
2243
|
+
fafData.stack = {};
|
|
2244
|
+
for (const [key, value] of Object.entries(formatsResult.slotFillRecommendations)) {
|
|
2245
|
+
if (!fafData.stack[key] || fafData.stack[key] === 'None') {
|
|
2246
|
+
fafData.stack[key] = value;
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
if (formatsResult.stackSignature) {
|
|
2250
|
+
fafData.stack_signature = formatsResult.stackSignature;
|
|
2251
|
+
}
|
|
2252
|
+
fs.writeFileSync(fafPath, yaml.stringify(fafData), 'utf-8');
|
|
2253
|
+
steps.push(`✅ TURBO-CAT discovered ${formatsResult.discoveredFormats.length} formats`);
|
|
2254
|
+
}
|
|
2255
|
+
else {
|
|
2256
|
+
steps.push('⚠️ No additional formats detected');
|
|
2257
|
+
}
|
|
2258
|
+
// Step 3: Extract human context from README
|
|
2259
|
+
const readmePath = path.join(cwd, 'README.md');
|
|
2260
|
+
if (fs.existsSync(readmePath)) {
|
|
2261
|
+
const readmeContent = fs.readFileSync(readmePath, 'utf-8');
|
|
2262
|
+
const extracted = this.extractSixWsFromReadme(readmeContent);
|
|
2263
|
+
if (!fafData.human_context)
|
|
2264
|
+
fafData.human_context = {};
|
|
2265
|
+
let extractedCount = 0;
|
|
2266
|
+
for (const [field, value] of Object.entries(extracted)) {
|
|
2267
|
+
if (value && !fafData.human_context[field]) {
|
|
2268
|
+
fafData.human_context[field] = value;
|
|
2269
|
+
extractedCount++;
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
if (extractedCount > 0) {
|
|
2273
|
+
fs.writeFileSync(fafPath, yaml.stringify(fafData), 'utf-8');
|
|
2274
|
+
steps.push(`✅ Extracted ${extractedCount} human context fields from README`);
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
// Step 4: Create/Update CLAUDE.md (bi-sync)
|
|
2278
|
+
const claudePath = path.join(cwd, 'CLAUDE.md');
|
|
2279
|
+
if (!fs.existsSync(claudePath)) {
|
|
2280
|
+
const claudeContent = `# 🏎️ CLAUDE.md - AI Telemetry Link
|
|
2281
|
+
|
|
2282
|
+
## Project: ${fafData.project || path.basename(cwd)}
|
|
2283
|
+
**Championship-Grade Project DNA Foundation**
|
|
2284
|
+
|
|
2285
|
+
### 🎯 Project Mission
|
|
2286
|
+
${fafData.human_context?.why || fafData.project?.goal || 'AI-ready project context'}
|
|
2287
|
+
|
|
2288
|
+
### 🏗️ Architecture Overview
|
|
2289
|
+
${fafData.stack_signature || 'Auto-detected stack'}
|
|
2290
|
+
|
|
2291
|
+
---
|
|
2292
|
+
|
|
2293
|
+
**STATUS: BI-SYNC ACTIVE 🔗**
|
|
2294
|
+
*Last Sync: ${new Date().toISOString()}*
|
|
2295
|
+
*Sync Engine: FAF Auto*
|
|
2296
|
+
`;
|
|
2297
|
+
fs.writeFileSync(claudePath, claudeContent);
|
|
2298
|
+
steps.push('✅ Created CLAUDE.md');
|
|
2299
|
+
}
|
|
2300
|
+
else {
|
|
2301
|
+
steps.push('✅ CLAUDE.md already exists');
|
|
2302
|
+
}
|
|
2303
|
+
// Step 5: Calculate final score
|
|
2304
|
+
const updatedContent = fs.readFileSync(fafPath, 'utf-8');
|
|
2305
|
+
const updatedData = yaml.parse(updatedContent) || {};
|
|
2306
|
+
const newScore = this.calculateSimpleScore(updatedData);
|
|
2307
|
+
const scoreDelta = newScore - currentScore;
|
|
2308
|
+
// Calculate elapsed time
|
|
2309
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
2310
|
+
// Format output
|
|
2311
|
+
const deltaDisplay = scoreDelta > 0 ? `(+${scoreDelta}%)` : scoreDelta < 0 ? `(${scoreDelta}%)` : '(no change)';
|
|
2312
|
+
let output = `🏎️⚡️ FAF AUTO - CHAMPIONSHIP MODE!\n`;
|
|
2313
|
+
output += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n`;
|
|
2314
|
+
output += steps.join('\n') + '\n\n';
|
|
2315
|
+
output += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
|
|
2316
|
+
output += `⏱️ Completed in ${elapsed}s\n`;
|
|
2317
|
+
output += `📊 Before: ${currentScore}% | After: ${newScore}% ${deltaDisplay}\n`;
|
|
2318
|
+
output += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n`;
|
|
2319
|
+
if (newScore >= 99) {
|
|
2320
|
+
output += `🏆 CHAMPIONSHIP ACHIEVED! Your AI has complete context.\n`;
|
|
2321
|
+
}
|
|
2322
|
+
else if (newScore >= 85) {
|
|
2323
|
+
output += `🥇 Elite level! ${100 - newScore}% to perfection.\n`;
|
|
2324
|
+
}
|
|
2325
|
+
else if (newScore >= 70) {
|
|
2326
|
+
output += `🥈 Great progress! Run faf_go to reach championship.\n`;
|
|
2327
|
+
}
|
|
2328
|
+
else {
|
|
2329
|
+
output += `🚀 Good start! Run faf_go for guided improvement.\n`;
|
|
2330
|
+
}
|
|
2331
|
+
output += `\n💡 Next: faf_score --details | faf_go | faf_enhance`;
|
|
2332
|
+
return { content: [{ type: 'text', text: output }] };
|
|
2333
|
+
}
|
|
2334
|
+
catch (error) {
|
|
2335
|
+
return {
|
|
2336
|
+
content: [{ type: 'text', text: `🏎️ FAF Auto:\n\n❌ Error: ${error.message}` }],
|
|
2337
|
+
isError: true
|
|
2338
|
+
};
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
/**
|
|
2342
|
+
* faf_dna - Show your FAF DNA journey
|
|
2343
|
+
* Displays evolution from birth to current (22% → 85% → 99%)
|
|
2344
|
+
*/
|
|
2345
|
+
async handleFafDna(args) {
|
|
2346
|
+
const cwd = this.getProjectPath(args?.path);
|
|
2347
|
+
const path = await import('path');
|
|
2348
|
+
try {
|
|
2349
|
+
const dnaPath = path.join(cwd, '.faf-dna');
|
|
2350
|
+
// Check if DNA file exists
|
|
2351
|
+
if (!fs.existsSync(dnaPath)) {
|
|
2352
|
+
// No DNA yet - check if .faf exists
|
|
2353
|
+
const fafResult = await (0, faf_file_finder_js_1.findFafFile)(cwd);
|
|
2354
|
+
if (!fafResult) {
|
|
2355
|
+
return {
|
|
2356
|
+
content: [{
|
|
2357
|
+
type: 'text',
|
|
2358
|
+
text: `🧬 FAF DNA Journey\n\n❌ No FAF DNA found\n💡 Run faf_auto to start your journey!`
|
|
2359
|
+
}]
|
|
2360
|
+
};
|
|
2361
|
+
}
|
|
2362
|
+
// .faf exists but no DNA - create initial DNA
|
|
2363
|
+
const yaml = await import('yaml');
|
|
2364
|
+
const fafContent = fs.readFileSync(fafResult.path, 'utf-8');
|
|
2365
|
+
const fafData = yaml.parse(fafContent) || {};
|
|
2366
|
+
const currentScore = this.calculateSimpleScore(fafData);
|
|
2367
|
+
const dna = {
|
|
2368
|
+
birthCertificate: {
|
|
2369
|
+
born: new Date().toISOString(),
|
|
2370
|
+
birthDNA: currentScore,
|
|
2371
|
+
birthDNASource: 'auto',
|
|
2372
|
+
authenticated: false,
|
|
2373
|
+
certificate: `FAF-${new Date().getFullYear()}-${path.basename(cwd).toUpperCase().slice(0, 8)}-${Math.random().toString(36).slice(2, 6).toUpperCase()}`
|
|
2374
|
+
},
|
|
2375
|
+
current: {
|
|
2376
|
+
score: currentScore,
|
|
2377
|
+
version: 'v1.0.0',
|
|
2378
|
+
lastSync: new Date().toISOString()
|
|
2379
|
+
},
|
|
2380
|
+
milestones: [
|
|
2381
|
+
{ type: 'birth', score: currentScore, date: new Date().toISOString(), version: 'v1.0.0' }
|
|
2382
|
+
],
|
|
2383
|
+
format: 'faf-dna-v1'
|
|
2384
|
+
};
|
|
2385
|
+
fs.writeFileSync(dnaPath, JSON.stringify(dna, null, 2));
|
|
2386
|
+
return {
|
|
2387
|
+
content: [{
|
|
2388
|
+
type: 'text',
|
|
2389
|
+
text: `🧬 FAF DNA Journey\n\n🐣 Birth Certificate Created!\n\n📊 Birth DNA: ${currentScore}%\n📅 Born: ${new Date().toISOString().split('T')[0]}\n🎫 Certificate: ${dna.birthCertificate.certificate}\n\n💡 Your journey begins here! Run faf_auto or faf_go to grow.`
|
|
2390
|
+
}]
|
|
2391
|
+
};
|
|
2392
|
+
}
|
|
2393
|
+
// Load existing DNA
|
|
2394
|
+
const dnaContent = fs.readFileSync(dnaPath, 'utf-8');
|
|
2395
|
+
const dna = JSON.parse(dnaContent);
|
|
2396
|
+
// Build journey string
|
|
2397
|
+
const birthScore = dna.birthCertificate?.birthDNA || 0;
|
|
2398
|
+
const currentScore = dna.current?.score || 0;
|
|
2399
|
+
const milestones = dna.milestones || [];
|
|
2400
|
+
// Find key milestones
|
|
2401
|
+
const _birth = milestones.find((m) => m.type === 'birth');
|
|
2402
|
+
const peak = milestones.find((m) => m.type === 'peak');
|
|
2403
|
+
const championship = milestones.find((m) => m.type === 'championship');
|
|
2404
|
+
const elite = milestones.find((m) => m.type === 'elite');
|
|
2405
|
+
// Build compact journey
|
|
2406
|
+
let journey = `${birthScore}%`;
|
|
2407
|
+
if (championship && championship.score !== birthScore) {
|
|
2408
|
+
journey += ` → ${championship.score}%`;
|
|
2409
|
+
}
|
|
2410
|
+
if (elite && (!championship || elite.score !== championship.score)) {
|
|
2411
|
+
journey += ` → ${elite.score}%`;
|
|
2412
|
+
}
|
|
2413
|
+
if (peak) {
|
|
2414
|
+
journey += ` → ${peak.score}%`;
|
|
2415
|
+
if (currentScore < peak.score) {
|
|
2416
|
+
journey += ` ← ${currentScore}%`;
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
else if (currentScore !== birthScore) {
|
|
2420
|
+
journey += ` → ${currentScore}%`;
|
|
2421
|
+
}
|
|
2422
|
+
// Calculate stats
|
|
2423
|
+
const birthDate = new Date(dna.birthCertificate?.born || Date.now());
|
|
2424
|
+
const daysActive = Math.floor((Date.now() - birthDate.getTime()) / (1000 * 60 * 60 * 24));
|
|
2425
|
+
const totalGrowth = currentScore - birthScore;
|
|
2426
|
+
let output = `🧬 YOUR FAF DNA\n\n`;
|
|
2427
|
+
output += ` ${journey}\n\n`;
|
|
2428
|
+
output += `═══════════════════════════════════════════════════\n\n`;
|
|
2429
|
+
output += `📊 QUICK STATS\n`;
|
|
2430
|
+
output += ` Born: ${birthDate.toISOString().split('T')[0]}\n`;
|
|
2431
|
+
output += ` Days Active: ${daysActive}\n`;
|
|
2432
|
+
output += ` Total Growth: +${totalGrowth}%\n`;
|
|
2433
|
+
if (dna.birthCertificate?.authenticated) {
|
|
2434
|
+
output += ` ✅ Authenticated: ${dna.birthCertificate.certificate}\n`;
|
|
2435
|
+
}
|
|
2436
|
+
else {
|
|
2437
|
+
output += ` ⚠️ Not authenticated\n`;
|
|
2438
|
+
}
|
|
2439
|
+
output += `\n🧬 MILESTONES\n`;
|
|
2440
|
+
const milestoneIcons = {
|
|
2441
|
+
birth: '🐣', first_save: '💾', doubled: '2️⃣',
|
|
2442
|
+
championship: '🏆', elite: '⭐', peak: '🏔️', perfect: '💎'
|
|
2443
|
+
};
|
|
2444
|
+
for (const m of milestones) {
|
|
2445
|
+
const icon = milestoneIcons[m.type] || '📍';
|
|
2446
|
+
const isCurrent = m.score === currentScore;
|
|
2447
|
+
output += ` ${icon} ${m.type}: ${m.score}%${isCurrent ? ' ← You are here!' : ''}\n`;
|
|
2448
|
+
}
|
|
2449
|
+
output += `\n═══════════════════════════════════════════════════\n`;
|
|
2450
|
+
// Motivational message
|
|
2451
|
+
if (totalGrowth > 70) {
|
|
2452
|
+
output += `🚀 Incredible journey! You've transformed your AI context!\n`;
|
|
2453
|
+
}
|
|
2454
|
+
else if (totalGrowth > 50) {
|
|
2455
|
+
output += `📈 Great progress! Your context is evolving beautifully.\n`;
|
|
2456
|
+
}
|
|
2457
|
+
else if (totalGrowth > 0) {
|
|
2458
|
+
output += `🌱 Your journey has begun. Every step counts!\n`;
|
|
2459
|
+
}
|
|
2460
|
+
else {
|
|
2461
|
+
output += `🐣 Just born! Your growth story starts now.\n`;
|
|
2462
|
+
}
|
|
2463
|
+
return { content: [{ type: 'text', text: output }] };
|
|
2464
|
+
}
|
|
2465
|
+
catch (error) {
|
|
2466
|
+
return {
|
|
2467
|
+
content: [{ type: 'text', text: `🧬 FAF DNA:\n\n❌ Error: ${error.message}` }],
|
|
2468
|
+
isError: true
|
|
2469
|
+
};
|
|
2470
|
+
}
|
|
2471
|
+
}
|
|
2472
|
+
/**
|
|
2473
|
+
* faf_formats - TURBO-CAT format discovery
|
|
2474
|
+
* Discovers all formats in the project
|
|
2475
|
+
*/
|
|
2476
|
+
async handleFafFormats(args) {
|
|
2477
|
+
const cwd = this.getProjectPath(args?.path);
|
|
2478
|
+
const startTime = Date.now();
|
|
2479
|
+
try {
|
|
2480
|
+
const analysis = await this.discoverFormatsInternal(cwd);
|
|
2481
|
+
const elapsed = Date.now() - startTime;
|
|
2482
|
+
if (args?.json) {
|
|
2483
|
+
return {
|
|
2484
|
+
content: [{
|
|
2485
|
+
type: 'text',
|
|
2486
|
+
text: JSON.stringify(analysis, null, 2)
|
|
2487
|
+
}]
|
|
2488
|
+
};
|
|
2489
|
+
}
|
|
2490
|
+
// Format human-readable output
|
|
2491
|
+
let output = `😽 TURBO-CAT™ Format Discovery v2.0.0\n`;
|
|
2492
|
+
output += `═══════════════════════════════════════════════════\n\n`;
|
|
2493
|
+
output += `✅ Found ${analysis.discoveredFormats.length} formats in ${elapsed}ms!\n\n`;
|
|
2494
|
+
output += `📋 Discovered Formats (A-Z):\n`;
|
|
2495
|
+
const sorted = [...analysis.discoveredFormats].sort((a, b) => a.fileName.localeCompare(b.fileName));
|
|
2496
|
+
for (const format of sorted) {
|
|
2497
|
+
output += ` ✅ ${format.fileName}\n`;
|
|
2498
|
+
}
|
|
2499
|
+
output += `\n💡 Stack Signature: ${analysis.stackSignature}\n`;
|
|
2500
|
+
output += `🏆 Intelligence Score: ${analysis.totalIntelligenceScore}\n\n`;
|
|
2501
|
+
if (Object.keys(analysis.slotFillRecommendations).length > 0) {
|
|
2502
|
+
output += `📊 Recommended Slot Fills:\n`;
|
|
2503
|
+
for (const [key, value] of Object.entries(analysis.slotFillRecommendations)) {
|
|
2504
|
+
output += ` • ${key}: ${value}\n`;
|
|
2505
|
+
}
|
|
2506
|
+
output += `\n`;
|
|
2507
|
+
}
|
|
2508
|
+
output += `───────────────────────────────────────────────────\n`;
|
|
2509
|
+
output += `😽 TURBO-CAT™: "I detected ${analysis.discoveredFormats.length} formats and made your stack PURRR!"\n`;
|
|
2510
|
+
return { content: [{ type: 'text', text: output }] };
|
|
2511
|
+
}
|
|
2512
|
+
catch (error) {
|
|
2513
|
+
return {
|
|
2514
|
+
content: [{ type: 'text', text: `😽 TURBO-CAT:\n\n❌ Error: ${error.message}` }],
|
|
2515
|
+
isError: true
|
|
2516
|
+
};
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
/**
|
|
2520
|
+
* Internal helper: Discover formats in a directory (TURBO-CAT logic)
|
|
2521
|
+
*/
|
|
2522
|
+
async discoverFormatsInternal(projectDir) {
|
|
2523
|
+
const path = await import('path');
|
|
2524
|
+
// Known format files and their categories
|
|
2525
|
+
const KNOWN_FORMATS = {
|
|
2526
|
+
'package.json': { category: 'package-manager', priority: 35 },
|
|
2527
|
+
'tsconfig.json': { category: 'typescript-config', priority: 30 },
|
|
2528
|
+
'Cargo.toml': { category: 'package-manager', priority: 35 },
|
|
2529
|
+
'pyproject.toml': { category: 'package-manager', priority: 35 },
|
|
2530
|
+
'requirements.txt': { category: 'package-manager', priority: 25 },
|
|
2531
|
+
'go.mod': { category: 'package-manager', priority: 35 },
|
|
2532
|
+
'pom.xml': { category: 'package-manager', priority: 35 },
|
|
2533
|
+
'README.md': { category: 'documentation', priority: 20 },
|
|
2534
|
+
'CLAUDE.md': { category: 'ai-context', priority: 40 },
|
|
2535
|
+
'project.faf': { category: 'faf-context', priority: 45 },
|
|
2536
|
+
'.faf': { category: 'faf-context', priority: 45 },
|
|
2537
|
+
'Dockerfile': { category: 'docker', priority: 25 },
|
|
2538
|
+
'docker-compose.yml': { category: 'docker', priority: 25 },
|
|
2539
|
+
'vercel.json': { category: 'deployment', priority: 20 },
|
|
2540
|
+
'netlify.toml': { category: 'deployment', priority: 20 },
|
|
2541
|
+
'.eslintrc.json': { category: 'linting', priority: 15 },
|
|
2542
|
+
'.prettierrc': { category: 'linting', priority: 15 },
|
|
2543
|
+
'jest.config.js': { category: 'testing', priority: 20 },
|
|
2544
|
+
'vitest.config.ts': { category: 'testing', priority: 20 },
|
|
2545
|
+
'svelte.config.js': { category: 'framework', priority: 30 },
|
|
2546
|
+
'next.config.js': { category: 'framework', priority: 30 },
|
|
2547
|
+
'vite.config.ts': { category: 'build', priority: 25 },
|
|
2548
|
+
'webpack.config.js': { category: 'build', priority: 25 },
|
|
2549
|
+
'.github': { category: 'ci-cd', priority: 20 },
|
|
2550
|
+
'manifest.json': { category: 'chrome-extension', priority: 35 }
|
|
2551
|
+
};
|
|
2552
|
+
const discoveredFormats = [];
|
|
2553
|
+
let totalIntelligenceScore = 0;
|
|
2554
|
+
const slotFillRecommendations = {};
|
|
2555
|
+
const extractedContext = {};
|
|
2556
|
+
// Scan directory
|
|
2557
|
+
try {
|
|
2558
|
+
const files = fs.readdirSync(projectDir);
|
|
2559
|
+
for (const file of files) {
|
|
2560
|
+
if (KNOWN_FORMATS[file]) {
|
|
2561
|
+
const format = KNOWN_FORMATS[file];
|
|
2562
|
+
discoveredFormats.push({
|
|
2563
|
+
fileName: file,
|
|
2564
|
+
category: format.category,
|
|
2565
|
+
priority: format.priority
|
|
2566
|
+
});
|
|
2567
|
+
totalIntelligenceScore += format.priority;
|
|
2568
|
+
}
|
|
2569
|
+
}
|
|
2570
|
+
// Extract intelligence from package.json
|
|
2571
|
+
const pkgPath = path.join(projectDir, 'package.json');
|
|
2572
|
+
if (fs.existsSync(pkgPath)) {
|
|
2573
|
+
const pkgContent = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
2574
|
+
const allDeps = { ...pkgContent.dependencies, ...pkgContent.devDependencies };
|
|
2575
|
+
extractedContext.projectName = pkgContent.name;
|
|
2576
|
+
extractedContext.projectDescription = pkgContent.description;
|
|
2577
|
+
// Detect frameworks and fill slots
|
|
2578
|
+
if (allDeps['typescript'] || allDeps['@types/node']) {
|
|
2579
|
+
slotFillRecommendations['mainLanguage'] = 'TypeScript';
|
|
2580
|
+
}
|
|
2581
|
+
if (allDeps['react'] || allDeps['next']) {
|
|
2582
|
+
slotFillRecommendations['frontend'] = allDeps['next'] ? 'Next.js' : 'React';
|
|
2583
|
+
}
|
|
2584
|
+
if (allDeps['vue'] || allDeps['nuxt']) {
|
|
2585
|
+
slotFillRecommendations['frontend'] = allDeps['nuxt'] ? 'Nuxt' : 'Vue';
|
|
2586
|
+
}
|
|
2587
|
+
if (allDeps['svelte'] || allDeps['@sveltejs/kit']) {
|
|
2588
|
+
slotFillRecommendations['frontend'] = allDeps['@sveltejs/kit'] ? 'SvelteKit' : 'Svelte';
|
|
2589
|
+
}
|
|
2590
|
+
if (allDeps['express']) {
|
|
2591
|
+
slotFillRecommendations['backend'] = 'Express';
|
|
2592
|
+
}
|
|
2593
|
+
if (allDeps['fastify']) {
|
|
2594
|
+
slotFillRecommendations['backend'] = 'Fastify';
|
|
2595
|
+
}
|
|
2596
|
+
if (allDeps['vite']) {
|
|
2597
|
+
slotFillRecommendations['build'] = 'Vite';
|
|
2598
|
+
}
|
|
2599
|
+
if (allDeps['jest'] || allDeps['vitest']) {
|
|
2600
|
+
slotFillRecommendations['testing'] = allDeps['vitest'] ? 'Vitest' : 'Jest';
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
// Check for deployment indicators
|
|
2604
|
+
if (fs.existsSync(path.join(projectDir, 'vercel.json'))) {
|
|
2605
|
+
slotFillRecommendations['hosting'] = 'Vercel';
|
|
2606
|
+
}
|
|
2607
|
+
else if (fs.existsSync(path.join(projectDir, 'netlify.toml'))) {
|
|
2608
|
+
slotFillRecommendations['hosting'] = 'Netlify';
|
|
2609
|
+
}
|
|
2610
|
+
}
|
|
2611
|
+
catch (error) {
|
|
2612
|
+
// Ignore errors, return empty results
|
|
2613
|
+
}
|
|
2614
|
+
// Generate stack signature
|
|
2615
|
+
const parts = [];
|
|
2616
|
+
if (slotFillRecommendations['mainLanguage'])
|
|
2617
|
+
parts.push(slotFillRecommendations['mainLanguage'].toLowerCase());
|
|
2618
|
+
if (slotFillRecommendations['frontend'])
|
|
2619
|
+
parts.push(slotFillRecommendations['frontend'].toLowerCase());
|
|
2620
|
+
const stackSignature = parts.length > 0 ? parts.join('-') : 'unknown-stack';
|
|
2621
|
+
return {
|
|
2622
|
+
discoveredFormats,
|
|
2623
|
+
totalIntelligenceScore,
|
|
2624
|
+
stackSignature,
|
|
2625
|
+
slotFillRecommendations,
|
|
2626
|
+
extractedContext
|
|
2627
|
+
};
|
|
2628
|
+
}
|
|
2629
|
+
/**
|
|
2630
|
+
* Internal helper: Calculate simple score from .faf data
|
|
2631
|
+
*/
|
|
2632
|
+
calculateSimpleScore(fafData) {
|
|
2633
|
+
let score = 0;
|
|
2634
|
+
const maxScore = 100;
|
|
2635
|
+
// Project section (30 points)
|
|
2636
|
+
if (fafData.project)
|
|
2637
|
+
score += 15;
|
|
2638
|
+
if (fafData.project?.goal || fafData.description)
|
|
2639
|
+
score += 15;
|
|
2640
|
+
// Human context (30 points)
|
|
2641
|
+
const humanContext = fafData.human_context || {};
|
|
2642
|
+
const wFields = ['who', 'what', 'why', 'where', 'when', 'how'];
|
|
2643
|
+
const filledW = wFields.filter(f => humanContext[f] && humanContext[f] !== 'null').length;
|
|
2644
|
+
score += Math.round((filledW / wFields.length) * 30);
|
|
2645
|
+
// Stack section (20 points)
|
|
2646
|
+
const stack = fafData.stack || {};
|
|
2647
|
+
const stackFields = ['frontend', 'backend', 'database', 'hosting', 'build'];
|
|
2648
|
+
const filledStack = stackFields.filter(f => stack[f] && stack[f] !== 'None').length;
|
|
2649
|
+
score += Math.round((filledStack / stackFields.length) * 20);
|
|
2650
|
+
// Files exist bonus (20 points)
|
|
2651
|
+
if (fafData.initialized_by || fafData.generated)
|
|
2652
|
+
score += 10;
|
|
2653
|
+
if (fafData.stack_signature)
|
|
2654
|
+
score += 10;
|
|
2655
|
+
return Math.min(score, maxScore);
|
|
2656
|
+
}
|
|
2657
|
+
/**
|
|
2658
|
+
* faf_quick - Lightning-fast .faf creation
|
|
2659
|
+
* One-liner format: "name, description, language, framework, hosting"
|
|
2660
|
+
*/
|
|
2661
|
+
async handleFafQuick(args) {
|
|
2662
|
+
const cwd = this.getProjectPath(args?.path);
|
|
2663
|
+
const path = await import('path');
|
|
2664
|
+
const yaml = await import('yaml');
|
|
2665
|
+
const startTime = Date.now();
|
|
2666
|
+
try {
|
|
2667
|
+
const input = args?.input;
|
|
2668
|
+
if (!input || typeof input !== 'string') {
|
|
2669
|
+
return {
|
|
2670
|
+
content: [{
|
|
2671
|
+
type: 'text',
|
|
2672
|
+
text: `⚡ FAF Quick
|
|
2673
|
+
|
|
2674
|
+
Usage: Provide a comma-separated string:
|
|
2675
|
+
"project-name, description, language, framework, hosting"
|
|
2676
|
+
|
|
2677
|
+
Examples:
|
|
2678
|
+
"my-app, e-commerce platform, typescript, react, vercel"
|
|
2679
|
+
"api-service, REST API for mobile app, python, fastapi, aws"
|
|
2680
|
+
"cli-tool, developer productivity tool, go"
|
|
2681
|
+
|
|
2682
|
+
Minimum: name and description. Rest is auto-detected!`
|
|
2683
|
+
}]
|
|
2684
|
+
};
|
|
2685
|
+
}
|
|
2686
|
+
// Parse the quick input
|
|
2687
|
+
const parts = input.split(',').map((s) => s.trim());
|
|
2688
|
+
if (parts.length < 2) {
|
|
2689
|
+
return {
|
|
2690
|
+
content: [{
|
|
2691
|
+
type: 'text',
|
|
2692
|
+
text: `⚡ FAF Quick: Need at least: project-name, description
|
|
2693
|
+
|
|
2694
|
+
Got: "${input}"
|
|
2695
|
+
|
|
2696
|
+
Example: "my-app, e-commerce platform"`
|
|
2697
|
+
}],
|
|
2698
|
+
isError: true
|
|
2699
|
+
};
|
|
2700
|
+
}
|
|
2701
|
+
const projectName = parts[0] || 'my-project';
|
|
2702
|
+
const projectGoal = parts[1] || 'Build amazing software';
|
|
2703
|
+
const mainLanguage = parts[2] || 'TypeScript';
|
|
2704
|
+
const framework = parts[3] || 'none';
|
|
2705
|
+
const hosting = parts[4] || 'cloud';
|
|
2706
|
+
// Check if .faf exists
|
|
2707
|
+
const fafPath = path.join(cwd, 'project.faf');
|
|
2708
|
+
if (fs.existsSync(fafPath) && !args?.force) {
|
|
2709
|
+
return {
|
|
2710
|
+
content: [{
|
|
2711
|
+
type: 'text',
|
|
2712
|
+
text: `⚡ FAF Quick
|
|
2713
|
+
|
|
2714
|
+
⚠️ project.faf already exists at: ${fafPath}
|
|
2715
|
+
|
|
2716
|
+
Use force: true to overwrite, or use faf_enhance to modify.`
|
|
2717
|
+
}]
|
|
2718
|
+
};
|
|
2719
|
+
}
|
|
2720
|
+
// Detect project type from inputs
|
|
2721
|
+
const projectType = this.detectProjectTypeFromQuick(projectGoal, framework, mainLanguage);
|
|
2722
|
+
// Build .faf content
|
|
2723
|
+
const fafData = {
|
|
2724
|
+
project: {
|
|
2725
|
+
name: projectName,
|
|
2726
|
+
goal: projectGoal,
|
|
2727
|
+
main_language: mainLanguage
|
|
2728
|
+
},
|
|
2729
|
+
type: projectType,
|
|
2730
|
+
generated: new Date().toISOString(),
|
|
2731
|
+
version: version_1.VERSION,
|
|
2732
|
+
initialized_by: 'claude-faf-mcp-quick'
|
|
2733
|
+
};
|
|
2734
|
+
if (framework && framework !== 'none') {
|
|
2735
|
+
fafData.stack = { frontend: framework };
|
|
2736
|
+
}
|
|
2737
|
+
if (hosting && hosting !== 'cloud') {
|
|
2738
|
+
if (!fafData.stack)
|
|
2739
|
+
fafData.stack = {};
|
|
2740
|
+
fafData.stack.hosting = hosting;
|
|
2741
|
+
}
|
|
2742
|
+
// Write the file
|
|
2743
|
+
fs.writeFileSync(fafPath, yaml.stringify(fafData), 'utf-8');
|
|
2744
|
+
const elapsed = Date.now() - startTime;
|
|
2745
|
+
let output = `⚡ FAF Quick - Created in ${elapsed}ms!\n\n`;
|
|
2746
|
+
output += `📦 Project: ${projectName}\n`;
|
|
2747
|
+
output += `🎯 Purpose: ${projectGoal}\n`;
|
|
2748
|
+
output += `💻 Stack: ${mainLanguage}${framework !== 'none' ? ` + ${framework}` : ''}\n`;
|
|
2749
|
+
output += `📍 Type: ${projectType}\n\n`;
|
|
2750
|
+
output += `✅ Created: ${fafPath}\n\n`;
|
|
2751
|
+
output += `Next steps:\n`;
|
|
2752
|
+
output += ` • faf_score - Check AI-readiness\n`;
|
|
2753
|
+
output += ` • faf_enhance - Improve context\n`;
|
|
2754
|
+
output += ` • faf_go - Guided interview to 100%`;
|
|
2755
|
+
return { content: [{ type: 'text', text: output }] };
|
|
2756
|
+
}
|
|
2757
|
+
catch (error) {
|
|
2758
|
+
return {
|
|
2759
|
+
content: [{ type: 'text', text: `⚡ FAF Quick:\n\n❌ Error: ${error.message}` }],
|
|
2760
|
+
isError: true
|
|
2761
|
+
};
|
|
2762
|
+
}
|
|
2763
|
+
}
|
|
2764
|
+
/**
|
|
2765
|
+
* Helper: Detect project type from quick input
|
|
2766
|
+
*/
|
|
2767
|
+
detectProjectTypeFromQuick(goal, framework, language) {
|
|
2768
|
+
const fw = framework?.toLowerCase() || '';
|
|
2769
|
+
const lang = language?.toLowerCase() || '';
|
|
2770
|
+
const g = goal?.toLowerCase() || '';
|
|
2771
|
+
// Framework-based detection
|
|
2772
|
+
if (fw.includes('react') || fw.includes('next'))
|
|
2773
|
+
return 'react';
|
|
2774
|
+
if (fw.includes('vue') || fw.includes('nuxt'))
|
|
2775
|
+
return 'vue';
|
|
2776
|
+
if (fw.includes('svelte') || fw.includes('kit'))
|
|
2777
|
+
return 'svelte';
|
|
2778
|
+
if (fw.includes('angular'))
|
|
2779
|
+
return 'angular';
|
|
2780
|
+
if (fw.includes('fastapi'))
|
|
2781
|
+
return 'python-fastapi';
|
|
2782
|
+
if (fw.includes('django'))
|
|
2783
|
+
return 'python-django';
|
|
2784
|
+
if (fw.includes('flask'))
|
|
2785
|
+
return 'python-flask';
|
|
2786
|
+
if (fw.includes('express'))
|
|
2787
|
+
return 'node-api';
|
|
2788
|
+
// Goal-based detection
|
|
2789
|
+
if (g.includes('chrome extension') || g.includes('browser extension'))
|
|
2790
|
+
return 'chrome-extension';
|
|
2791
|
+
if (g.includes('api') || g.includes('backend'))
|
|
2792
|
+
return 'node-api';
|
|
2793
|
+
if (g.includes('cli') || g.includes('command'))
|
|
2794
|
+
return 'cli-tool';
|
|
2795
|
+
if (g.includes('library') || g.includes('package'))
|
|
2796
|
+
return 'library';
|
|
2797
|
+
if (g.includes('mcp') || g.includes('model context'))
|
|
2798
|
+
return 'mcp-server';
|
|
2799
|
+
// Language-based fallback
|
|
2800
|
+
if (lang.includes('python'))
|
|
2801
|
+
return 'python';
|
|
2802
|
+
if (lang.includes('go'))
|
|
2803
|
+
return 'golang';
|
|
2804
|
+
if (lang.includes('rust'))
|
|
2805
|
+
return 'rust';
|
|
2806
|
+
if (lang.includes('typescript'))
|
|
2807
|
+
return 'typescript';
|
|
2808
|
+
return 'general';
|
|
2809
|
+
}
|
|
2810
|
+
/**
|
|
2811
|
+
* faf_doctor - Health check for .faf setup
|
|
2812
|
+
* Diagnose and fix common issues
|
|
2813
|
+
*/
|
|
2814
|
+
async handleFafDoctor(args) {
|
|
2815
|
+
const cwd = this.getProjectPath(args?.path);
|
|
2816
|
+
const path = await import('path');
|
|
2817
|
+
const yaml = await import('yaml');
|
|
2818
|
+
try {
|
|
2819
|
+
const results = [];
|
|
2820
|
+
// Check 1: MCP Version
|
|
2821
|
+
results.push({
|
|
2822
|
+
status: 'ok',
|
|
2823
|
+
message: `claude-faf-mcp version: ${version_1.VERSION}`
|
|
2824
|
+
});
|
|
2825
|
+
// Check 2: .faf file exists
|
|
2826
|
+
const fafResult = await (0, faf_file_finder_js_1.findFafFile)(cwd);
|
|
2827
|
+
if (!fafResult) {
|
|
2828
|
+
results.push({
|
|
2829
|
+
status: 'error',
|
|
2830
|
+
message: 'No .faf file found',
|
|
2831
|
+
fix: 'Run: faf_init, faf_quick, or faf_auto to create one'
|
|
2832
|
+
});
|
|
2833
|
+
}
|
|
2834
|
+
else {
|
|
2835
|
+
results.push({
|
|
2836
|
+
status: 'ok',
|
|
2837
|
+
message: `Found .faf at: ${fafResult.path}`
|
|
2838
|
+
});
|
|
2839
|
+
// Check 3: .faf file validity
|
|
2840
|
+
try {
|
|
2841
|
+
const content = fs.readFileSync(fafResult.path, 'utf-8');
|
|
2842
|
+
const fafData = yaml.parse(content);
|
|
2843
|
+
if (!fafData) {
|
|
2844
|
+
results.push({
|
|
2845
|
+
status: 'error',
|
|
2846
|
+
message: '.faf file is empty',
|
|
2847
|
+
fix: 'Run: faf_init with force option to regenerate'
|
|
2848
|
+
});
|
|
2849
|
+
}
|
|
2850
|
+
else {
|
|
2851
|
+
// Check for required fields
|
|
2852
|
+
const missingFields = [];
|
|
2853
|
+
if (!fafData.project?.name && !fafData.project)
|
|
2854
|
+
missingFields.push('project.name');
|
|
2855
|
+
if (!fafData.project?.goal)
|
|
2856
|
+
missingFields.push('project.goal');
|
|
2857
|
+
if (missingFields.length > 0) {
|
|
2858
|
+
results.push({
|
|
2859
|
+
status: 'warning',
|
|
2860
|
+
message: `Missing important fields: ${missingFields.join(', ')}`,
|
|
2861
|
+
fix: 'Run: faf_enhance or faf_go to add missing info'
|
|
2862
|
+
});
|
|
2863
|
+
}
|
|
2864
|
+
else {
|
|
2865
|
+
results.push({
|
|
2866
|
+
status: 'ok',
|
|
2867
|
+
message: '.faf structure is valid'
|
|
2868
|
+
});
|
|
2869
|
+
}
|
|
2870
|
+
// Check 4: Score
|
|
2871
|
+
const score = this.calculateSimpleScore(fafData);
|
|
2872
|
+
if (score < 30) {
|
|
2873
|
+
results.push({
|
|
2874
|
+
status: 'error',
|
|
2875
|
+
message: `Score too low: ${score}%`,
|
|
2876
|
+
fix: 'Run: faf_enhance or faf_go to improve context'
|
|
2877
|
+
});
|
|
2878
|
+
}
|
|
2879
|
+
else if (score < 70) {
|
|
2880
|
+
results.push({
|
|
2881
|
+
status: 'warning',
|
|
2882
|
+
message: `Score could be better: ${score}%`,
|
|
2883
|
+
fix: 'Target 70%+ for championship AI context'
|
|
2884
|
+
});
|
|
2885
|
+
}
|
|
2886
|
+
else {
|
|
2887
|
+
results.push({
|
|
2888
|
+
status: 'ok',
|
|
2889
|
+
message: `Great score: ${score}%`
|
|
2890
|
+
});
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
2894
|
+
catch {
|
|
2895
|
+
results.push({
|
|
2896
|
+
status: 'error',
|
|
2897
|
+
message: '.faf file is corrupted or invalid YAML',
|
|
2898
|
+
fix: 'Run: faf_init with force option to regenerate'
|
|
2899
|
+
});
|
|
2900
|
+
}
|
|
2901
|
+
}
|
|
2902
|
+
// Check 5: CLAUDE.md exists
|
|
2903
|
+
const claudePath = path.join(cwd, 'CLAUDE.md');
|
|
2904
|
+
if (!fs.existsSync(claudePath)) {
|
|
2905
|
+
results.push({
|
|
2906
|
+
status: 'warning',
|
|
2907
|
+
message: 'No CLAUDE.md file',
|
|
2908
|
+
fix: 'Run: faf_auto or faf_bi_sync to create bi-directional sync'
|
|
2909
|
+
});
|
|
2910
|
+
}
|
|
2911
|
+
else {
|
|
2912
|
+
results.push({
|
|
2913
|
+
status: 'ok',
|
|
2914
|
+
message: 'CLAUDE.md found (bi-sync ready)'
|
|
2915
|
+
});
|
|
2916
|
+
}
|
|
2917
|
+
// Check 6: Project detection
|
|
2918
|
+
const packageJsonPath = path.join(cwd, 'package.json');
|
|
2919
|
+
const requirementsPath = path.join(cwd, 'requirements.txt');
|
|
2920
|
+
const goModPath = path.join(cwd, 'go.mod');
|
|
2921
|
+
const cargoPath = path.join(cwd, 'Cargo.toml');
|
|
2922
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
2923
|
+
results.push({
|
|
2924
|
+
status: 'ok',
|
|
2925
|
+
message: 'Node.js/JavaScript project detected'
|
|
2926
|
+
});
|
|
2927
|
+
}
|
|
2928
|
+
else if (fs.existsSync(requirementsPath)) {
|
|
2929
|
+
results.push({
|
|
2930
|
+
status: 'ok',
|
|
2931
|
+
message: 'Python project detected'
|
|
2932
|
+
});
|
|
2933
|
+
}
|
|
2934
|
+
else if (fs.existsSync(goModPath)) {
|
|
2935
|
+
results.push({
|
|
2936
|
+
status: 'ok',
|
|
2937
|
+
message: 'Go project detected'
|
|
2938
|
+
});
|
|
2939
|
+
}
|
|
2940
|
+
else if (fs.existsSync(cargoPath)) {
|
|
2941
|
+
results.push({
|
|
2942
|
+
status: 'ok',
|
|
2943
|
+
message: 'Rust project detected'
|
|
2944
|
+
});
|
|
2945
|
+
}
|
|
2946
|
+
else {
|
|
2947
|
+
results.push({
|
|
2948
|
+
status: 'warning',
|
|
2949
|
+
message: 'No standard project files detected',
|
|
2950
|
+
fix: 'FAF works best with package.json, requirements.txt, go.mod, or Cargo.toml'
|
|
2951
|
+
});
|
|
2952
|
+
}
|
|
2953
|
+
// Build output
|
|
2954
|
+
let output = `🏥 FAF Doctor - Health Check\n`;
|
|
2955
|
+
output += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n`;
|
|
2956
|
+
let hasErrors = false;
|
|
2957
|
+
let hasWarnings = false;
|
|
2958
|
+
for (const result of results) {
|
|
2959
|
+
const icon = result.status === 'ok' ? '✅' :
|
|
2960
|
+
result.status === 'warning' ? '⚠️' : '❌';
|
|
2961
|
+
output += `${icon} ${result.message}\n`;
|
|
2962
|
+
if (result.fix) {
|
|
2963
|
+
output += ` 💡 ${result.fix}\n`;
|
|
2964
|
+
}
|
|
2965
|
+
if (result.status === 'error')
|
|
2966
|
+
hasErrors = true;
|
|
2967
|
+
if (result.status === 'warning')
|
|
2968
|
+
hasWarnings = true;
|
|
2969
|
+
}
|
|
2970
|
+
output += `\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n`;
|
|
2971
|
+
if (!hasErrors && !hasWarnings) {
|
|
2972
|
+
output += `🏆 Perfect health! Your FAF setup is championship-ready!`;
|
|
2973
|
+
}
|
|
2974
|
+
else if (!hasErrors) {
|
|
2975
|
+
output += `🎯 Good health with minor improvements suggested.`;
|
|
2976
|
+
}
|
|
2977
|
+
else {
|
|
2978
|
+
output += `⚠️ Issues detected. Follow the fixes above.`;
|
|
2979
|
+
}
|
|
2980
|
+
return { content: [{ type: 'text', text: output }] };
|
|
2981
|
+
}
|
|
2982
|
+
catch (error) {
|
|
2983
|
+
return {
|
|
2984
|
+
content: [{ type: 'text', text: `🏥 FAF Doctor:\n\n❌ Error: ${error.message}` }],
|
|
2985
|
+
isError: true
|
|
2986
|
+
};
|
|
2987
|
+
}
|
|
2988
|
+
}
|
|
2989
|
+
// ============================================================================
|
|
2990
|
+
// v4.5.0 INTEROP HANDLERS
|
|
2991
|
+
// ============================================================================
|
|
2992
|
+
async handleFafAgents(args) {
|
|
2993
|
+
const cwd = this.getProjectPath(args?.path);
|
|
2994
|
+
const action = args?.action || 'sync';
|
|
2995
|
+
try {
|
|
2996
|
+
const result = await this.engineAdapter.callEngine('agents', [
|
|
2997
|
+
cwd,
|
|
2998
|
+
`--action=${action}`,
|
|
2999
|
+
...(args?.force ? ['--force'] : []),
|
|
3000
|
+
...(args?.merge ? ['--merge'] : []),
|
|
3001
|
+
]);
|
|
3002
|
+
if (!result.success) {
|
|
3003
|
+
return {
|
|
3004
|
+
content: [{ type: 'text', text: `AGENTS.md ${action}:\n\n❌ ${result.error}` }],
|
|
3005
|
+
isError: true
|
|
3006
|
+
};
|
|
3007
|
+
}
|
|
3008
|
+
const data = result.data;
|
|
3009
|
+
return {
|
|
3010
|
+
content: [{ type: 'text', text: `AGENTS.md ${action}:\n\n✅ ${data?.message || 'Done'}\n⏱️ ${result.duration}ms` }]
|
|
3011
|
+
};
|
|
3012
|
+
}
|
|
3013
|
+
catch (error) {
|
|
3014
|
+
return {
|
|
3015
|
+
content: [{ type: 'text', text: `AGENTS.md ${action}:\n\n❌ Error: ${error.message}` }],
|
|
3016
|
+
isError: true
|
|
3017
|
+
};
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
async handleFafCursor(args) {
|
|
3021
|
+
const cwd = this.getProjectPath(args?.path);
|
|
3022
|
+
const action = args?.action || 'sync';
|
|
3023
|
+
try {
|
|
3024
|
+
const result = await this.engineAdapter.callEngine('cursor', [
|
|
3025
|
+
cwd,
|
|
3026
|
+
`--action=${action}`,
|
|
3027
|
+
...(args?.force ? ['--force'] : []),
|
|
3028
|
+
...(args?.merge ? ['--merge'] : []),
|
|
3029
|
+
]);
|
|
3030
|
+
if (!result.success) {
|
|
3031
|
+
return {
|
|
3032
|
+
content: [{ type: 'text', text: `.cursorrules ${action}:\n\n❌ ${result.error}` }],
|
|
3033
|
+
isError: true
|
|
3034
|
+
};
|
|
3035
|
+
}
|
|
3036
|
+
const data = result.data;
|
|
3037
|
+
return {
|
|
3038
|
+
content: [{ type: 'text', text: `.cursorrules ${action}:\n\n✅ ${data?.message || 'Done'}\n⏱️ ${result.duration}ms` }]
|
|
3039
|
+
};
|
|
3040
|
+
}
|
|
3041
|
+
catch (error) {
|
|
3042
|
+
return {
|
|
3043
|
+
content: [{ type: 'text', text: `.cursorrules ${action}:\n\n❌ Error: ${error.message}` }],
|
|
3044
|
+
isError: true
|
|
3045
|
+
};
|
|
3046
|
+
}
|
|
3047
|
+
}
|
|
3048
|
+
async handleFafGemini(args) {
|
|
3049
|
+
const cwd = this.getProjectPath(args?.path);
|
|
3050
|
+
const action = args?.action || 'sync';
|
|
3051
|
+
try {
|
|
3052
|
+
const result = await this.engineAdapter.callEngine('gemini', [
|
|
3053
|
+
cwd,
|
|
3054
|
+
`--action=${action}`,
|
|
3055
|
+
...(args?.force ? ['--force'] : []),
|
|
3056
|
+
...(args?.merge ? ['--merge'] : []),
|
|
3057
|
+
]);
|
|
3058
|
+
if (!result.success) {
|
|
3059
|
+
return {
|
|
3060
|
+
content: [{ type: 'text', text: `GEMINI.md ${action}:\n\n❌ ${result.error}` }],
|
|
3061
|
+
isError: true
|
|
3062
|
+
};
|
|
3063
|
+
}
|
|
3064
|
+
const data = result.data;
|
|
3065
|
+
return {
|
|
3066
|
+
content: [{ type: 'text', text: `GEMINI.md ${action}:\n\n✅ ${data?.message || 'Done'}\n⏱️ ${result.duration}ms` }]
|
|
3067
|
+
};
|
|
3068
|
+
}
|
|
3069
|
+
catch (error) {
|
|
3070
|
+
return {
|
|
3071
|
+
content: [{ type: 'text', text: `GEMINI.md ${action}:\n\n❌ Error: ${error.message}` }],
|
|
3072
|
+
isError: true
|
|
3073
|
+
};
|
|
3074
|
+
}
|
|
3075
|
+
}
|
|
3076
|
+
async handleFafConductor(args) {
|
|
3077
|
+
const cwd = this.getProjectPath(args?.path);
|
|
3078
|
+
const action = args?.action || 'import';
|
|
3079
|
+
try {
|
|
3080
|
+
const result = await this.engineAdapter.callEngine('conductor', [
|
|
3081
|
+
cwd,
|
|
3082
|
+
`--action=${action}`,
|
|
3083
|
+
...(args?.force ? ['--force'] : []),
|
|
3084
|
+
...(args?.merge ? ['--merge'] : []),
|
|
3085
|
+
]);
|
|
3086
|
+
if (!result.success) {
|
|
3087
|
+
return {
|
|
3088
|
+
content: [{ type: 'text', text: `Conductor ${action}:\n\n❌ ${result.error}` }],
|
|
3089
|
+
isError: true
|
|
3090
|
+
};
|
|
3091
|
+
}
|
|
3092
|
+
const data = result.data;
|
|
3093
|
+
return {
|
|
3094
|
+
content: [{ type: 'text', text: `Conductor ${action}:\n\n✅ ${data?.message || 'Done'}\n⏱️ ${result.duration}ms` }]
|
|
3095
|
+
};
|
|
3096
|
+
}
|
|
3097
|
+
catch (error) {
|
|
3098
|
+
return {
|
|
3099
|
+
content: [{ type: 'text', text: `Conductor ${action}:\n\n❌ Error: ${error.message}` }],
|
|
3100
|
+
isError: true
|
|
3101
|
+
};
|
|
3102
|
+
}
|
|
3103
|
+
}
|
|
3104
|
+
async handleFafGit(args) {
|
|
3105
|
+
const url = args?.url;
|
|
3106
|
+
if (!url) {
|
|
3107
|
+
return {
|
|
3108
|
+
content: [{ type: 'text', text: 'faf_git: Missing required parameter "url"' }],
|
|
3109
|
+
isError: true
|
|
3110
|
+
};
|
|
3111
|
+
}
|
|
3112
|
+
const outputPath = args?.path ? this.getProjectPath(args.path) : undefined;
|
|
3113
|
+
try {
|
|
3114
|
+
const result = await this.engineAdapter.callEngine('git', [
|
|
3115
|
+
url,
|
|
3116
|
+
...(outputPath ? [outputPath] : []),
|
|
3117
|
+
]);
|
|
3118
|
+
if (!result.success) {
|
|
3119
|
+
return {
|
|
3120
|
+
content: [{ type: 'text', text: `GitHub Context:\n\n❌ ${result.error}` }],
|
|
3121
|
+
isError: true
|
|
3122
|
+
};
|
|
3123
|
+
}
|
|
3124
|
+
const data = result.data;
|
|
3125
|
+
let output = `GitHub Context:\n\n✅ ${data?.message || 'Done'}\n⏱️ ${result.duration}ms`;
|
|
3126
|
+
// Include generated .faf content if no output path (preview mode)
|
|
3127
|
+
if (!outputPath && data?.data?.fafContent) {
|
|
3128
|
+
output += `\n\n--- Generated project.faf ---\n${data.data.fafContent}`;
|
|
3129
|
+
}
|
|
3130
|
+
return {
|
|
3131
|
+
content: [{ type: 'text', text: output }]
|
|
3132
|
+
};
|
|
3133
|
+
}
|
|
3134
|
+
catch (error) {
|
|
3135
|
+
return {
|
|
3136
|
+
content: [{ type: 'text', text: `GitHub Context:\n\n❌ Error: ${error.message}` }],
|
|
3137
|
+
isError: true
|
|
3138
|
+
};
|
|
3139
|
+
}
|
|
3140
|
+
}
|
|
1140
3141
|
}
|
|
1141
3142
|
exports.FafToolHandler = FafToolHandler;
|
|
1142
3143
|
//# sourceMappingURL=tools.js.map
|