faf-mcp 2.3.1 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +49 -4
- package/CLAUDE.md +10 -6
- package/README.md +95 -86
- package/dist/src/faf-core/commands/agents.d.ts +2 -2
- package/dist/src/faf-core/commands/agents.js +2 -2
- package/dist/src/faf-core/commands/claude.d.ts +31 -0
- package/dist/src/faf-core/commands/{bi-sync.js → claude.js} +39 -77
- package/dist/src/faf-core/commands/claude.js.map +1 -0
- package/dist/src/faf-core/commands/conductor.d.ts +1 -1
- package/dist/src/faf-core/commands/conductor.js +1 -1
- package/dist/src/faf-core/commands/cursor.d.ts +2 -2
- package/dist/src/faf-core/commands/cursor.js +2 -2
- package/dist/src/faf-core/commands/gemini.d.ts +2 -2
- package/dist/src/faf-core/commands/gemini.js +2 -2
- package/dist/src/faf-core/commands/git-context.d.ts +1 -1
- package/dist/src/faf-core/commands/git-context.js +2 -2
- package/dist/src/faf-core/commands/git-context.js.map +1 -1
- package/dist/src/faf-core/commands/sync.js.map +1 -1
- package/dist/src/faf-core/engines/dependency-tsa.js.map +1 -1
- package/dist/src/faf-core/fix-once/colors.js +1 -1
- package/dist/src/faf-core/fix-once/colors.js.map +1 -1
- package/dist/src/faf-core/parsers/agents-parser.d.ts +17 -1
- package/dist/src/faf-core/parsers/agents-parser.js +31 -124
- package/dist/src/faf-core/parsers/agents-parser.js.map +1 -1
- package/dist/src/faf-core/parsers/conductor-parser.d.ts +1 -1
- package/dist/src/faf-core/parsers/conductor-parser.js +2 -2
- package/dist/src/faf-core/parsers/conductor-parser.js.map +1 -1
- package/dist/src/faf-core/parsers/cursorrules-parser.d.ts +17 -1
- package/dist/src/faf-core/parsers/cursorrules-parser.js +56 -112
- package/dist/src/faf-core/parsers/cursorrules-parser.js.map +1 -1
- package/dist/src/faf-core/parsers/faf-git-generator.d.ts +1 -1
- package/dist/src/faf-core/parsers/faf-git-generator.js +6 -6
- package/dist/src/faf-core/parsers/faf-git-generator.js.map +1 -1
- package/dist/src/faf-core/parsers/gemini-parser.d.ts +12 -1
- package/dist/src/faf-core/parsers/gemini-parser.js +21 -78
- package/dist/src/faf-core/parsers/gemini-parser.js.map +1 -1
- package/dist/src/faf-core/parsers/github-extractor.d.ts +1 -1
- package/dist/src/faf-core/parsers/github-extractor.js +1 -1
- package/dist/src/faf-core/parsers/slot-counter.d.ts +1 -1
- package/dist/src/faf-core/parsers/slot-counter.js +1 -1
- package/dist/src/handlers/engine-adapter.d.ts +13 -12
- package/dist/src/handlers/engine-adapter.js +105 -683
- package/dist/src/handlers/engine-adapter.js.map +1 -1
- package/dist/src/handlers/resources.d.ts +19 -1
- package/dist/src/handlers/resources.js +108 -23
- package/dist/src/handlers/resources.js.map +1 -1
- package/dist/src/handlers/skills.d.ts +70 -0
- package/dist/src/handlers/skills.js +174 -0
- package/dist/src/handlers/skills.js.map +1 -0
- package/dist/src/handlers/tools.d.ts +43 -7
- package/dist/src/handlers/tools.js +300 -375
- package/dist/src/handlers/tools.js.map +1 -1
- package/dist/src/server.d.ts +4 -0
- package/dist/src/server.js +51 -6
- package/dist/src/server.js.map +1 -1
- package/dist/src/utils/faf-cli-bridge.d.ts +14 -8
- package/dist/src/utils/faf-cli-bridge.js +28 -8
- package/dist/src/utils/faf-cli-bridge.js.map +1 -1
- package/package.json +18 -15
- package/project.faf +14 -9
- package/scripts/check-engines.mjs +53 -0
- package/scripts/sync-version.js +33 -19
- package/skills/faf-ide/SKILL.md +40 -0
- package/dist/src/config/visibility.d.ts +0 -41
- package/dist/src/config/visibility.js +0 -158
- package/dist/src/config/visibility.js.map +0 -1
- package/dist/src/faf-core/commands/audit.d.ts +0 -21
- package/dist/src/faf-core/commands/audit.js +0 -83
- package/dist/src/faf-core/commands/audit.js.map +0 -1
- package/dist/src/faf-core/commands/auto.d.ts +0 -25
- package/dist/src/faf-core/commands/auto.js +0 -74
- package/dist/src/faf-core/commands/auto.js.map +0 -1
- package/dist/src/faf-core/commands/bi-sync.d.ts +0 -31
- package/dist/src/faf-core/commands/bi-sync.js.map +0 -1
- package/dist/src/faf-core/commands/doctor.d.ts +0 -17
- package/dist/src/faf-core/commands/doctor.js +0 -198
- package/dist/src/faf-core/commands/doctor.js.map +0 -1
- package/dist/src/faf-core/commands/enhance.d.ts +0 -46
- package/dist/src/faf-core/commands/enhance.js +0 -360
- package/dist/src/faf-core/commands/enhance.js.map +0 -1
- package/dist/src/faf-core/commands/formats.d.ts +0 -22
- package/dist/src/faf-core/commands/formats.js +0 -117
- package/dist/src/faf-core/commands/formats.js.map +0 -1
- package/dist/src/faf-core/commands/human.d.ts +0 -27
- package/dist/src/faf-core/commands/human.js +0 -183
- package/dist/src/faf-core/commands/human.js.map +0 -1
- package/dist/src/faf-core/commands/init.d.ts +0 -26
- package/dist/src/faf-core/commands/init.js +0 -114
- package/dist/src/faf-core/commands/init.js.map +0 -1
- package/dist/src/faf-core/commands/innit.d.ts +0 -7
- package/dist/src/faf-core/commands/innit.js +0 -13
- package/dist/src/faf-core/commands/innit.js.map +0 -1
- package/dist/src/faf-core/commands/migrate.d.ts +0 -15
- package/dist/src/faf-core/commands/migrate.js +0 -86
- package/dist/src/faf-core/commands/migrate.js.map +0 -1
- package/dist/src/faf-core/commands/quick.d.ts +0 -16
- package/dist/src/faf-core/commands/quick.js +0 -184
- package/dist/src/faf-core/commands/quick.js.map +0 -1
- package/dist/src/faf-core/commands/readme.d.ts +0 -47
- package/dist/src/faf-core/commands/readme.js +0 -361
- package/dist/src/faf-core/commands/readme.js.map +0 -1
- package/dist/src/faf-core/commands/score.d.ts +0 -47
- package/dist/src/faf-core/commands/score.js +0 -49
- package/dist/src/faf-core/commands/score.js.map +0 -1
- package/dist/src/faf-core/commands/update.d.ts +0 -12
- package/dist/src/faf-core/commands/update.js +0 -46
- package/dist/src/faf-core/commands/update.js.map +0 -1
- package/dist/src/faf-core/commands/validate.d.ts +0 -21
- package/dist/src/faf-core/commands/validate.js +0 -81
- package/dist/src/faf-core/commands/validate.js.map +0 -1
- package/dist/src/faf-core/compiler/faf-compiler.d.ts +0 -138
- package/dist/src/faf-core/compiler/faf-compiler.js +0 -1362
- package/dist/src/faf-core/compiler/faf-compiler.js.map +0 -1
- package/dist/src/faf-core/engines/fab-formats-processor.d.ts +0 -166
- package/dist/src/faf-core/engines/fab-formats-processor.js +0 -1274
- package/dist/src/faf-core/engines/fab-formats-processor.js.map +0 -1
- package/dist/src/faf-core/engines/faf-dna.d.ts +0 -159
- package/dist/src/faf-core/engines/faf-dna.js +0 -555
- package/dist/src/faf-core/engines/faf-dna.js.map +0 -1
- package/dist/src/faf-core/engines/relentless-context-extractor.d.ts +0 -100
- package/dist/src/faf-core/engines/relentless-context-extractor.js +0 -625
- package/dist/src/faf-core/engines/relentless-context-extractor.js.map +0 -1
- package/dist/src/faf-core/fix-once/types.d.ts +0 -257
- package/dist/src/faf-core/fix-once/types.js +0 -26
- package/dist/src/faf-core/fix-once/types.js.map +0 -1
- package/dist/src/faf-core/generators/faf-generator-championship.d.ts +0 -16
- package/dist/src/faf-core/generators/faf-generator-championship.js +0 -463
- package/dist/src/faf-core/generators/faf-generator-championship.js.map +0 -1
- package/dist/src/faf-core/inject.d.ts +0 -19
- package/dist/src/faf-core/inject.js +0 -57
- package/dist/src/faf-core/inject.js.map +0 -1
- package/dist/src/faf-core/parsers/index.d.ts +0 -12
- package/dist/src/faf-core/parsers/index.js +0 -29
- package/dist/src/faf-core/parsers/index.js.map +0 -1
- package/dist/src/faf-core/utils/balance-visualizer.d.ts +0 -37
- package/dist/src/faf-core/utils/balance-visualizer.js +0 -197
- package/dist/src/faf-core/utils/balance-visualizer.js.map +0 -1
- package/dist/src/faf-core/utils/championship-style.d.ts +0 -109
- package/dist/src/faf-core/utils/championship-style.js +0 -219
- package/dist/src/faf-core/utils/championship-style.js.map +0 -1
- package/dist/src/faf-core/utils/chrome-extension-detector.d.ts +0 -73
- package/dist/src/faf-core/utils/chrome-extension-detector.js +0 -268
- package/dist/src/faf-core/utils/chrome-extension-detector.js.map +0 -1
- package/dist/src/faf-core/utils/platform-detector.d.ts +0 -30
- package/dist/src/faf-core/utils/platform-detector.js +0 -218
- package/dist/src/faf-core/utils/platform-detector.js.map +0 -1
- package/dist/src/faf-core/utils/technical-credit.d.ts +0 -35
- package/dist/src/faf-core/utils/technical-credit.js +0 -286
- package/dist/src/faf-core/utils/technical-credit.js.map +0 -1
- package/dist/src/faf-core/utils/yaml-generator.d.ts +0 -41
- package/dist/src/faf-core/utils/yaml-generator.js +0 -360
- package/dist/src/faf-core/utils/yaml-generator.js.map +0 -1
- package/dist/src/handlers/behavioral-instruction.d.ts +0 -16
- package/dist/src/handlers/behavioral-instruction.js +0 -43
- package/dist/src/handlers/behavioral-instruction.js.map +0 -1
- package/dist/src/handlers/championship-tools.d.ts +0 -116
- package/dist/src/handlers/championship-tools.js +0 -2320
- package/dist/src/handlers/championship-tools.js.map +0 -1
- package/dist/src/handlers/cloud-handler.d.ts +0 -59
- package/dist/src/handlers/cloud-handler.js +0 -348
- package/dist/src/handlers/cloud-handler.js.map +0 -1
- package/dist/src/handlers/tool-registry.d.ts +0 -23
- package/dist/src/handlers/tool-registry.js +0 -68
- package/dist/src/handlers/tool-registry.js.map +0 -1
- package/dist/src/handlers/tool-types.d.ts +0 -167
- package/dist/src/handlers/tool-types.js +0 -7
- package/dist/src/handlers/tool-types.js.map +0 -1
- package/dist/src/test-all-functions.d.ts +0 -15
- package/dist/src/test-all-functions.js +0 -163
- package/dist/src/test-all-functions.js.map +0 -1
- package/dist/src/types/mcp-tools.d.ts +0 -53
- package/dist/src/types/mcp-tools.js +0 -77
- package/dist/src/types/mcp-tools.js.map +0 -1
- package/dist/src/types/tool-visibility.d.ts +0 -36
- package/dist/src/types/tool-visibility.js +0 -551
- package/dist/src/types/tool-visibility.js.map +0 -1
- package/dist/src/utils/auto-path-detection.d.ts +0 -26
- package/dist/src/utils/auto-path-detection.js +0 -198
- package/dist/src/utils/auto-path-detection.js.map +0 -1
- package/dist/src/utils/championship-format.d.ts +0 -30
- package/dist/src/utils/championship-format.js +0 -81
- package/dist/src/utils/championship-format.js.map +0 -1
- package/dist/src/utils/cli-detector.d.ts +0 -17
- package/dist/src/utils/cli-detector.js +0 -247
- package/dist/src/utils/cli-detector.js.map +0 -1
- package/dist/src/utils/display-protocol.d.ts +0 -57
- package/dist/src/utils/display-protocol.js +0 -131
- package/dist/src/utils/display-protocol.js.map +0 -1
- package/dist/src/utils/username-detector.d.ts +0 -27
- package/dist/src/utils/username-detector.js +0 -90
- package/dist/src/utils/username-detector.js.map +0 -1
- package/dist/src/utils/visual-style.d.ts +0 -62
- package/dist/src/utils/visual-style.js +0 -166
- package/dist/src/utils/visual-style.js.map +0 -1
- package/scripts/postinstall.js +0 -50
- package/skill/SKILL.md +0 -385
|
@@ -38,7 +38,6 @@ const fileHandler_1 = require("./fileHandler");
|
|
|
38
38
|
const fs = __importStar(require("fs"));
|
|
39
39
|
const os = __importStar(require("os"));
|
|
40
40
|
const pathModule = __importStar(require("path"));
|
|
41
|
-
const fuzzy_detector_1 = require("../utils/fuzzy-detector");
|
|
42
41
|
const faf_file_finder_js_1 = require("../utils/faf-file-finder.js");
|
|
43
42
|
const safe_path_1 = require("../utils/safe-path");
|
|
44
43
|
const version_1 = require("../version");
|
|
@@ -49,12 +48,12 @@ const path_resolver_1 = require("../utils/path-resolver");
|
|
|
49
48
|
// scoring or the tier ladder here — that's exactly the drift v2.1.0 set out
|
|
50
49
|
// to kill across the surface; v2.1.1 closes the loop on the active handler.
|
|
51
50
|
//
|
|
52
|
-
// Imported via ../utils/faf-cli-bridge.js — a
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
//
|
|
51
|
+
// Imported via ../utils/faf-cli-bridge.js — a one-file re-export that pins
|
|
52
|
+
// the relative dist path. It began as a workaround for faf-cli 6.7.1's `bun`
|
|
53
|
+
// exports condition (pointed at a non-shipped src/); 6.8.0 dropped it and
|
|
54
|
+
// 7.12.0 exports only `types`/`default`. Node and Bun load the same compiled
|
|
55
|
+
// module through the bridge, which stays for the CJS-to-ESM load and
|
|
56
|
+
// bundledFafCliVersion()'s path-walk. See the bridge header for the rationale.
|
|
58
57
|
const faf_cli_bridge_js_1 = require("../utils/faf-cli-bridge.js");
|
|
59
58
|
const turbocat_bridge_js_1 = require("../faf-core/extract/turbocat-bridge.js");
|
|
60
59
|
/**
|
|
@@ -66,8 +65,8 @@ const turbocat_bridge_js_1 = require("../faf-core/extract/turbocat-bridge.js");
|
|
|
66
65
|
* the 40%-MIN rule means one weak tool on the surface caps the whole server.
|
|
67
66
|
*
|
|
68
67
|
* Spine mirrors claude-faf-mcp's proven-A Core; the interop quad
|
|
69
|
-
* (
|
|
70
|
-
* the Cursor / IDE multi-format
|
|
68
|
+
* (faf_claude + faf_agents/faf_cursor/faf_gemini) is this edition's identity —
|
|
69
|
+
* the Cursor / IDE multi-format write — where CFM carries memory tools instead.
|
|
71
70
|
* See PLANET-FAF/strategy/claude-faf-mcp-core-tier-glama-a-2026-06-17.md.
|
|
72
71
|
*/
|
|
73
72
|
const CORE_TOOLS = new Set([
|
|
@@ -75,7 +74,7 @@ const CORE_TOOLS = new Set([
|
|
|
75
74
|
'faf_init', 'faf_auto', 'faf_go', 'faf_score', 'faf_doctor',
|
|
76
75
|
'faf_sync', 'faf_context', 'faf_trust', 'faf_about',
|
|
77
76
|
// IDE-edition interop — the differentiator
|
|
78
|
-
'
|
|
77
|
+
'faf_claude', 'faf_agents', 'faf_cursor', 'faf_gemini',
|
|
79
78
|
// Craft-grade utility
|
|
80
79
|
'faf_check', 'faf_git',
|
|
81
80
|
]);
|
|
@@ -206,9 +205,9 @@ class FafToolHandler {
|
|
|
206
205
|
},
|
|
207
206
|
{
|
|
208
207
|
name: 'faf_sync',
|
|
209
|
-
description: '
|
|
208
|
+
description: 'Reconcile project.faf with what the repo manifests say now (package.json name, description, framework dependencies). Dry-run by default: reports the fields that would change. Pass apply:true to write them into project.faf. Does not touch CLAUDE.md — use faf_claude for CLAUDE.md, AGENTS.md, .cursorrules and GEMINI.md.',
|
|
210
209
|
annotations: {
|
|
211
|
-
title: '
|
|
210
|
+
title: 'Reconcile .faf with repo manifests',
|
|
212
211
|
readOnlyHint: false,
|
|
213
212
|
destructiveHint: false,
|
|
214
213
|
openWorldHint: false
|
|
@@ -216,16 +215,17 @@ class FafToolHandler {
|
|
|
216
215
|
inputSchema: {
|
|
217
216
|
type: 'object',
|
|
218
217
|
properties: {
|
|
218
|
+
apply: { type: 'boolean', description: 'Write the detected changes into project.faf (default: dry-run report only)' },
|
|
219
219
|
path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
|
|
220
220
|
},
|
|
221
221
|
additionalProperties: false
|
|
222
222
|
}
|
|
223
223
|
},
|
|
224
224
|
{
|
|
225
|
-
name: '
|
|
226
|
-
description: '
|
|
225
|
+
name: 'faf_claude',
|
|
226
|
+
description: 'Write CLAUDE.md from project.faf (one direction: .faf → CLAUDE.md) and, with the format flags or all, also AGENTS.md, .cursorrules and GEMINI.md. Content outside the faf-managed block is preserved. Returns the files written. Use this to keep every AI tool\'s context file current from one source. (Named faf_bi_sync before 3.0.1; that name still works.)',
|
|
227
227
|
annotations: {
|
|
228
|
-
title: '
|
|
228
|
+
title: 'Write CLAUDE.md and IDE formats from .faf',
|
|
229
229
|
readOnlyHint: false,
|
|
230
230
|
destructiveHint: false,
|
|
231
231
|
openWorldHint: false
|
|
@@ -233,13 +233,10 @@ class FafToolHandler {
|
|
|
233
233
|
inputSchema: {
|
|
234
234
|
type: 'object',
|
|
235
235
|
properties: {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
cursor: { type: 'boolean', description: 'Also sync to .cursorrules (Cursor IDE format)' },
|
|
241
|
-
gemini: { type: 'boolean', description: 'Also sync to GEMINI.md (Google Gemini format)' },
|
|
242
|
-
all: { type: 'boolean', description: 'Sync to ALL formats: CLAUDE.md + AGENTS.md + .cursorrules + GEMINI.md' },
|
|
236
|
+
agents: { type: 'boolean', description: 'Also write AGENTS.md (OpenAI/Codex format)' },
|
|
237
|
+
cursor: { type: 'boolean', description: 'Also write .cursorrules (Cursor IDE format)' },
|
|
238
|
+
gemini: { type: 'boolean', description: 'Also write GEMINI.md (Google Gemini format)' },
|
|
239
|
+
all: { type: 'boolean', description: 'Write ALL formats: CLAUDE.md + AGENTS.md + .cursorrules + GEMINI.md' },
|
|
243
240
|
path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
|
|
244
241
|
},
|
|
245
242
|
additionalProperties: false
|
|
@@ -247,7 +244,7 @@ class FafToolHandler {
|
|
|
247
244
|
},
|
|
248
245
|
{
|
|
249
246
|
name: 'faf_clear',
|
|
250
|
-
description: '
|
|
247
|
+
description: 'Remove the ~/.faf-cli-cache directory left by older faf-mcp versions. This version persists nothing outside your project files.',
|
|
251
248
|
annotations: {
|
|
252
249
|
title: 'Clear .faf Data',
|
|
253
250
|
readOnlyHint: false,
|
|
@@ -257,17 +254,15 @@ class FafToolHandler {
|
|
|
257
254
|
inputSchema: {
|
|
258
255
|
type: 'object',
|
|
259
256
|
properties: {
|
|
260
|
-
cache: { type: 'boolean', description: '
|
|
261
|
-
|
|
262
|
-
backups: { type: 'boolean', description: 'Clear backup files only' },
|
|
263
|
-
all: { type: 'boolean', description: 'Clear everything (default)' }
|
|
257
|
+
cache: { type: 'boolean', description: 'Remove ~/.faf-cli-cache (written by faf-mcp < 3.0)' },
|
|
258
|
+
all: { type: 'boolean', description: 'Remove everything older versions persisted (default; today that is the cache)' }
|
|
264
259
|
},
|
|
265
260
|
additionalProperties: false
|
|
266
261
|
}
|
|
267
262
|
},
|
|
268
263
|
{
|
|
269
264
|
name: 'faf_debug',
|
|
270
|
-
description: 'Debug faf-mcp environment - show working directory, permissions, and
|
|
265
|
+
description: 'Debug faf-mcp environment - show working directory, write permissions, the bundled faf-cli version, and whether a project.faf is present',
|
|
271
266
|
annotations: {
|
|
272
267
|
title: 'Debug Info',
|
|
273
268
|
readOnlyHint: true,
|
|
@@ -476,7 +471,7 @@ class FafToolHandler {
|
|
|
476
471
|
},
|
|
477
472
|
{
|
|
478
473
|
name: 'faf_auto',
|
|
479
|
-
description: 'Run the full setup pipeline in one call — init, stack detection, sync,
|
|
474
|
+
description: 'Run the full setup pipeline in one call — init, stack detection, sync, CLAUDE.md, and score — taking a project from no context to a complete project.faf. Returns the final AI-readiness score and what was created. Use this as the fast path on a fresh project; use the individual tools when you need finer control.',
|
|
480
475
|
annotations: {
|
|
481
476
|
title: 'Auto-detect Context',
|
|
482
477
|
readOnlyHint: false,
|
|
@@ -486,15 +481,17 @@ class FafToolHandler {
|
|
|
486
481
|
inputSchema: {
|
|
487
482
|
type: 'object',
|
|
488
483
|
properties: {
|
|
489
|
-
path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
|
|
490
|
-
force
|
|
484
|
+
path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
|
|
485
|
+
// `force` used to be declared here; handleFafAuto never read it.
|
|
486
|
+
// faf_auto merges into an existing project.faf non-destructively
|
|
487
|
+
// and there is no overwrite mode to switch on.
|
|
491
488
|
},
|
|
492
489
|
additionalProperties: false
|
|
493
490
|
}
|
|
494
491
|
},
|
|
495
492
|
{
|
|
496
493
|
name: 'faf_dna',
|
|
497
|
-
description: 'Show your FAF DNA journey
|
|
494
|
+
description: 'Show your FAF DNA journey — the score evolution of this project.faf from birth to today (e.g. 22% → 85% → 100%)',
|
|
498
495
|
annotations: {
|
|
499
496
|
title: 'View Project DNA',
|
|
500
497
|
readOnlyHint: true,
|
|
@@ -562,11 +559,11 @@ class FafToolHandler {
|
|
|
562
559
|
}
|
|
563
560
|
},
|
|
564
561
|
// ============================================================================
|
|
565
|
-
//
|
|
562
|
+
// INTEROP TOOLS — landed in faf-mcp 2.0.0 (The Interop MCP for Context); ported from faf-cli 4.5.0 via claude-faf-mcp 4.5.0
|
|
566
563
|
// ============================================================================
|
|
567
564
|
{
|
|
568
565
|
name: 'faf_agents',
|
|
569
|
-
description: 'Import
|
|
566
|
+
description: 'Import AGENTS.md (the OpenAI/Codex convention) into project.faf, or write it from project.faf. Returns the merged or written result for the chosen action. Use this to keep a Codex/OpenAI-style AGENTS.md and your .faf in agreement from one source.',
|
|
570
567
|
annotations: {
|
|
571
568
|
title: 'Sync AGENTS.md',
|
|
572
569
|
readOnlyHint: false,
|
|
@@ -576,7 +573,7 @@ class FafToolHandler {
|
|
|
576
573
|
inputSchema: {
|
|
577
574
|
type: 'object',
|
|
578
575
|
properties: {
|
|
579
|
-
action: { type: 'string', enum: ['import', 'export', 'sync'], description: 'Action: import (AGENTS.md -> .faf), export (.faf -> AGENTS.md), sync (
|
|
576
|
+
action: { type: 'string', enum: ['import', 'export', 'sync'], description: 'Action: import (AGENTS.md -> .faf; merge:true writes it into project.faf), export (.faf -> AGENTS.md), sync (re-writes AGENTS.md from .faf; export with force)' },
|
|
580
577
|
force: { type: 'boolean', description: 'Force overwrite existing files' },
|
|
581
578
|
merge: { type: 'boolean', description: 'Merge imported data with existing .faf instead of replacing' },
|
|
582
579
|
path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
|
|
@@ -587,7 +584,7 @@ class FafToolHandler {
|
|
|
587
584
|
},
|
|
588
585
|
{
|
|
589
586
|
name: 'faf_cursor',
|
|
590
|
-
description: 'Import
|
|
587
|
+
description: 'Import .cursorrules (the Cursor IDE convention) into project.faf, or write it from project.faf. Returns the merged or written result for the chosen action. Use this to keep Cursor rules and your .faf in agreement from one source.',
|
|
591
588
|
annotations: {
|
|
592
589
|
title: 'Sync .cursorrules',
|
|
593
590
|
readOnlyHint: false,
|
|
@@ -597,7 +594,7 @@ class FafToolHandler {
|
|
|
597
594
|
inputSchema: {
|
|
598
595
|
type: 'object',
|
|
599
596
|
properties: {
|
|
600
|
-
action: { type: 'string', enum: ['import', 'export', 'sync'], description: 'Action: import (.cursorrules -> .faf), export (.faf -> .cursorrules), sync (
|
|
597
|
+
action: { type: 'string', enum: ['import', 'export', 'sync'], description: 'Action: import (.cursorrules -> .faf; merge:true writes it into project.faf), export (.faf -> .cursorrules), sync (re-writes .cursorrules from .faf; export with force)' },
|
|
601
598
|
force: { type: 'boolean', description: 'Force overwrite existing files' },
|
|
602
599
|
merge: { type: 'boolean', description: 'Merge imported data with existing .faf instead of replacing' },
|
|
603
600
|
path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
|
|
@@ -608,7 +605,7 @@ class FafToolHandler {
|
|
|
608
605
|
},
|
|
609
606
|
{
|
|
610
607
|
name: 'faf_gemini',
|
|
611
|
-
description: 'Import
|
|
608
|
+
description: 'Import GEMINI.md (the Google Gemini CLI convention) into project.faf, or write it from project.faf. Returns the merged or written result for the chosen action. Use this to keep a Gemini context file and your .faf in agreement from one source.',
|
|
612
609
|
annotations: {
|
|
613
610
|
title: 'Sync GEMINI.md',
|
|
614
611
|
readOnlyHint: false,
|
|
@@ -618,7 +615,7 @@ class FafToolHandler {
|
|
|
618
615
|
inputSchema: {
|
|
619
616
|
type: 'object',
|
|
620
617
|
properties: {
|
|
621
|
-
action: { type: 'string', enum: ['import', 'export', 'sync'], description: 'Action: import (GEMINI.md -> .faf), export (.faf -> GEMINI.md), sync (
|
|
618
|
+
action: { type: 'string', enum: ['import', 'export', 'sync'], description: 'Action: import (GEMINI.md -> .faf; merge:true writes it into project.faf), export (.faf -> GEMINI.md), sync (re-writes GEMINI.md from .faf; export with force)' },
|
|
622
619
|
force: { type: 'boolean', description: 'Force overwrite existing files' },
|
|
623
620
|
merge: { type: 'boolean', description: 'Merge imported data with existing .faf instead of replacing' },
|
|
624
621
|
path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
|
|
@@ -650,7 +647,7 @@ class FafToolHandler {
|
|
|
650
647
|
},
|
|
651
648
|
{
|
|
652
649
|
name: 'faf_git',
|
|
653
|
-
description: '
|
|
650
|
+
description: 'Author a project.faf from any public GitHub repository URL — fetches the repo and extracts its stack and context. Returns the authored .faf. Use this to bootstrap context for a project you have the URL for but not cloned locally.',
|
|
654
651
|
annotations: {
|
|
655
652
|
title: 'Extract from GitHub',
|
|
656
653
|
readOnlyHint: false,
|
|
@@ -661,7 +658,7 @@ class FafToolHandler {
|
|
|
661
658
|
type: 'object',
|
|
662
659
|
properties: {
|
|
663
660
|
url: { type: 'string', description: 'GitHub repository URL (e.g., https://github.com/owner/repo or owner/repo)' },
|
|
664
|
-
path: { type: 'string', description: 'Output directory for
|
|
661
|
+
path: { type: 'string', description: 'Output directory for the authored project.faf. If omitted, returns content without writing.' }
|
|
665
662
|
},
|
|
666
663
|
required: ['url'],
|
|
667
664
|
additionalProperties: false
|
|
@@ -691,8 +688,9 @@ class FafToolHandler {
|
|
|
691
688
|
return await this.handleFafTrust(args);
|
|
692
689
|
case 'faf_sync':
|
|
693
690
|
return await this.handleFafSync(args);
|
|
694
|
-
case 'faf_bi_sync':
|
|
695
|
-
|
|
691
|
+
case 'faf_bi_sync': // pre-3.0.1 name: still callable, never listed
|
|
692
|
+
case 'faf_claude':
|
|
693
|
+
return await this.handleFafClaude(args);
|
|
696
694
|
case 'faf_clear':
|
|
697
695
|
return await this.handleFafClear(args);
|
|
698
696
|
case 'faf_debug':
|
|
@@ -738,7 +736,7 @@ class FafToolHandler {
|
|
|
738
736
|
return await this.handleFafQuick(args);
|
|
739
737
|
case 'faf_doctor':
|
|
740
738
|
return await this.handleFafDoctor(args);
|
|
741
|
-
//
|
|
739
|
+
// Interop tools (faf-mcp 2.0.0; ported from faf-cli 4.5.0 via claude-faf-mcp 4.5.0)
|
|
742
740
|
case 'faf_agents':
|
|
743
741
|
return await this.handleFafAgents(args);
|
|
744
742
|
case 'faf_cursor':
|
|
@@ -771,7 +769,7 @@ class FafToolHandler {
|
|
|
771
769
|
return {
|
|
772
770
|
content: [{
|
|
773
771
|
type: 'text',
|
|
774
|
-
text: `🤖
|
|
772
|
+
text: `🤖 FAF Project Status:\n\n❌ No FAF file found in ${cwd}\n💡 Run faf_init to create project.faf`
|
|
775
773
|
}]
|
|
776
774
|
};
|
|
777
775
|
}
|
|
@@ -780,7 +778,7 @@ class FafToolHandler {
|
|
|
780
778
|
return {
|
|
781
779
|
content: [{
|
|
782
780
|
type: 'text',
|
|
783
|
-
text: `🤖
|
|
781
|
+
text: `🤖 FAF Project Status:\n\n✅ ${fafResult.filename} found in ${cwd}\n\nContent preview:\n${lines.join('\n')}`
|
|
784
782
|
}]
|
|
785
783
|
};
|
|
786
784
|
}
|
|
@@ -788,7 +786,7 @@ class FafToolHandler {
|
|
|
788
786
|
return {
|
|
789
787
|
content: [{
|
|
790
788
|
type: 'text',
|
|
791
|
-
text: `🤖
|
|
789
|
+
text: `🤖 FAF Project Status:\n\n❌ Error: ${error.message}`
|
|
792
790
|
}],
|
|
793
791
|
isError: true
|
|
794
792
|
};
|
|
@@ -851,7 +849,7 @@ class FafToolHandler {
|
|
|
851
849
|
text: `FAF SCORE: 0/100 (0%) ○ INVALID\n\n` +
|
|
852
850
|
`\`${fafPath}\` couldn't be parsed as a valid .faf YAML:\n` +
|
|
853
851
|
` ${error?.message ?? String(error)}\n\n` +
|
|
854
|
-
`Re-run \`faf_init\` to
|
|
852
|
+
`Re-run \`faf_init\` with force: true to write a fresh, valid file.`,
|
|
855
853
|
},
|
|
856
854
|
],
|
|
857
855
|
isError: true,
|
|
@@ -868,7 +866,7 @@ class FafToolHandler {
|
|
|
868
866
|
// Headline carries both `/100` AND `(%)` so multiple matchers stay happy.
|
|
869
867
|
let output = `FAF SCORE: ${score}/100 (${score}%) ${tierDisplay}\n` +
|
|
870
868
|
`${progressBar} ${score}%\n` +
|
|
871
|
-
`${result.populated}/${result.
|
|
869
|
+
`${result.populated}/${result.active} slots populated` +
|
|
872
870
|
(nextTierDisplay ? ` · next: ${nextTierDisplay}` : ' · top tier') +
|
|
873
871
|
`\n\n` +
|
|
874
872
|
`Scored by faf-cli — the same context your AI reads.`;
|
|
@@ -900,13 +898,19 @@ class FafToolHandler {
|
|
|
900
898
|
};
|
|
901
899
|
}
|
|
902
900
|
async handleFafInit(args) {
|
|
903
|
-
//
|
|
901
|
+
// faf_init is `faf init`: the same exported faf-cli steps the CLI command
|
|
902
|
+
// runs (assembleFreshFaf detects the folder → writeFaf → readFafRaw →
|
|
903
|
+
// scoreFafYaml), on the folder the path resolves to. Until 3.0.1 this
|
|
904
|
+
// wrote its own legacy template (no format version, `project:` as a
|
|
905
|
+
// plain string) that scored 0% whatever the folder held, failed faf_trust and
|
|
906
|
+
// crashed faf_go. Two steps of `faf init` are not exported by faf-cli
|
|
907
|
+
// 7.12 and are not repeated here: the home/root refusal and the
|
|
908
|
+
// .faf-dna birth record (faf_dna writes that on its first call).
|
|
904
909
|
try {
|
|
905
|
-
//
|
|
910
|
+
// Path resolution unchanged (supports "my-app", "~/Projects/my-app", "/full/path")
|
|
906
911
|
const userInput = args?.path;
|
|
907
912
|
const resolution = (0, path_resolver_1.resolveProjectPath)(userInput);
|
|
908
913
|
const targetDir = resolution.projectPath;
|
|
909
|
-
const projectName = resolution.projectName;
|
|
910
914
|
const fafPath = resolution.fafFilePath;
|
|
911
915
|
// Ensure project directory exists
|
|
912
916
|
if (!fs.existsSync(targetDir)) {
|
|
@@ -918,57 +922,16 @@ class FafToolHandler {
|
|
|
918
922
|
return {
|
|
919
923
|
content: [{
|
|
920
924
|
type: 'text',
|
|
921
|
-
text: `🚀
|
|
925
|
+
text: `🚀 FAF Initialization:\n\n⚠️ ${existingFaf.filename} already exists in ${targetDir}\n💡 Use force: true to overwrite`
|
|
922
926
|
}]
|
|
923
927
|
};
|
|
924
928
|
}
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
let projectData = {
|
|
932
|
-
project: projectName,
|
|
933
|
-
project_type: projectType,
|
|
934
|
-
description: projectDescription,
|
|
935
|
-
generated: new Date().toISOString(),
|
|
936
|
-
version: version_1.VERSION
|
|
937
|
-
};
|
|
938
|
-
// Apply Intel-Friday: Auto-fill Chrome Extension slots for 90%+ score!
|
|
939
|
-
if (chromeDetection.detected) {
|
|
940
|
-
projectData = (0, fuzzy_detector_1.applyIntelFriday)(projectData);
|
|
941
|
-
}
|
|
942
|
-
// Create enhanced .faf content
|
|
943
|
-
const fafContent = `# FAF - Foundational AI Context
|
|
944
|
-
project: ${projectData.project}
|
|
945
|
-
type: ${projectData.project_type}${chromeDetection.detected ? ' 🎯' : ''}
|
|
946
|
-
context: I⚡🍊
|
|
947
|
-
generated: ${projectData.generated}
|
|
948
|
-
version: ${projectData.version}
|
|
949
|
-
${chromeDetection.corrected ? `# Auto-corrected: "${args?.description}" → "${chromeDetection.corrected}"` : ''}
|
|
950
|
-
|
|
951
|
-
# The Formula
|
|
952
|
-
human_input: Your project files
|
|
953
|
-
multiplier: FAF Context
|
|
954
|
-
output: Championship Performance
|
|
955
|
-
|
|
956
|
-
# Quick Context
|
|
957
|
-
working_directory: ${targetDir}
|
|
958
|
-
initialized_by: faf-mcp${projectData._friday_feature ? `\nfriday_feature: ${projectData._friday_feature}` : ''}
|
|
959
|
-
vitamin_context: true
|
|
960
|
-
faffless: true
|
|
961
|
-
|
|
962
|
-
${chromeDetection.detected ? `# Chrome Extension Auto-Fill (90%+ Score!)
|
|
963
|
-
runtime: ${projectData.runtime}
|
|
964
|
-
hosting: ${projectData.hosting}
|
|
965
|
-
api_type: ${projectData.api_type}
|
|
966
|
-
backend: ${projectData.backend}
|
|
967
|
-
database: ${projectData.database}
|
|
968
|
-
build: ${projectData.build}
|
|
969
|
-
package_manager: ${projectData.package_manager}` : ''}
|
|
970
|
-
`;
|
|
971
|
-
fs.writeFileSync(fafPath, fafContent);
|
|
929
|
+
const { assembleFreshFaf, writeFaf, readFafRaw, scoreFafYaml } = await faf_cli_bridge_js_1.fafCli;
|
|
930
|
+
writeFaf(fafPath, assembleFreshFaf(targetDir));
|
|
931
|
+
const score = scoreFafYaml(readFafRaw(fafPath));
|
|
932
|
+
// Like `cd` + `faf init`: the new project becomes the current one, so the
|
|
933
|
+
// next steps printed below (faf_score, faf_claude, faf_go) act on it.
|
|
934
|
+
this.engineAdapter.setWorkingDirectory(targetDir);
|
|
972
935
|
// Pomelli-style success confirmation with path resolution info
|
|
973
936
|
const pathConfirmation = (0, path_resolver_1.formatPathConfirmation)(resolution);
|
|
974
937
|
const sourceExplanation = resolution.source === 'user-name'
|
|
@@ -977,7 +940,7 @@ package_manager: ${projectData.package_manager}` : ''}
|
|
|
977
940
|
return {
|
|
978
941
|
content: [{
|
|
979
942
|
type: 'text',
|
|
980
|
-
text: `🚀
|
|
943
|
+
text: `🚀 FAF Initialization:\n\n✅ Created project.faf\n📊 ${score.score}/100 (${score.populated}/${score.active} slots populated) — ${score.tier.name}\n\n${pathConfirmation}${sourceExplanation}\n\n🍊 Vitamin Context activated!\n⚡ FAFFLESS AI ready!\n\n🏁 Next steps:\n • Run faf_score for AI-readiness score\n • Run faf_claude to write CLAUDE.md\n • Run faf_go to fill the gaps`
|
|
981
944
|
}]
|
|
982
945
|
};
|
|
983
946
|
}
|
|
@@ -985,31 +948,71 @@ package_manager: ${projectData.package_manager}` : ''}
|
|
|
985
948
|
return {
|
|
986
949
|
content: [{
|
|
987
950
|
type: 'text',
|
|
988
|
-
text: `🚀
|
|
951
|
+
text: `🚀 FAF Initialization:\n\n❌ Error: ${error.message}`
|
|
989
952
|
}],
|
|
990
953
|
isError: true
|
|
991
954
|
};
|
|
992
955
|
}
|
|
993
956
|
}
|
|
957
|
+
/**
|
|
958
|
+
* v3.0: was `this.engineAdapter.callEngine('trust')` — no bundled 'trust'
|
|
959
|
+
* branch exists in engine-adapter.ts, so this fell straight through to
|
|
960
|
+
* the ambient-PATH shell-out fallback (`which faf`, then execAsync).
|
|
961
|
+
* faf-cli itself has no `trust` subcommand at all — on any host where an
|
|
962
|
+
* unrelated `faf`-named binary sits first on PATH (verified live on this
|
|
963
|
+
* machine: Rust "FAFb"), this either hard-errors on an "unrecognized
|
|
964
|
+
* subcommand" or, worse, silently "succeeds" with that tool's own output.
|
|
965
|
+
* Rewired onto the bundled faf-cli bridge — bundled faf-cli only, never
|
|
966
|
+
* ambient PATH — composing `validateFaf` (structural integrity) +
|
|
967
|
+
* `scoreFafYaml` (trust metrics) directly, matching the tool's own
|
|
968
|
+
* description ("structural integrity and field consistency... trust
|
|
969
|
+
* metrics"). No CLI subcommand to shell out to even exists for this.
|
|
970
|
+
*/
|
|
994
971
|
async handleFafTrust(_args) {
|
|
995
|
-
const
|
|
996
|
-
|
|
972
|
+
const cwd = this.getProjectPath();
|
|
973
|
+
const { findFafFile: findFaf, readFaf, readFafRaw, validateFaf, scoreFafYaml } = await faf_cli_bridge_js_1.fafCli;
|
|
974
|
+
const fafPath = findFaf(cwd);
|
|
975
|
+
if (!fafPath) {
|
|
976
|
+
return {
|
|
977
|
+
content: [{
|
|
978
|
+
type: 'text',
|
|
979
|
+
text: `🔒 FAF Trust Validation:\n\n❌ No .faf found in ${cwd}\n💡 Run faf_init to create one — then faf_trust reports real trust metrics.`
|
|
980
|
+
}]
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
let raw;
|
|
984
|
+
let data;
|
|
985
|
+
try {
|
|
986
|
+
raw = readFafRaw(fafPath);
|
|
987
|
+
data = readFaf(fafPath);
|
|
988
|
+
}
|
|
989
|
+
catch (error) {
|
|
997
990
|
return {
|
|
998
991
|
content: [{
|
|
999
992
|
type: 'text',
|
|
1000
|
-
text: `🔒
|
|
993
|
+
text: `🔒 FAF Trust Validation:\n\n❌ Could not read ${fafPath}: ${error?.message ?? String(error)}`
|
|
1001
994
|
}],
|
|
1002
995
|
isError: true
|
|
1003
996
|
};
|
|
1004
997
|
}
|
|
1005
|
-
const
|
|
1006
|
-
|
|
1007
|
-
|
|
998
|
+
const validation = validateFaf(data);
|
|
999
|
+
let output = validation.valid
|
|
1000
|
+
? `✅ Structurally sound — no malformed, missing, or contradictory fields found.`
|
|
1001
|
+
: `❌ ${validation.errors.length} issue${validation.errors.length === 1 ? '' : 's'} found:\n` +
|
|
1002
|
+
validation.errors.map((e) => ` • ${e}`).join('\n');
|
|
1003
|
+
try {
|
|
1004
|
+
const score = scoreFafYaml(raw);
|
|
1005
|
+
output += `\n\n📊 ${score.score}/100 (${score.populated}/${score.active} slots populated) — ${score.tier.name}`;
|
|
1006
|
+
}
|
|
1007
|
+
catch {
|
|
1008
|
+
// Score is a bonus signal here; a validation-only result is still honest.
|
|
1009
|
+
}
|
|
1008
1010
|
return {
|
|
1009
1011
|
content: [{
|
|
1010
1012
|
type: 'text',
|
|
1011
|
-
text: `🔒
|
|
1012
|
-
}]
|
|
1013
|
+
text: `🔒 FAF Trust Validation:\n\n${output}`
|
|
1014
|
+
}],
|
|
1015
|
+
isError: !validation.valid
|
|
1013
1016
|
};
|
|
1014
1017
|
}
|
|
1015
1018
|
async handleFafSync(args) {
|
|
@@ -1017,12 +1020,14 @@ package_manager: ${projectData.package_manager}` : ''}
|
|
|
1017
1020
|
if (args?.path) {
|
|
1018
1021
|
this.getProjectPath(args.path);
|
|
1019
1022
|
}
|
|
1020
|
-
|
|
1023
|
+
// `apply` maps to the bundled sync command's --auto; without it the
|
|
1024
|
+
// command only reports what would change.
|
|
1025
|
+
const result = await this.engineAdapter.callEngine('sync', args?.apply ? ['--auto'] : []);
|
|
1021
1026
|
if (!result.success) {
|
|
1022
1027
|
return {
|
|
1023
1028
|
content: [{
|
|
1024
1029
|
type: 'text',
|
|
1025
|
-
text: `🔄
|
|
1030
|
+
text: `🔄 FAF Sync:\n\nFailed to sync: ${result.error}`
|
|
1026
1031
|
}],
|
|
1027
1032
|
isError: true
|
|
1028
1033
|
};
|
|
@@ -1033,31 +1038,39 @@ package_manager: ${projectData.package_manager}` : ''}
|
|
|
1033
1038
|
return {
|
|
1034
1039
|
content: [{
|
|
1035
1040
|
type: 'text',
|
|
1036
|
-
text: `🔄
|
|
1041
|
+
text: `🔄 FAF Sync:\n\n${output}`
|
|
1037
1042
|
}]
|
|
1038
1043
|
};
|
|
1039
1044
|
}
|
|
1040
|
-
async
|
|
1045
|
+
async handleFafClaude(args) {
|
|
1041
1046
|
// Set project context if path provided
|
|
1042
1047
|
if (args?.path) {
|
|
1043
1048
|
this.getProjectPath(args.path);
|
|
1044
1049
|
}
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1050
|
+
// auto / watch / force used to be declared here too; none of them was
|
|
1051
|
+
// ever read by the command, so the schema no longer promises them.
|
|
1052
|
+
const claudeArgs = [];
|
|
1053
|
+
// v3.0 fix: the tool's own schema advertises agents/cursor/gemini/all,
|
|
1054
|
+
// but before 3.0 they were never forwarded to the engine adapter —
|
|
1055
|
+
// the tool silently ignored them and only ever wrote CLAUDE.md.
|
|
1056
|
+
if (args?.agents) {
|
|
1057
|
+
claudeArgs.push('--agents');
|
|
1058
|
+
}
|
|
1059
|
+
if (args?.cursor) {
|
|
1060
|
+
claudeArgs.push('--cursor');
|
|
1048
1061
|
}
|
|
1049
|
-
if (args?.
|
|
1050
|
-
|
|
1062
|
+
if (args?.gemini) {
|
|
1063
|
+
claudeArgs.push('--gemini');
|
|
1051
1064
|
}
|
|
1052
|
-
if (args?.
|
|
1053
|
-
|
|
1065
|
+
if (args?.all) {
|
|
1066
|
+
claudeArgs.push('--all');
|
|
1054
1067
|
}
|
|
1055
|
-
const result = await this.engineAdapter.callEngine('
|
|
1068
|
+
const result = await this.engineAdapter.callEngine('claude', claudeArgs);
|
|
1056
1069
|
if (!result.success) {
|
|
1057
1070
|
return {
|
|
1058
1071
|
content: [{
|
|
1059
1072
|
type: 'text',
|
|
1060
|
-
text: `🔗
|
|
1073
|
+
text: `🔗 FAF → CLAUDE.md:\n\nCould not write CLAUDE.md: ${result.error}`
|
|
1061
1074
|
}],
|
|
1062
1075
|
isError: true
|
|
1063
1076
|
};
|
|
@@ -1068,41 +1081,50 @@ package_manager: ${projectData.package_manager}` : ''}
|
|
|
1068
1081
|
return {
|
|
1069
1082
|
content: [{
|
|
1070
1083
|
type: 'text',
|
|
1071
|
-
text: `🔗
|
|
1084
|
+
text: `🔗 FAF → CLAUDE.md:\n\n${output}`
|
|
1072
1085
|
}]
|
|
1073
1086
|
};
|
|
1074
1087
|
}
|
|
1088
|
+
/**
|
|
1089
|
+
* v3.0: was `this.engineAdapter.callEngine('clear', clearArgs)` — no
|
|
1090
|
+
* bundled 'clear' branch exists in engine-adapter.ts, so this fell
|
|
1091
|
+
* straight through to the ambient-PATH shell-out fallback. Worse: even
|
|
1092
|
+
* when a real faf-cli WAS first on PATH, its actual `clear` subcommand
|
|
1093
|
+
* only clears `faf-git-*` temp directories from the OS tmpdir (see
|
|
1094
|
+
* ~/FAF/cli/src/commands/clear.ts) — entirely unrelated to this tool's
|
|
1095
|
+
* own cache/todos/backups semantics. There was never a correct backing
|
|
1096
|
+
* implementation for this tool's contract, on any PATH.
|
|
1097
|
+
*
|
|
1098
|
+
* Rewired as a genuinely local operation — no CLI dependency, bundled or
|
|
1099
|
+
* ambient, at all. `cache` clears the one real cache path this codebase
|
|
1100
|
+
* writes (~/.faf-cli-cache, faf-core/utils/technical-credit.ts). The
|
|
1101
|
+
* schema used to advertise `todos` and `backups` as well; no persisted todo
|
|
1102
|
+
* list or backup-file store exists anywhere in faf-mcp, so those flags are
|
|
1103
|
+
* gone rather than answered with a made-up "cleared".
|
|
1104
|
+
*/
|
|
1075
1105
|
async handleFafClear(args) {
|
|
1076
|
-
const
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
type: 'text',
|
|
1094
|
-
text: `🧹 Claude FAF Clear:\n\nFailed to clear: ${result.error}`
|
|
1095
|
-
}],
|
|
1096
|
-
isError: true
|
|
1097
|
-
};
|
|
1106
|
+
const wantCache = !!args?.cache;
|
|
1107
|
+
const wantAll = !!args?.all || !wantCache;
|
|
1108
|
+
const lines = [];
|
|
1109
|
+
if (wantCache || wantAll) {
|
|
1110
|
+
// HOME/USERPROFILE first (Node's own os.homedir() precedence; Bun's ignores a
|
|
1111
|
+
// runtime override) so a test can redirect the home dir instead of
|
|
1112
|
+
// deleting from the developer's real one.
|
|
1113
|
+
const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
1114
|
+
const cacheDir = pathModule.join(home, '.faf-cli-cache');
|
|
1115
|
+
const creditCache = pathModule.join(cacheDir, 'technical-credit.json');
|
|
1116
|
+
if (fs.existsSync(creditCache)) {
|
|
1117
|
+
fs.rmSync(creditCache, { force: true });
|
|
1118
|
+
lines.push(`✅ cache: cleared ${creditCache}`);
|
|
1119
|
+
}
|
|
1120
|
+
else {
|
|
1121
|
+
lines.push('✅ cache: nothing to clear');
|
|
1122
|
+
}
|
|
1098
1123
|
}
|
|
1099
|
-
const output = typeof result.data === 'string'
|
|
1100
|
-
? result.data
|
|
1101
|
-
: result.data?.output || JSON.stringify(result.data, null, 2);
|
|
1102
1124
|
return {
|
|
1103
1125
|
content: [{
|
|
1104
1126
|
type: 'text',
|
|
1105
|
-
text: `🧹
|
|
1127
|
+
text: `🧹 FAF Clear:\n\n${lines.join('\n')}`
|
|
1106
1128
|
}]
|
|
1107
1129
|
};
|
|
1108
1130
|
}
|
|
@@ -1131,7 +1153,7 @@ WHAT IS .FAF?
|
|
|
1131
1153
|
• The dot (.) means it's a file format!
|
|
1132
1154
|
|
|
1133
1155
|
🧡 Trust: Context verified
|
|
1134
|
-
⚡️ Speed:
|
|
1156
|
+
⚡️ Speed: Authored in <29ms
|
|
1135
1157
|
SPEEDY AI you can TRUST!
|
|
1136
1158
|
|
|
1137
1159
|
Version ${packageInfo.version}
|
|
@@ -1142,10 +1164,10 @@ Just like JPEG makes images universal,
|
|
|
1142
1164
|
HOW IT WORKS:
|
|
1143
1165
|
1. Drop a file or paste the path
|
|
1144
1166
|
2. Create .faf (Foundational AI-context Format)
|
|
1145
|
-
3.
|
|
1167
|
+
3. Ask your AI to run faf_claude
|
|
1146
1168
|
4. You're done⚡
|
|
1147
1169
|
|
|
1148
|
-
🩵 You just made
|
|
1170
|
+
🩵 You just made your AI happy
|
|
1149
1171
|
🧡⚡️ SPEEDY AI you can TRUST!`;
|
|
1150
1172
|
return {
|
|
1151
1173
|
content: [{
|
|
@@ -1164,7 +1186,7 @@ WHY: Just like JPEG makes images viewable everywhere,
|
|
|
1164
1186
|
.faf makes projects understandable by AI.
|
|
1165
1187
|
|
|
1166
1188
|
HOW: Run 'faf' on any project to create one.
|
|
1167
|
-
Run 'faf_score' to check AI-readiness (target:
|
|
1189
|
+
Run 'faf_score' to check AI-readiness (target: 100%).
|
|
1168
1190
|
|
|
1169
1191
|
REMEMBER: Always use ".faf" with the dot - it's a FORMAT!
|
|
1170
1192
|
|
|
@@ -1180,18 +1202,15 @@ REMEMBER: Always use ".faf" with the dot - it's a FORMAT!
|
|
|
1180
1202
|
try {
|
|
1181
1203
|
const fs = await import('fs');
|
|
1182
1204
|
const path = await import('path');
|
|
1183
|
-
const { exec } = await import('child_process');
|
|
1184
|
-
const { promisify } = await import('util');
|
|
1185
|
-
const _execAsync = promisify(exec);
|
|
1186
1205
|
const cwd = this.engineAdapter.getWorkingDirectory();
|
|
1187
1206
|
const debugInfo = {
|
|
1188
1207
|
workingDirectory: cwd,
|
|
1189
1208
|
canWrite: false,
|
|
1190
|
-
|
|
1191
|
-
|
|
1209
|
+
// Every tool runs on the faf-cli bundled in node_modules — report
|
|
1210
|
+
// that, not whatever `which faf` happens to find on PATH.
|
|
1211
|
+
fafVersion: (0, faf_cli_bridge_js_1.bundledFafCliVersion)(),
|
|
1192
1212
|
permissions: {},
|
|
1193
1213
|
enginePath: this.engineAdapter.getEnginePath(),
|
|
1194
|
-
pathEnv: process.env.PATH?.split(':') || []
|
|
1195
1214
|
};
|
|
1196
1215
|
// Check write permissions
|
|
1197
1216
|
try {
|
|
@@ -1203,21 +1222,6 @@ REMEMBER: Always use ".faf" with the dot - it's a FORMAT!
|
|
|
1203
1222
|
catch (error) {
|
|
1204
1223
|
debugInfo.permissions.writeError = error instanceof Error ? error.message : String(error);
|
|
1205
1224
|
}
|
|
1206
|
-
// Check FAF CLI availability using championship auto-detection
|
|
1207
|
-
try {
|
|
1208
|
-
const cliInfo = this.engineAdapter.getCliInfo();
|
|
1209
|
-
if (cliInfo.detected && cliInfo.path) {
|
|
1210
|
-
debugInfo.fafCliPath = cliInfo.path;
|
|
1211
|
-
debugInfo.fafVersion = cliInfo.version || null;
|
|
1212
|
-
}
|
|
1213
|
-
else {
|
|
1214
|
-
debugInfo.fafCliPath = null;
|
|
1215
|
-
debugInfo.fafVersion = null;
|
|
1216
|
-
}
|
|
1217
|
-
}
|
|
1218
|
-
catch (error) {
|
|
1219
|
-
debugInfo.permissions.fafError = error instanceof Error ? error.message : String(error);
|
|
1220
|
-
}
|
|
1221
1225
|
// Check for existing FAF file (v1.2.0: project.faf, *.faf, or .faf)
|
|
1222
1226
|
const fafResult = await (0, faf_file_finder_js_1.findFafFile)(cwd);
|
|
1223
1227
|
const hasFaf = fafResult !== null;
|
|
@@ -1226,15 +1230,12 @@ REMEMBER: Always use ".faf" with the dot - it's a FORMAT!
|
|
|
1226
1230
|
📂 Working Directory: ${debugInfo.workingDirectory}
|
|
1227
1231
|
✏️ Write Permissions: ${debugInfo.canWrite ? '✅ Yes' : '❌ No'}
|
|
1228
1232
|
${debugInfo.permissions.writeError ? ` Error: ${debugInfo.permissions.writeError}\n` : ''}🤖 FAF Engine Path: ${debugInfo.enginePath}
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
${debugInfo.permissions.fafError ? ` FAF Error: ${debugInfo.permissions.fafError}\n` : ''}📄 FAF File: ${hasFaf ? `✅ ${fafResult.filename} exists` : '❌ Not found (run faf_init)'}
|
|
1232
|
-
🛤️ System PATH: ${debugInfo.pathEnv.slice(0, 3).join(', ')}${debugInfo.pathEnv.length > 3 ? '...' : ''}
|
|
1233
|
+
📋 faf-cli (bundled): ${debugInfo.fafVersion ? `v${debugInfo.fafVersion}` : '❌ not found in node_modules — reinstall faf-mcp'}
|
|
1234
|
+
📄 FAF File: ${hasFaf ? `✅ ${fafResult.filename} exists` : '❌ Not found (run faf_init)'}
|
|
1233
1235
|
|
|
1234
1236
|
💡 Quick Start:
|
|
1235
|
-
1. If
|
|
1236
|
-
2.
|
|
1237
|
-
3. For optimization: use faf_enhance tool with model="claude"
|
|
1237
|
+
1. If .faf file missing: use faf_init or faf_auto
|
|
1238
|
+
2. To check AI-readiness: use faf_score
|
|
1238
1239
|
`;
|
|
1239
1240
|
return {
|
|
1240
1241
|
content: [{
|
|
@@ -1247,7 +1248,7 @@ ${debugInfo.permissions.fafError ? ` FAF Error: ${debugInfo.permissions.fafErr
|
|
|
1247
1248
|
return {
|
|
1248
1249
|
content: [{
|
|
1249
1250
|
type: 'text',
|
|
1250
|
-
text: `🔍
|
|
1251
|
+
text: `🔍 faf-mcp Debug Failed: ${error instanceof Error ? error.message : String(error)}`
|
|
1251
1252
|
}],
|
|
1252
1253
|
isError: true
|
|
1253
1254
|
};
|
|
@@ -1299,7 +1300,6 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
|
|
|
1299
1300
|
**Extensions:**
|
|
1300
1301
|
- \`new\` - force overwrite existing
|
|
1301
1302
|
- \`full\` - detailed output
|
|
1302
|
-
- \`bi\` - bi-directional sync
|
|
1303
1303
|
|
|
1304
1304
|
## UX Rules
|
|
1305
1305
|
1. **Don't offer option menus** - just solve it
|
|
@@ -1459,7 +1459,7 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
|
|
|
1459
1459
|
}
|
|
1460
1460
|
}
|
|
1461
1461
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
1462
|
-
//
|
|
1462
|
+
// Human Context Tools (parity with faf-cli v3.2.0)
|
|
1463
1463
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
1464
1464
|
async handleFafReadme(args) {
|
|
1465
1465
|
try {
|
|
@@ -1686,7 +1686,7 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
|
|
|
1686
1686
|
}
|
|
1687
1687
|
// Default: show quality report
|
|
1688
1688
|
const icons = {
|
|
1689
|
-
empty: '⬜', generic: '
|
|
1689
|
+
empty: '⬜', generic: '◻️', good: '◼️', excellent: '💎'
|
|
1690
1690
|
};
|
|
1691
1691
|
let output = `🔍 FAF Human Context Quality\n\n`;
|
|
1692
1692
|
for (const field of fields) {
|
|
@@ -1780,7 +1780,7 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
|
|
|
1780
1780
|
// they're SOURCED by Turbo-Cat (faf-cli's separate STACK_INTERVIEW if ever
|
|
1781
1781
|
// needed), never asked of a human. (Decision: single-source the 8-Q 6Ws
|
|
1782
1782
|
// Interview, wolfejam 2026-06-10 — language is not on the human side.)
|
|
1783
|
-
const { SIX_WS_INTERVIEW } = await faf_cli_bridge_js_1.fafCli;
|
|
1783
|
+
const { SIX_WS_INTERVIEW, isPlaceholder, scoreFafYaml } = await faf_cli_bridge_js_1.fafCli;
|
|
1784
1784
|
const QUESTION_REGISTRY = Object.fromEntries(SIX_WS_INTERVIEW.map((q) => [q.path, q]));
|
|
1785
1785
|
// Priority order for questions
|
|
1786
1786
|
const priorityOrder = [
|
|
@@ -1825,16 +1825,18 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
|
|
|
1825
1825
|
}
|
|
1826
1826
|
current[parts[parts.length - 1]] = value;
|
|
1827
1827
|
};
|
|
1828
|
-
//
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1828
|
+
// Empty = what the scorer calls empty. faf-cli's isPlaceholder is the one
|
|
1829
|
+
// rule the kernel scores by; a local list ('TBD', 'Unknown', ...) drifted
|
|
1830
|
+
// from it in both directions and made faf_go's numbers disagree with
|
|
1831
|
+
// faf_score on the same file. slotignored is inactive, never asked.
|
|
1832
|
+
const isEmpty = (value) => isPlaceholder(value) && value !== 'slotignored';
|
|
1833
|
+
// One score function, period: the real scorer on the real bytes.
|
|
1834
|
+
const scoreOf = (raw) => scoreFafYaml(raw).score;
|
|
1835
|
+
const tierLabel = (score) => score >= 100 ? '✪ Trophy — 100% AI-Readiness' :
|
|
1836
|
+
score >= 99 ? '★ Gold' :
|
|
1837
|
+
score >= 95 ? '◆ Silver' :
|
|
1838
|
+
score >= 85 ? '◇ Bronze — production ready' :
|
|
1839
|
+
score >= 70 ? '● Green — solid foundation' : '📈 Keep going!';
|
|
1838
1840
|
// PHASE 2: Apply answers if provided
|
|
1839
1841
|
if (args?.answers && typeof args.answers === 'object') {
|
|
1840
1842
|
const answers = args.answers;
|
|
@@ -1845,15 +1847,10 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
|
|
|
1845
1847
|
appliedCount++;
|
|
1846
1848
|
}
|
|
1847
1849
|
}
|
|
1848
|
-
// Write updated file
|
|
1850
|
+
// Write updated file, then score the bytes that were written.
|
|
1849
1851
|
fs.writeFileSync(fafResult.path, yaml.stringify(fafData), 'utf-8');
|
|
1850
|
-
|
|
1851
|
-
const
|
|
1852
|
-
const filledFields = Object.keys(QUESTION_REGISTRY).filter(field => !isEmpty(getNestedValue(fafData, field))).length;
|
|
1853
|
-
const newScore = Math.round((filledFields / totalFields) * 100);
|
|
1854
|
-
const celebration = newScore >= 100 ? '🏆 GOLD CODE ACHIEVED!' :
|
|
1855
|
-
newScore >= 85 ? '🥇 Championship grade!' :
|
|
1856
|
-
newScore >= 70 ? '🥈 Great progress!' : '📈 Keep going!';
|
|
1852
|
+
const newScore = scoreOf(fs.readFileSync(fafResult.path, 'utf-8'));
|
|
1853
|
+
const celebration = tierLabel(newScore);
|
|
1857
1854
|
return {
|
|
1858
1855
|
content: [{
|
|
1859
1856
|
type: 'text',
|
|
@@ -1864,12 +1861,10 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
|
|
|
1864
1861
|
// PHASE 1: Analyze and return questions — the canonical 6Ws (SIX_WS_INTERVIEW),
|
|
1865
1862
|
// scoped to slots that are empty AND active (slotignored is never asked).
|
|
1866
1863
|
const missingFields = SIX_WS_INTERVIEW
|
|
1867
|
-
.filter((q) =>
|
|
1864
|
+
.filter((q) => isEmpty(getNestedValue(fafData, q.path)))
|
|
1868
1865
|
.map((q) => q.path);
|
|
1869
|
-
//
|
|
1870
|
-
const
|
|
1871
|
-
const filledFields = totalFields - missingFields.length;
|
|
1872
|
-
const currentScore = Math.round((filledFields / totalFields) * 100);
|
|
1866
|
+
// Current score — the same number faf_score reports for this file.
|
|
1867
|
+
const currentScore = scoreOf(fafContent);
|
|
1873
1868
|
// Already at 100%?
|
|
1874
1869
|
if (currentScore >= 100) {
|
|
1875
1870
|
return {
|
|
@@ -1878,7 +1873,7 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
|
|
|
1878
1873
|
text: JSON.stringify({
|
|
1879
1874
|
complete: true,
|
|
1880
1875
|
score: 100,
|
|
1881
|
-
message: '
|
|
1876
|
+
message: '✪ GOLD CODE ACHIEVED! Your project has 100% AI-Readiness.',
|
|
1882
1877
|
context: 'faf_go'
|
|
1883
1878
|
}, null, 2)
|
|
1884
1879
|
}]
|
|
@@ -1892,7 +1887,7 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
|
|
|
1892
1887
|
text: JSON.stringify({
|
|
1893
1888
|
score: currentScore,
|
|
1894
1889
|
message: `Score is ${currentScore}%. All fields filled but content may need enhancement.`,
|
|
1895
|
-
suggestion: 'Use
|
|
1890
|
+
suggestion: 'Use faf_go or faf_human_add to improve content quality.',
|
|
1896
1891
|
context: 'faf_go'
|
|
1897
1892
|
}, null, 2)
|
|
1898
1893
|
}]
|
|
@@ -1945,120 +1940,52 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
|
|
|
1945
1940
|
/**
|
|
1946
1941
|
* faf_auto - ONE COMMAND TO RULE THEM ALL
|
|
1947
1942
|
* Zero to Championship in one command
|
|
1948
|
-
*
|
|
1943
|
+
*
|
|
1944
|
+
* v3.0: composes faf-cli's own `faf auto` chain — `updateExistingFaf` on an
|
|
1945
|
+
* existing file, `assembleFreshFaf` on a new one, `writeFaf` for the bytes,
|
|
1946
|
+
* `renderClaudeMd`/`writeClaudeMd` for CLAUDE.md — plus the same
|
|
1947
|
+
* `scoreFafYaml` faf_score reads. Previously this handler ran a bespoke
|
|
1948
|
+
* bootstrap template plus a local `calculateSimpleScore` pseudo-scorer,
|
|
1949
|
+
* which could (and did) report a different score than faf_score for the
|
|
1950
|
+
* identical file in the same server run. NEVER reimplement detection or
|
|
1951
|
+
* scoring here — compose faf-cli, exactly like faf_score/faf_formats do.
|
|
1949
1952
|
*/
|
|
1950
1953
|
async handleFafAuto(args) {
|
|
1951
1954
|
const startTime = Date.now();
|
|
1952
1955
|
const cwd = this.getProjectPath(args?.path);
|
|
1953
|
-
const yaml = await import('yaml');
|
|
1954
1956
|
const path = await import('path');
|
|
1955
1957
|
try {
|
|
1956
1958
|
const steps = [];
|
|
1957
|
-
|
|
1958
|
-
// Step 1:
|
|
1959
|
-
const
|
|
1959
|
+
const { findFafFile: findFaf, readFaf, readFafRaw, scoreFafYaml, assembleFreshFaf, updateExistingFaf, writeFaf, renderClaudeMd, writeClaudeMd, } = await faf_cli_bridge_js_1.fafCli;
|
|
1960
|
+
// Step 1: locate any existing .faf and its real "before" score.
|
|
1961
|
+
const existingPath = findFaf(cwd);
|
|
1962
|
+
const beforeScore = existingPath ? scoreFafYaml(readFafRaw(existingPath)).score : 0;
|
|
1963
|
+
steps.push(existingPath ? `✅ Found ${path.basename(existingPath)}` : '✅ Will create project.faf');
|
|
1964
|
+
// Step 2: exactly what faf-cli's own `faf auto` does — an existing file
|
|
1965
|
+
// is updated with updateExistingFaf (existing values win; interrogated →
|
|
1966
|
+
// detected → Turbo-Cat → Relentless fill the empties), a new one is
|
|
1967
|
+
// assembled with assembleFreshFaf — and written with faf-cli's writer,
|
|
1968
|
+
// so the bytes match `faf auto` (no runtime `_meta` block, same layout).
|
|
1960
1969
|
let fafPath;
|
|
1961
|
-
if (
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
const initFafContent = `# FAF - Foundational AI Context
|
|
1966
|
-
project: ${projectName}
|
|
1967
|
-
type: auto-detected
|
|
1968
|
-
context: I⚡🍊
|
|
1969
|
-
generated: ${new Date().toISOString()}
|
|
1970
|
-
version: ${version_1.VERSION}
|
|
1971
|
-
|
|
1972
|
-
# Quick Context
|
|
1973
|
-
working_directory: ${cwd}
|
|
1974
|
-
initialized_by: faf-mcp-auto
|
|
1975
|
-
vitamin_context: true
|
|
1976
|
-
faffless: true
|
|
1977
|
-
`;
|
|
1978
|
-
fs.writeFileSync(fafPath, initFafContent);
|
|
1979
|
-
steps.push('✅ Created project.faf');
|
|
1970
|
+
if (existingPath) {
|
|
1971
|
+
fafPath = existingPath;
|
|
1972
|
+
writeFaf(fafPath, updateExistingFaf(cwd, readFaf(existingPath)));
|
|
1973
|
+
steps.push('✅ Merged fresh facts into existing project.faf (existing values preserved)');
|
|
1980
1974
|
}
|
|
1981
1975
|
else {
|
|
1982
|
-
fafPath =
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
// Get initial score
|
|
1986
|
-
const fafContent = fs.readFileSync(fafPath, 'utf-8');
|
|
1987
|
-
const fafData = yaml.parse(fafContent) || {};
|
|
1988
|
-
currentScore = this.calculateSimpleScore(fafData);
|
|
1989
|
-
// Step 2: Run TURBO-CAT format discovery — composed from faf-cli's engine.
|
|
1990
|
-
const formatsResult = await (0, turbocat_bridge_js_1.composedTurboCat)(cwd);
|
|
1991
|
-
if (formatsResult && formatsResult.discoveredFormats.length > 0) {
|
|
1992
|
-
// Apply slot fills to .faf. turboCatSlots routes them into the correct
|
|
1993
|
-
// .faf sections; fill the stack section (main_language lives under
|
|
1994
|
-
// project, which is a string here) — clean keys, no noise.
|
|
1995
|
-
const slots = await (0, turbocat_bridge_js_1.composedTurboCatSlots)(cwd);
|
|
1996
|
-
if (!fafData.stack)
|
|
1997
|
-
fafData.stack = {};
|
|
1998
|
-
for (const [key, value] of Object.entries(slots?.stack ?? {})) {
|
|
1999
|
-
if (!fafData.stack[key] || fafData.stack[key] === 'None') {
|
|
2000
|
-
fafData.stack[key] = value;
|
|
2001
|
-
}
|
|
2002
|
-
}
|
|
2003
|
-
if (formatsResult.stackSignature) {
|
|
2004
|
-
fafData.stack_signature = formatsResult.stackSignature;
|
|
2005
|
-
}
|
|
2006
|
-
fs.writeFileSync(fafPath, yaml.stringify(fafData), 'utf-8');
|
|
2007
|
-
steps.push(`✅ TURBO-CAT discovered ${formatsResult.discoveredFormats.length} formats`);
|
|
2008
|
-
}
|
|
2009
|
-
else {
|
|
2010
|
-
steps.push('⚠️ No additional formats detected');
|
|
2011
|
-
}
|
|
2012
|
-
// Step 3: Extract human context from README
|
|
2013
|
-
const readmePath = path.join(cwd, 'README.md');
|
|
2014
|
-
if (fs.existsSync(readmePath)) {
|
|
2015
|
-
const readmeContent = fs.readFileSync(readmePath, 'utf-8');
|
|
2016
|
-
const extracted = this.extractSixWsFromReadme(readmeContent);
|
|
2017
|
-
if (!fafData.human_context)
|
|
2018
|
-
fafData.human_context = {};
|
|
2019
|
-
let extractedCount = 0;
|
|
2020
|
-
for (const [field, value] of Object.entries(extracted)) {
|
|
2021
|
-
if (value && !fafData.human_context[field]) {
|
|
2022
|
-
fafData.human_context[field] = value;
|
|
2023
|
-
extractedCount++;
|
|
2024
|
-
}
|
|
2025
|
-
}
|
|
2026
|
-
if (extractedCount > 0) {
|
|
2027
|
-
fs.writeFileSync(fafPath, yaml.stringify(fafData), 'utf-8');
|
|
2028
|
-
steps.push(`✅ Extracted ${extractedCount} human context fields from README`);
|
|
2029
|
-
}
|
|
2030
|
-
}
|
|
2031
|
-
// Step 4: Create/Update CLAUDE.md (bi-sync)
|
|
2032
|
-
const claudePath = path.join(cwd, 'CLAUDE.md');
|
|
2033
|
-
if (!fs.existsSync(claudePath)) {
|
|
2034
|
-
const claudeContent = `# 🏎️ CLAUDE.md - AI Telemetry Link
|
|
2035
|
-
|
|
2036
|
-
## Project: ${fafData.project || path.basename(cwd)}
|
|
2037
|
-
**Championship-Grade Project DNA Foundation**
|
|
2038
|
-
|
|
2039
|
-
### 🎯 Project Mission
|
|
2040
|
-
${fafData.human_context?.why || fafData.project?.goal || 'AI-ready project context'}
|
|
2041
|
-
|
|
2042
|
-
### 🏗️ Architecture Overview
|
|
2043
|
-
${fafData.stack_signature || 'Auto-detected stack'}
|
|
2044
|
-
|
|
2045
|
-
---
|
|
2046
|
-
|
|
2047
|
-
**STATUS: BI-SYNC ACTIVE 🔗**
|
|
2048
|
-
*Last Sync: ${new Date().toISOString()}*
|
|
2049
|
-
*Sync Engine: FAF Auto*
|
|
2050
|
-
`;
|
|
2051
|
-
fs.writeFileSync(claudePath, claudeContent);
|
|
2052
|
-
steps.push('✅ Created CLAUDE.md');
|
|
2053
|
-
}
|
|
2054
|
-
else {
|
|
2055
|
-
steps.push('✅ CLAUDE.md already exists');
|
|
1976
|
+
fafPath = path.join(cwd, 'project.faf');
|
|
1977
|
+
writeFaf(fafPath, assembleFreshFaf(cwd));
|
|
1978
|
+
steps.push('✅ Created project.faf');
|
|
2056
1979
|
}
|
|
2057
|
-
// Step
|
|
2058
|
-
|
|
2059
|
-
const
|
|
2060
|
-
|
|
2061
|
-
|
|
1980
|
+
// Step 3: CLAUDE.md — faf-cli's render of the file just written (the
|
|
1981
|
+
// same bytes `faf sync` writes), injected non-destructively.
|
|
1982
|
+
const claudeExisted = fs.existsSync(path.join(cwd, 'CLAUDE.md'));
|
|
1983
|
+
writeClaudeMd(cwd, renderClaudeMd(readFaf(fafPath)));
|
|
1984
|
+
steps.push(claudeExisted ? '✅ Updated CLAUDE.md (faf-managed block)' : '✅ Created CLAUDE.md');
|
|
1985
|
+
// Step 4: final score — same real scorer faf_score reads, on the file
|
|
1986
|
+
// just written. By construction this can never disagree with faf_score.
|
|
1987
|
+
const newScore = scoreFafYaml(readFafRaw(fafPath)).score;
|
|
1988
|
+
const scoreDelta = newScore - beforeScore;
|
|
2062
1989
|
// Calculate elapsed time
|
|
2063
1990
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
2064
1991
|
// Format output
|
|
@@ -2068,21 +1995,27 @@ ${fafData.stack_signature || 'Auto-detected stack'}
|
|
|
2068
1995
|
output += steps.join('\n') + '\n\n';
|
|
2069
1996
|
output += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
|
|
2070
1997
|
output += `⏱️ Completed in ${elapsed}s\n`;
|
|
2071
|
-
output += `📊 Before: ${
|
|
1998
|
+
output += `📊 Before: ${beforeScore}% | After: ${newScore}% ${deltaDisplay}\n`;
|
|
2072
1999
|
output += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n`;
|
|
2073
|
-
if (newScore >=
|
|
2074
|
-
output +=
|
|
2000
|
+
if (newScore >= 100) {
|
|
2001
|
+
output += `✪ TROPHY — 100% AI-Readiness. Your AI has complete context.\n`;
|
|
2002
|
+
}
|
|
2003
|
+
else if (newScore >= 99) {
|
|
2004
|
+
output += `★ Gold — 1% to Trophy. Run faf_go to finish.\n`;
|
|
2005
|
+
}
|
|
2006
|
+
else if (newScore >= 95) {
|
|
2007
|
+
output += `◆ Silver — ${100 - newScore}% to Trophy.\n`;
|
|
2075
2008
|
}
|
|
2076
2009
|
else if (newScore >= 85) {
|
|
2077
|
-
output +=
|
|
2010
|
+
output += `◇ Bronze — production ready. ${100 - newScore}% to Trophy.\n`;
|
|
2078
2011
|
}
|
|
2079
2012
|
else if (newScore >= 70) {
|
|
2080
|
-
output +=
|
|
2013
|
+
output += `● Green — solid foundation. Run faf_go to climb tiers.\n`;
|
|
2081
2014
|
}
|
|
2082
2015
|
else {
|
|
2083
2016
|
output += `🚀 Good start! Run faf_go for guided improvement.\n`;
|
|
2084
2017
|
}
|
|
2085
|
-
output += `\n💡 Next: faf_score
|
|
2018
|
+
output += `\n💡 Next: faf_score | faf_go | faf_claude`;
|
|
2086
2019
|
return { content: [{ type: 'text', text: output }] };
|
|
2087
2020
|
}
|
|
2088
2021
|
catch (error) {
|
|
@@ -2094,7 +2027,7 @@ ${fafData.stack_signature || 'Auto-detected stack'}
|
|
|
2094
2027
|
}
|
|
2095
2028
|
/**
|
|
2096
2029
|
* faf_dna - Show your FAF DNA journey
|
|
2097
|
-
* Displays evolution from birth to
|
|
2030
|
+
* Displays the score evolution from birth to today (e.g. 22% → 85% → 100%)
|
|
2098
2031
|
*/
|
|
2099
2032
|
async handleFafDna(args) {
|
|
2100
2033
|
const cwd = this.getProjectPath(args?.path);
|
|
@@ -2113,11 +2046,10 @@ ${fafData.stack_signature || 'Auto-detected stack'}
|
|
|
2113
2046
|
}]
|
|
2114
2047
|
};
|
|
2115
2048
|
}
|
|
2116
|
-
// .faf exists but no DNA - create initial DNA
|
|
2117
|
-
|
|
2118
|
-
const
|
|
2119
|
-
const
|
|
2120
|
-
const currentScore = this.calculateSimpleScore(fafData);
|
|
2049
|
+
// .faf exists but no DNA - create initial DNA. Real scorer — same
|
|
2050
|
+
// one faf_score reads — never a local pseudo-score (v3.0 fix).
|
|
2051
|
+
const { readFafRaw, scoreFafYaml } = await faf_cli_bridge_js_1.fafCli;
|
|
2052
|
+
const currentScore = scoreFafYaml(readFafRaw(fafResult.path)).score;
|
|
2121
2053
|
const dna = {
|
|
2122
2054
|
birthCertificate: {
|
|
2123
2055
|
born: new Date().toISOString(),
|
|
@@ -2149,8 +2081,24 @@ ${fafData.stack_signature || 'Auto-detected stack'}
|
|
|
2149
2081
|
const dna = JSON.parse(dnaContent);
|
|
2150
2082
|
// Build journey string
|
|
2151
2083
|
const birthScore = dna.birthCertificate?.birthDNA || 0;
|
|
2152
|
-
|
|
2084
|
+
let currentScore = dna.current?.score || 0;
|
|
2153
2085
|
const milestones = dna.milestones || [];
|
|
2086
|
+
dna.milestones = milestones;
|
|
2087
|
+
// "Current" is the live score of the file on disk, not whatever was
|
|
2088
|
+
// stored last time. Nothing else records growth into .faf-dna, so this
|
|
2089
|
+
// is where the journey gets its next point.
|
|
2090
|
+
const fafResult = await (0, faf_file_finder_js_1.findFafFile)(cwd);
|
|
2091
|
+
if (fafResult) {
|
|
2092
|
+
const { readFafRaw, scoreFafYaml } = await faf_cli_bridge_js_1.fafCli;
|
|
2093
|
+
const live = scoreFafYaml(readFafRaw(fafResult.path)).score;
|
|
2094
|
+
if (live !== currentScore) {
|
|
2095
|
+
const now = new Date().toISOString();
|
|
2096
|
+
dna.current = { ...(dna.current ?? {}), score: live, lastSync: now };
|
|
2097
|
+
milestones.push({ type: 'sync', score: live, date: now, version: dna.current.version ?? 'v1.0.0' });
|
|
2098
|
+
fs.writeFileSync(dnaPath, JSON.stringify(dna, null, 2));
|
|
2099
|
+
currentScore = live;
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2154
2102
|
// Find key milestones
|
|
2155
2103
|
const _birth = milestones.find((m) => m.type === 'birth');
|
|
2156
2104
|
const peak = milestones.find((m) => m.type === 'peak');
|
|
@@ -2192,8 +2140,8 @@ ${fafData.stack_signature || 'Auto-detected stack'}
|
|
|
2192
2140
|
}
|
|
2193
2141
|
output += `\n🧬 MILESTONES\n`;
|
|
2194
2142
|
const milestoneIcons = {
|
|
2195
|
-
birth: '🐣', first_save: '💾', doubled: '2️⃣',
|
|
2196
|
-
championship: '
|
|
2143
|
+
birth: '🐣', first_save: '💾', doubled: '2️⃣', sync: '🔄',
|
|
2144
|
+
championship: '✪', elite: '⭐', peak: '🏔️', perfect: '💎'
|
|
2197
2145
|
};
|
|
2198
2146
|
for (const m of milestones) {
|
|
2199
2147
|
const icon = milestoneIcons[m.type] || '📍';
|
|
@@ -2242,7 +2190,7 @@ ${fafData.stack_signature || 'Auto-detected stack'}
|
|
|
2242
2190
|
};
|
|
2243
2191
|
}
|
|
2244
2192
|
// Format human-readable output
|
|
2245
|
-
let output = `😽 TURBO-CAT™ Format Discovery
|
|
2193
|
+
let output = `😽 TURBO-CAT™ Format Discovery\n`;
|
|
2246
2194
|
output += `═══════════════════════════════════════════════════\n\n`;
|
|
2247
2195
|
output += `✅ Found ${analysis.discoveredFormats.length} formats in ${elapsed}ms!\n\n`;
|
|
2248
2196
|
output += `📋 Discovered Formats (A-Z):\n`;
|
|
@@ -2251,7 +2199,7 @@ ${fafData.stack_signature || 'Auto-detected stack'}
|
|
|
2251
2199
|
output += ` ✅ ${format.fileName}\n`;
|
|
2252
2200
|
}
|
|
2253
2201
|
output += `\n💡 Stack Signature: ${analysis.stackSignature}\n`;
|
|
2254
|
-
output +=
|
|
2202
|
+
output += `🧠 Intelligence Score: ${analysis.totalIntelligenceScore}\n\n`;
|
|
2255
2203
|
if (Object.keys(analysis.slotFillRecommendations).length > 0) {
|
|
2256
2204
|
output += `📊 Recommended Slot Fills:\n`;
|
|
2257
2205
|
for (const [key, value] of Object.entries(analysis.slotFillRecommendations)) {
|
|
@@ -2271,33 +2219,6 @@ ${fafData.stack_signature || 'Auto-detected stack'}
|
|
|
2271
2219
|
}
|
|
2272
2220
|
}
|
|
2273
2221
|
/**
|
|
2274
|
-
* Internal helper: Calculate simple score from .faf data
|
|
2275
|
-
*/
|
|
2276
|
-
calculateSimpleScore(fafData) {
|
|
2277
|
-
let score = 0;
|
|
2278
|
-
const maxScore = 100;
|
|
2279
|
-
// Project section (30 points)
|
|
2280
|
-
if (fafData.project)
|
|
2281
|
-
score += 15;
|
|
2282
|
-
if (fafData.project?.goal || fafData.description)
|
|
2283
|
-
score += 15;
|
|
2284
|
-
// Human context (30 points)
|
|
2285
|
-
const humanContext = fafData.human_context || {};
|
|
2286
|
-
const wFields = ['who', 'what', 'why', 'where', 'when', 'how'];
|
|
2287
|
-
const filledW = wFields.filter(f => humanContext[f] && humanContext[f] !== 'null').length;
|
|
2288
|
-
score += Math.round((filledW / wFields.length) * 30);
|
|
2289
|
-
// Stack section (20 points)
|
|
2290
|
-
const stack = fafData.stack || {};
|
|
2291
|
-
const stackFields = ['frontend', 'backend', 'database', 'hosting', 'build'];
|
|
2292
|
-
const filledStack = stackFields.filter(f => stack[f] && stack[f] !== 'None').length;
|
|
2293
|
-
score += Math.round((filledStack / stackFields.length) * 20);
|
|
2294
|
-
// Files exist bonus (20 points)
|
|
2295
|
-
if (fafData.initialized_by || fafData.generated)
|
|
2296
|
-
score += 10;
|
|
2297
|
-
if (fafData.stack_signature)
|
|
2298
|
-
score += 10;
|
|
2299
|
-
return Math.min(score, maxScore);
|
|
2300
|
-
}
|
|
2301
2222
|
/**
|
|
2302
2223
|
* faf_quick - Lightning-fast .faf creation
|
|
2303
2224
|
* One-liner format: "name, description, language, framework, hosting"
|
|
@@ -2357,7 +2278,7 @@ Example: "my-app, e-commerce platform"`
|
|
|
2357
2278
|
|
|
2358
2279
|
⚠️ project.faf already exists at: ${fafPath}
|
|
2359
2280
|
|
|
2360
|
-
Use force: true to overwrite, or use
|
|
2281
|
+
Use force: true to overwrite, or use faf_go / faf_human_add to modify.`
|
|
2361
2282
|
}]
|
|
2362
2283
|
};
|
|
2363
2284
|
}
|
|
@@ -2394,7 +2315,6 @@ Use force: true to overwrite, or use faf_enhance to modify.`
|
|
|
2394
2315
|
output += `✅ Created: ${fafPath}\n\n`;
|
|
2395
2316
|
output += `Next steps:\n`;
|
|
2396
2317
|
output += ` • faf_score - Check AI-readiness\n`;
|
|
2397
|
-
output += ` • faf_enhance - Improve context\n`;
|
|
2398
2318
|
output += ` • faf_go - Guided interview to 100%`;
|
|
2399
2319
|
return { content: [{ type: 'text', text: output }] };
|
|
2400
2320
|
}
|
|
@@ -2488,7 +2408,7 @@ Use force: true to overwrite, or use faf_enhance to modify.`
|
|
|
2488
2408
|
results.push({
|
|
2489
2409
|
status: 'error',
|
|
2490
2410
|
message: '.faf file is empty',
|
|
2491
|
-
fix: 'Run: faf_init with force option to
|
|
2411
|
+
fix: 'Run: faf_init with force option to rewrite it'
|
|
2492
2412
|
});
|
|
2493
2413
|
}
|
|
2494
2414
|
else {
|
|
@@ -2502,7 +2422,7 @@ Use force: true to overwrite, or use faf_enhance to modify.`
|
|
|
2502
2422
|
results.push({
|
|
2503
2423
|
status: 'warning',
|
|
2504
2424
|
message: `Missing important fields: ${missingFields.join(', ')}`,
|
|
2505
|
-
fix: 'Run:
|
|
2425
|
+
fix: 'Run: faf_go to add missing info'
|
|
2506
2426
|
});
|
|
2507
2427
|
}
|
|
2508
2428
|
else {
|
|
@@ -2511,26 +2431,31 @@ Use force: true to overwrite, or use faf_enhance to modify.`
|
|
|
2511
2431
|
message: '.faf structure is valid'
|
|
2512
2432
|
});
|
|
2513
2433
|
}
|
|
2514
|
-
// Check 4: Score
|
|
2515
|
-
|
|
2434
|
+
// Check 4: Score — real scorer (same one faf_score reads), never
|
|
2435
|
+
// a local pseudo-score (v3.0 fix). Slot breakdown included so
|
|
2436
|
+
// faf_doctor's "score could be better" fix advice is actionable.
|
|
2437
|
+
const { scoreFafYaml } = await faf_cli_bridge_js_1.fafCli;
|
|
2438
|
+
const scoreResult = scoreFafYaml(content);
|
|
2439
|
+
const score = scoreResult.score;
|
|
2440
|
+
const slotSummary = `${scoreResult.populated}/${scoreResult.active} slots populated`;
|
|
2516
2441
|
if (score < 30) {
|
|
2517
2442
|
results.push({
|
|
2518
2443
|
status: 'error',
|
|
2519
|
-
message: `Score too low: ${score}
|
|
2520
|
-
fix: 'Run:
|
|
2444
|
+
message: `Score too low: ${score}% (${slotSummary})`,
|
|
2445
|
+
fix: 'Run: faf_go to improve context'
|
|
2521
2446
|
});
|
|
2522
2447
|
}
|
|
2523
2448
|
else if (score < 70) {
|
|
2524
2449
|
results.push({
|
|
2525
2450
|
status: 'warning',
|
|
2526
|
-
message: `Score could be better: ${score}
|
|
2451
|
+
message: `Score could be better: ${score}% (${slotSummary})`,
|
|
2527
2452
|
fix: 'Target 70%+ for championship AI context'
|
|
2528
2453
|
});
|
|
2529
2454
|
}
|
|
2530
2455
|
else {
|
|
2531
2456
|
results.push({
|
|
2532
2457
|
status: 'ok',
|
|
2533
|
-
message: `Great score: ${score}
|
|
2458
|
+
message: `Great score: ${score}% (${slotSummary})`
|
|
2534
2459
|
});
|
|
2535
2460
|
}
|
|
2536
2461
|
}
|
|
@@ -2539,7 +2464,7 @@ Use force: true to overwrite, or use faf_enhance to modify.`
|
|
|
2539
2464
|
results.push({
|
|
2540
2465
|
status: 'error',
|
|
2541
2466
|
message: '.faf file is corrupted or invalid YAML',
|
|
2542
|
-
fix: 'Run: faf_init with force option to
|
|
2467
|
+
fix: 'Run: faf_init with force option to rewrite it'
|
|
2543
2468
|
});
|
|
2544
2469
|
}
|
|
2545
2470
|
}
|
|
@@ -2549,13 +2474,13 @@ Use force: true to overwrite, or use faf_enhance to modify.`
|
|
|
2549
2474
|
results.push({
|
|
2550
2475
|
status: 'warning',
|
|
2551
2476
|
message: 'No CLAUDE.md file',
|
|
2552
|
-
fix: 'Run: faf_auto or
|
|
2477
|
+
fix: 'Run: faf_auto or faf_claude to write CLAUDE.md'
|
|
2553
2478
|
});
|
|
2554
2479
|
}
|
|
2555
2480
|
else {
|
|
2556
2481
|
results.push({
|
|
2557
2482
|
status: 'ok',
|
|
2558
|
-
message: 'CLAUDE.md found
|
|
2483
|
+
message: 'CLAUDE.md found'
|
|
2559
2484
|
});
|
|
2560
2485
|
}
|
|
2561
2486
|
// Check 6: Project detection
|
|
@@ -2613,7 +2538,7 @@ Use force: true to overwrite, or use faf_enhance to modify.`
|
|
|
2613
2538
|
}
|
|
2614
2539
|
output += `\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n`;
|
|
2615
2540
|
if (!hasErrors && !hasWarnings) {
|
|
2616
|
-
output +=
|
|
2541
|
+
output += `✅ Perfect health! Your FAF setup is championship-ready!`;
|
|
2617
2542
|
}
|
|
2618
2543
|
else if (!hasErrors) {
|
|
2619
2544
|
output += `🎯 Good health with minor improvements suggested.`;
|
|
@@ -2631,7 +2556,7 @@ Use force: true to overwrite, or use faf_enhance to modify.`
|
|
|
2631
2556
|
}
|
|
2632
2557
|
}
|
|
2633
2558
|
// ============================================================================
|
|
2634
|
-
//
|
|
2559
|
+
// INTEROP HANDLERS (faf-mcp 2.0.0; ported from faf-cli 4.5.0 via claude-faf-mcp 4.5.0)
|
|
2635
2560
|
// ============================================================================
|
|
2636
2561
|
async handleFafAgents(args) {
|
|
2637
2562
|
const cwd = this.getProjectPath(args?.path);
|
|
@@ -2769,7 +2694,7 @@ Use force: true to overwrite, or use faf_enhance to modify.`
|
|
|
2769
2694
|
let output = `GitHub Context:\n\n✅ ${data?.message || 'Done'}\n⏱️ ${result.duration}ms`;
|
|
2770
2695
|
// Include generated .faf content if no output path (preview mode)
|
|
2771
2696
|
if (!outputPath && data?.data?.fafContent) {
|
|
2772
|
-
output += `\n\n---
|
|
2697
|
+
output += `\n\n--- Authored project.faf ---\n${data.data.fafContent}`;
|
|
2773
2698
|
}
|
|
2774
2699
|
return {
|
|
2775
2700
|
content: [{ type: 'text', text: output }]
|