faf-mcp 3.0.1 ā 4.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 +30 -1
- package/CLAUDE.md +2 -2
- package/README.md +36 -18
- package/dist/src/cli.js +2 -1
- package/dist/src/cli.js.map +1 -1
- package/dist/src/faf-core/commands/agents.js +19 -5
- package/dist/src/faf-core/commands/agents.js.map +1 -1
- package/dist/src/faf-core/commands/claude.d.ts +0 -1
- package/dist/src/faf-core/commands/claude.js +1 -2
- package/dist/src/faf-core/commands/claude.js.map +1 -1
- package/dist/src/faf-core/commands/conductor.js +35 -10
- package/dist/src/faf-core/commands/conductor.js.map +1 -1
- package/dist/src/faf-core/commands/cursor.js +19 -5
- package/dist/src/faf-core/commands/cursor.js.map +1 -1
- package/dist/src/faf-core/commands/gemini.js +19 -5
- package/dist/src/faf-core/commands/gemini.js.map +1 -1
- package/dist/src/faf-core/commands/git-context.d.ts +12 -3
- package/dist/src/faf-core/commands/git-context.js +28 -6
- package/dist/src/faf-core/commands/git-context.js.map +1 -1
- package/dist/src/faf-core/commands/sync.d.ts +8 -0
- package/dist/src/faf-core/commands/sync.js +20 -23
- package/dist/src/faf-core/commands/sync.js.map +1 -1
- package/dist/src/faf-core/fix-once/yaml.js +12 -13
- package/dist/src/faf-core/fix-once/yaml.js.map +1 -1
- package/dist/src/faf-core/parsers/faf-git-generator.d.ts +0 -1
- package/dist/src/faf-core/parsers/faf-git-generator.js +13 -34
- package/dist/src/faf-core/parsers/faf-git-generator.js.map +1 -1
- package/dist/src/handlers/engine-adapter.js +5 -3
- package/dist/src/handlers/engine-adapter.js.map +1 -1
- package/dist/src/handlers/tools.js +95 -80
- package/dist/src/handlers/tools.js.map +1 -1
- package/package.json +6 -6
- package/project.faf +2 -2
- package/scripts/sync-version.js +2 -1
- package/skills/faf-ide/SKILL.md +4 -4
|
@@ -191,7 +191,7 @@ class FafToolHandler {
|
|
|
191
191
|
},
|
|
192
192
|
{
|
|
193
193
|
name: 'faf_trust',
|
|
194
|
-
description: 'Validate a project.faf
|
|
194
|
+
description: 'Validate a project.faf\'s required fields (faf_version, project.name) and about.* block with faf-cli\'s validator, and report its real score.',
|
|
195
195
|
annotations: {
|
|
196
196
|
title: 'Trust Score',
|
|
197
197
|
readOnlyHint: true,
|
|
@@ -244,7 +244,7 @@ class FafToolHandler {
|
|
|
244
244
|
},
|
|
245
245
|
{
|
|
246
246
|
name: 'faf_clear',
|
|
247
|
-
description: 'Remove
|
|
247
|
+
description: 'Remove ~/.faf-cli-cache/technical-credit.json, the cache file left by faf-mcp versions before 3.0. Other files in ~/.faf-cli-cache are left in place.',
|
|
248
248
|
annotations: {
|
|
249
249
|
title: 'Clear .faf Data',
|
|
250
250
|
readOnlyHint: false,
|
|
@@ -254,8 +254,8 @@ class FafToolHandler {
|
|
|
254
254
|
inputSchema: {
|
|
255
255
|
type: 'object',
|
|
256
256
|
properties: {
|
|
257
|
-
cache: { type: 'boolean', description: 'Remove ~/.faf-cli-cache (written by faf-mcp < 3.0)' },
|
|
258
|
-
all: { type: 'boolean', description: 'Remove everything older versions
|
|
257
|
+
cache: { type: 'boolean', description: 'Remove ~/.faf-cli-cache/technical-credit.json (written by faf-mcp < 3.0)' },
|
|
258
|
+
all: { type: 'boolean', description: 'Remove everything older versions left behind (default; today that is technical-credit.json)' }
|
|
259
259
|
},
|
|
260
260
|
additionalProperties: false
|
|
261
261
|
}
|
|
@@ -276,9 +276,9 @@ class FafToolHandler {
|
|
|
276
276
|
},
|
|
277
277
|
{
|
|
278
278
|
name: 'faf_read',
|
|
279
|
-
description: 'Read a file within the
|
|
279
|
+
description: 'Read a file within the allowed roots (cwd, the OS temp dir, or FAF_ALLOWED_ROOTS). Paths outside them are refused.',
|
|
280
280
|
annotations: {
|
|
281
|
-
title: 'Read
|
|
281
|
+
title: 'Read Project File',
|
|
282
282
|
readOnlyHint: true,
|
|
283
283
|
openWorldHint: false
|
|
284
284
|
},
|
|
@@ -296,11 +296,11 @@ class FafToolHandler {
|
|
|
296
296
|
},
|
|
297
297
|
{
|
|
298
298
|
name: 'faf_write',
|
|
299
|
-
description: 'Write a file within the
|
|
299
|
+
description: 'Write a file within the allowed roots (cwd, the OS temp dir, or FAF_ALLOWED_ROOTS). Paths outside them are refused.',
|
|
300
300
|
annotations: {
|
|
301
|
-
title: 'Write
|
|
301
|
+
title: 'Write Project File',
|
|
302
302
|
readOnlyHint: false,
|
|
303
|
-
destructiveHint:
|
|
303
|
+
destructiveHint: true,
|
|
304
304
|
openWorldHint: false
|
|
305
305
|
},
|
|
306
306
|
inputSchema: {
|
|
@@ -323,7 +323,7 @@ class FafToolHandler {
|
|
|
323
323
|
name: 'faf_list',
|
|
324
324
|
description: 'List directories and discover projects with project.faf files - Essential for FAF discovery workflow',
|
|
325
325
|
annotations: {
|
|
326
|
-
title: 'List
|
|
326
|
+
title: 'List Directories',
|
|
327
327
|
readOnlyHint: true,
|
|
328
328
|
openWorldHint: false
|
|
329
329
|
},
|
|
@@ -414,7 +414,7 @@ class FafToolHandler {
|
|
|
414
414
|
},
|
|
415
415
|
{
|
|
416
416
|
name: 'faf_check',
|
|
417
|
-
description: 'Inspect the human_context fields and rate each empty/generic/good
|
|
417
|
+
description: 'Inspect the human_context fields and rate each empty/generic/good. Returns the ratings; with protect it records the good fields in _protected_fields as an advisory list (other tools do not enforce it), with unlock it clears that list. Use this to gauge context quality.',
|
|
418
418
|
annotations: {
|
|
419
419
|
title: 'Check .faf Health',
|
|
420
420
|
readOnlyHint: false,
|
|
@@ -424,8 +424,8 @@ class FafToolHandler {
|
|
|
424
424
|
inputSchema: {
|
|
425
425
|
type: 'object',
|
|
426
426
|
properties: {
|
|
427
|
-
protect: { type: 'boolean', description: '
|
|
428
|
-
unlock: { type: 'boolean', description: '
|
|
427
|
+
protect: { type: 'boolean', description: 'Record good fields in _protected_fields (advisory; other tools do not enforce it)' },
|
|
428
|
+
unlock: { type: 'boolean', description: 'Clear the _protected_fields list' },
|
|
429
429
|
path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
|
|
430
430
|
},
|
|
431
431
|
additionalProperties: false
|
|
@@ -433,7 +433,7 @@ class FafToolHandler {
|
|
|
433
433
|
},
|
|
434
434
|
{
|
|
435
435
|
name: 'faf_context',
|
|
436
|
-
description: 'Set or show the active project path that later faf_
|
|
436
|
+
description: 'Set or show the active project path that later faf_ project tools resolve against (faf_read and faf_write resolve relative paths against the server\'s working directory instead). Returns the current context path. Call this once at the start of a session so the project tools target the right project.',
|
|
437
437
|
annotations: {
|
|
438
438
|
title: 'View Context',
|
|
439
439
|
readOnlyHint: true,
|
|
@@ -471,7 +471,7 @@ class FafToolHandler {
|
|
|
471
471
|
},
|
|
472
472
|
{
|
|
473
473
|
name: 'faf_auto',
|
|
474
|
-
description: 'Run the
|
|
474
|
+
description: 'Run the setup pipeline in one call ā init or merge, stack detection, CLAUDE.md, and score ā taking a project from no context to a scored project.faf plus CLAUDE.md; faf_go closes the human slots. 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.',
|
|
475
475
|
annotations: {
|
|
476
476
|
title: 'Auto-detect Context',
|
|
477
477
|
readOnlyHint: false,
|
|
@@ -491,10 +491,11 @@ class FafToolHandler {
|
|
|
491
491
|
},
|
|
492
492
|
{
|
|
493
493
|
name: 'faf_dna',
|
|
494
|
-
description: 'Show your FAF DNA journey ā the score evolution of this project.faf from birth to today (e.g. 22% ā 85% ā 100%)',
|
|
494
|
+
description: 'Show and record your FAF DNA journey ā the score evolution of this project.faf from birth to today (e.g. 22% ā 85% ā 100%). Creates .faf-dna on the first call and appends a milestone when the score changes.',
|
|
495
495
|
annotations: {
|
|
496
496
|
title: 'View Project DNA',
|
|
497
|
-
readOnlyHint:
|
|
497
|
+
readOnlyHint: false,
|
|
498
|
+
destructiveHint: false,
|
|
498
499
|
openWorldHint: false
|
|
499
500
|
},
|
|
500
501
|
inputSchema: {
|
|
@@ -507,7 +508,7 @@ class FafToolHandler {
|
|
|
507
508
|
},
|
|
508
509
|
{
|
|
509
510
|
name: 'faf_formats',
|
|
510
|
-
description: 'Scan the project for tech-stack signals across 150+ manifest types (package.json, Cargo.toml, pyproject.toml, go.mod, and more)
|
|
511
|
+
description: 'Scan the project for tech-stack signals across 150+ manifest types (package.json, Cargo.toml, pyproject.toml, go.mod, and more). Returns the detected formats, the stack signature and faf-cli\'s slot-fill hints. Use this to see what stack detection finds.',
|
|
511
512
|
annotations: {
|
|
512
513
|
title: 'List Formats',
|
|
513
514
|
readOnlyHint: true,
|
|
@@ -544,7 +545,7 @@ class FafToolHandler {
|
|
|
544
545
|
},
|
|
545
546
|
{
|
|
546
547
|
name: 'faf_doctor',
|
|
547
|
-
description: 'Diagnose a project.faf setup ā report missing files,
|
|
548
|
+
description: 'Diagnose a project.faf setup ā report missing files, the real score with populated/active slot counts, and common configuration issues, each with a fix. Returns a checklist. Use this when your score is low and you need to know why.',
|
|
548
549
|
annotations: {
|
|
549
550
|
title: 'Diagnose Issues',
|
|
550
551
|
readOnlyHint: true,
|
|
@@ -574,8 +575,8 @@ class FafToolHandler {
|
|
|
574
575
|
type: 'object',
|
|
575
576
|
properties: {
|
|
576
577
|
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)' },
|
|
577
|
-
force: { type: 'boolean', description: '
|
|
578
|
-
merge: { type: 'boolean', description: '
|
|
578
|
+
force: { type: 'boolean', description: 'Update the faf-managed block in an existing file (content outside it is preserved)' },
|
|
579
|
+
merge: { type: 'boolean', description: 'Write the imported data into project.faf (without it, import only reports the section count and writes nothing)' },
|
|
579
580
|
path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
|
|
580
581
|
},
|
|
581
582
|
required: ['action'],
|
|
@@ -595,8 +596,8 @@ class FafToolHandler {
|
|
|
595
596
|
type: 'object',
|
|
596
597
|
properties: {
|
|
597
598
|
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)' },
|
|
598
|
-
force: { type: 'boolean', description: '
|
|
599
|
-
merge: { type: 'boolean', description: '
|
|
599
|
+
force: { type: 'boolean', description: 'Update the faf-managed block in an existing file (content outside it is preserved)' },
|
|
600
|
+
merge: { type: 'boolean', description: 'Write the imported data into project.faf (without it, import only reports the section count and writes nothing)' },
|
|
600
601
|
path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
|
|
601
602
|
},
|
|
602
603
|
required: ['action'],
|
|
@@ -616,8 +617,8 @@ class FafToolHandler {
|
|
|
616
617
|
type: 'object',
|
|
617
618
|
properties: {
|
|
618
619
|
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)' },
|
|
619
|
-
force: { type: 'boolean', description: '
|
|
620
|
-
merge: { type: 'boolean', description: '
|
|
620
|
+
force: { type: 'boolean', description: 'Update the faf-managed block in an existing file (content outside it is preserved)' },
|
|
621
|
+
merge: { type: 'boolean', description: 'Write the imported data into project.faf (without it, import only reports the section count and writes nothing)' },
|
|
621
622
|
path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
|
|
622
623
|
},
|
|
623
624
|
required: ['action'],
|
|
@@ -630,7 +631,7 @@ class FafToolHandler {
|
|
|
630
631
|
annotations: {
|
|
631
632
|
title: 'Sync Conductor',
|
|
632
633
|
readOnlyHint: false,
|
|
633
|
-
destructiveHint:
|
|
634
|
+
destructiveHint: true,
|
|
634
635
|
openWorldHint: false
|
|
635
636
|
},
|
|
636
637
|
inputSchema: {
|
|
@@ -638,7 +639,7 @@ class FafToolHandler {
|
|
|
638
639
|
properties: {
|
|
639
640
|
action: { type: 'string', enum: ['import', 'export'], description: 'Action: import (conductor/ -> .faf), export (.faf -> conductor/)' },
|
|
640
641
|
force: { type: 'boolean', description: 'Force overwrite existing files' },
|
|
641
|
-
merge: { type: 'boolean', description: '
|
|
642
|
+
merge: { type: 'boolean', description: 'Write the imported data into project.faf (without it, import only reports the section count and writes nothing)' },
|
|
642
643
|
path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
|
|
643
644
|
},
|
|
644
645
|
required: ['action'],
|
|
@@ -651,14 +652,15 @@ class FafToolHandler {
|
|
|
651
652
|
annotations: {
|
|
652
653
|
title: 'Extract from GitHub',
|
|
653
654
|
readOnlyHint: false,
|
|
654
|
-
destructiveHint:
|
|
655
|
+
destructiveHint: true,
|
|
655
656
|
openWorldHint: true
|
|
656
657
|
},
|
|
657
658
|
inputSchema: {
|
|
658
659
|
type: 'object',
|
|
659
660
|
properties: {
|
|
660
661
|
url: { type: 'string', description: 'GitHub repository URL (e.g., https://github.com/owner/repo or owner/repo)' },
|
|
661
|
-
path: { type: 'string', description: 'Output directory for the authored project.faf. If omitted, returns content without writing.' }
|
|
662
|
+
path: { type: 'string', description: 'Output directory for the authored project.faf. If omitted, returns content without writing.' },
|
|
663
|
+
force: { type: 'boolean', description: 'Overwrite an existing project.faf at path (without it, faf_git refuses)' }
|
|
662
664
|
},
|
|
663
665
|
required: ['url'],
|
|
664
666
|
additionalProperties: false
|
|
@@ -817,7 +819,7 @@ class FafToolHandler {
|
|
|
817
819
|
}
|
|
818
820
|
// Strip ANSI from tier indicator (faf-cli emits colored glyphs).
|
|
819
821
|
// eslint-disable-next-line no-control-regex
|
|
820
|
-
const strip = (s) => s.replace(/\[[0-9;]*m/g, '').trim();
|
|
822
|
+
const strip = (s) => s.replace(/\x1b\[[0-9;]*m/g, '').trim();
|
|
821
823
|
let raw;
|
|
822
824
|
try {
|
|
823
825
|
raw = readFafRaw(fafPath);
|
|
@@ -885,7 +887,7 @@ class FafToolHandler {
|
|
|
885
887
|
output += `Empty (${emptySlots.length}): ${emptySlots.join(', ') || '(none)'}\n`;
|
|
886
888
|
output += `Ignored (${ignoredSlots.length}): ${ignoredSlots.join(', ') || '(none)'}`;
|
|
887
889
|
if (score < 100 && emptySlots.length > 0) {
|
|
888
|
-
output += `\n\nTip: fill empty slots or mark them \`slotignored\` to climb tiers
|
|
890
|
+
output += `\n\nTip: fill empty slots or mark them \`slotignored\` to climb tiers.`;
|
|
889
891
|
}
|
|
890
892
|
}
|
|
891
893
|
return {
|
|
@@ -997,7 +999,7 @@ class FafToolHandler {
|
|
|
997
999
|
}
|
|
998
1000
|
const validation = validateFaf(data);
|
|
999
1001
|
let output = validation.valid
|
|
1000
|
-
? `ā
|
|
1002
|
+
? `ā
Required fields present (faf_version, project.name) ā no validation errors.`
|
|
1001
1003
|
: `ā ${validation.errors.length} issue${validation.errors.length === 1 ? '' : 's'} found:\n` +
|
|
1002
1004
|
validation.errors.map((e) => ` ⢠${e}`).join('\n');
|
|
1003
1005
|
try {
|
|
@@ -1032,9 +1034,16 @@ class FafToolHandler {
|
|
|
1032
1034
|
isError: true
|
|
1033
1035
|
};
|
|
1034
1036
|
}
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1037
|
+
// A dry run carries the fields it would change: list them by name, so the
|
|
1038
|
+
// agent sees what `apply: true` would write instead of a bare count.
|
|
1039
|
+
const changes = result.data?.changes;
|
|
1040
|
+
const shown = (v) => v === undefined || v === null || v === '' ? '(empty)' : typeof v === 'string' ? v : JSON.stringify(v);
|
|
1041
|
+
const output = Array.isArray(changes) && changes.length > 0
|
|
1042
|
+
? `${result.data.message}\n` +
|
|
1043
|
+
changes.map((c) => `⢠${c.path}: ${shown(c.oldValue)} ā ${shown(c.newValue)}`).join('\n')
|
|
1044
|
+
: typeof result.data === 'string'
|
|
1045
|
+
? result.data
|
|
1046
|
+
: result.data?.output || JSON.stringify(result.data, null, 2);
|
|
1038
1047
|
return {
|
|
1039
1048
|
content: [{
|
|
1040
1049
|
type: 'text',
|
|
@@ -1075,9 +1084,13 @@ class FafToolHandler {
|
|
|
1075
1084
|
isError: true
|
|
1076
1085
|
};
|
|
1077
1086
|
}
|
|
1087
|
+
// The command's message (it carries "FAF Score: N%") plus the files it
|
|
1088
|
+
// wrote, one per line ā not the raw result object.
|
|
1089
|
+
const filesChanged = Array.isArray(result.data?.filesChanged) ? result.data.filesChanged : [];
|
|
1078
1090
|
const output = typeof result.data === 'string'
|
|
1079
1091
|
? result.data
|
|
1080
|
-
: result.data?.
|
|
1092
|
+
: `${result.data?.message ?? ''}` +
|
|
1093
|
+
(filesChanged.length > 0 ? `\n\nFiles written:\n${filesChanged.map((f) => `⢠${f}`).join('\n')}` : '');
|
|
1081
1094
|
return {
|
|
1082
1095
|
content: [{
|
|
1083
1096
|
type: 'text',
|
|
@@ -1158,7 +1171,7 @@ SPEEDY AI you can TRUST!
|
|
|
1158
1171
|
|
|
1159
1172
|
Version ${packageInfo.version}
|
|
1160
1173
|
|
|
1161
|
-
Just like JPEG makes images
|
|
1174
|
+
Just like JPEG makes images portable,
|
|
1162
1175
|
.faf makes projects AI-readable.
|
|
1163
1176
|
|
|
1164
1177
|
HOW IT WORKS:
|
|
@@ -1185,7 +1198,7 @@ WHAT: .faf = Foundational AI-context Format
|
|
|
1185
1198
|
WHY: Just like JPEG makes images viewable everywhere,
|
|
1186
1199
|
.faf makes projects understandable by AI.
|
|
1187
1200
|
|
|
1188
|
-
HOW: Run
|
|
1201
|
+
HOW: Run faf_init or faf_auto on any project to create one.
|
|
1189
1202
|
Run 'faf_score' to check AI-readiness (target: 100%).
|
|
1190
1203
|
|
|
1191
1204
|
REMEMBER: Always use ".faf" with the dot - it's a FORMAT!
|
|
@@ -1201,7 +1214,6 @@ REMEMBER: Always use ".faf" with the dot - it's a FORMAT!
|
|
|
1201
1214
|
async handleFafDebug(_args) {
|
|
1202
1215
|
try {
|
|
1203
1216
|
const fs = await import('fs');
|
|
1204
|
-
const path = await import('path');
|
|
1205
1217
|
const cwd = this.engineAdapter.getWorkingDirectory();
|
|
1206
1218
|
const debugInfo = {
|
|
1207
1219
|
workingDirectory: cwd,
|
|
@@ -1212,11 +1224,10 @@ REMEMBER: Always use ".faf" with the dot - it's a FORMAT!
|
|
|
1212
1224
|
permissions: {},
|
|
1213
1225
|
enginePath: this.engineAdapter.getEnginePath(),
|
|
1214
1226
|
};
|
|
1215
|
-
// Check write permissions
|
|
1227
|
+
// Check write permissions ā an access check, never a probe file in the
|
|
1228
|
+
// user's workspace.
|
|
1216
1229
|
try {
|
|
1217
|
-
|
|
1218
|
-
fs.writeFileSync(testFile, 'test');
|
|
1219
|
-
fs.unlinkSync(testFile);
|
|
1230
|
+
fs.accessSync(cwd, fs.constants.W_OK);
|
|
1220
1231
|
debugInfo.canWrite = true;
|
|
1221
1232
|
}
|
|
1222
1233
|
catch (error) {
|
|
@@ -1228,7 +1239,7 @@ REMEMBER: Always use ".faf" with the dot - it's a FORMAT!
|
|
|
1228
1239
|
const debugOutput = `š faf-mcp Debug Information:
|
|
1229
1240
|
|
|
1230
1241
|
š Working Directory: ${debugInfo.workingDirectory}
|
|
1231
|
-
āļø Write Permissions: ${debugInfo.canWrite ? 'ā
|
|
1242
|
+
āļø Write Permissions: ${debugInfo.canWrite ? 'ā
writable' : 'ā not writable'}
|
|
1232
1243
|
${debugInfo.permissions.writeError ? ` Error: ${debugInfo.permissions.writeError}\n` : ''}š¤ FAF Engine Path: ${debugInfo.enginePath}
|
|
1233
1244
|
š faf-cli (bundled): ${debugInfo.fafVersion ? `v${debugInfo.fafVersion}` : 'ā not found in node_modules ā reinstall faf-mcp'}
|
|
1234
1245
|
š FAF File: ${hasFaf ? `ā
${fafResult.filename} exists` : 'ā Not found (run faf_init)'}
|
|
@@ -1284,22 +1295,18 @@ ${debugInfo.permissions.writeError ? ` Error: ${debugInfo.permissions.writeErr
|
|
|
1284
1295
|
- Confirm: "Creating at ~/Projects/heritage-club-dubai/"
|
|
1285
1296
|
|
|
1286
1297
|
## Real Filesystem Only
|
|
1287
|
-
- ā
|
|
1298
|
+
- ā
\`~/Projects/my-app/\`
|
|
1288
1299
|
- ā \`/mnt/user-data/\` (container paths)
|
|
1289
1300
|
- ā \`/home/claude/\` (container paths)
|
|
1290
1301
|
|
|
1291
1302
|
## Commands
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
- \`
|
|
1296
|
-
- \`
|
|
1297
|
-
- \`faf
|
|
1298
|
-
- \`faf
|
|
1299
|
-
|
|
1300
|
-
**Extensions:**
|
|
1301
|
-
- \`new\` - force overwrite existing
|
|
1302
|
-
- \`full\` - detailed output
|
|
1303
|
+
Call these MCP tools:
|
|
1304
|
+
- \`faf_init\` (path) - create project.faf (infer path from context)
|
|
1305
|
+
- \`faf_auto\` - init or merge, stack detection, CLAUDE.md, and score in one call
|
|
1306
|
+
- \`faf_score\` - show AI-readiness
|
|
1307
|
+
- \`faf_go\` - guided questions for the human slots, to 100%
|
|
1308
|
+
- \`faf_sync\` - reconcile project.faf with package.json (dry-run; \`apply: true\` writes)
|
|
1309
|
+
- \`faf_claude\` - write CLAUDE.md from project.faf (\`all: true\` also writes AGENTS.md, .cursorrules, GEMINI.md)
|
|
1303
1310
|
|
|
1304
1311
|
## UX Rules
|
|
1305
1312
|
1. **Don't offer option menus** - just solve it
|
|
@@ -1646,7 +1653,7 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
|
|
|
1646
1653
|
return {
|
|
1647
1654
|
content: [{
|
|
1648
1655
|
type: 'text',
|
|
1649
|
-
text: `š FAF Check:\n\nā
|
|
1656
|
+
text: `š FAF Check:\n\nā
Cleared _protected_fields\nš Updated: ${fafResult.filename}`
|
|
1650
1657
|
}]
|
|
1651
1658
|
};
|
|
1652
1659
|
}
|
|
@@ -1664,14 +1671,15 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
|
|
|
1664
1671
|
for (const field of fields) {
|
|
1665
1672
|
qualities[field] = assessField(humanContext[field]);
|
|
1666
1673
|
}
|
|
1667
|
-
// Handle --protect
|
|
1674
|
+
// Handle --protect. _protected_fields is an advisory list: it is
|
|
1675
|
+
// recorded here and nothing else in faf-mcp reads it.
|
|
1668
1676
|
if (args?.protect) {
|
|
1669
|
-
const toProtect = fields.filter(f => qualities[f] === 'good'
|
|
1677
|
+
const toProtect = fields.filter(f => qualities[f] === 'good');
|
|
1670
1678
|
if (toProtect.length === 0) {
|
|
1671
1679
|
return {
|
|
1672
1680
|
content: [{
|
|
1673
1681
|
type: 'text',
|
|
1674
|
-
text: `š FAF Check:\n\nā ļø No fields
|
|
1682
|
+
text: `š FAF Check:\n\nā ļø No fields rated good ā nothing recorded in _protected_fields`
|
|
1675
1683
|
}]
|
|
1676
1684
|
};
|
|
1677
1685
|
}
|
|
@@ -1680,13 +1688,13 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
|
|
|
1680
1688
|
return {
|
|
1681
1689
|
content: [{
|
|
1682
1690
|
type: 'text',
|
|
1683
|
-
text: `š FAF Check:\n\nā
|
|
1691
|
+
text: `š FAF Check:\n\nā
Recorded ${toProtect.length} field(s) in _protected_fields: ${toProtect.join(', ')}\nā¹ļø Advisory only ā other tools do not enforce it.\nš Updated: ${fafResult.filename}`
|
|
1684
1692
|
}]
|
|
1685
1693
|
};
|
|
1686
1694
|
}
|
|
1687
1695
|
// Default: show quality report
|
|
1688
1696
|
const icons = {
|
|
1689
|
-
empty: 'ā¬', generic: 'ā»ļø', good: 'ā¼ļø'
|
|
1697
|
+
empty: 'ā¬', generic: 'ā»ļø', good: 'ā¼ļø'
|
|
1690
1698
|
};
|
|
1691
1699
|
let output = `š FAF Human Context Quality\n\n`;
|
|
1692
1700
|
for (const field of fields) {
|
|
@@ -1696,11 +1704,11 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
|
|
|
1696
1704
|
const displayValue = value.length > 40 ? value.substring(0, 37) + '...' : value;
|
|
1697
1705
|
output += `${icons[q]} ${locked} ${field.toUpperCase().padEnd(6)} ${displayValue}\n`;
|
|
1698
1706
|
}
|
|
1699
|
-
const goodCount = fields.filter(f => qualities[f] === 'good'
|
|
1707
|
+
const goodCount = fields.filter(f => qualities[f] === 'good').length;
|
|
1700
1708
|
const emptyCount = fields.filter(f => qualities[f] === 'empty').length;
|
|
1701
1709
|
output += `\nš Quality: ${Math.round((goodCount / fields.length) * 100)}%\n`;
|
|
1702
1710
|
if (protectedFields.length > 0) {
|
|
1703
|
-
output += `š
|
|
1711
|
+
output += `š _protected_fields (advisory, not enforced): ${protectedFields.join(', ')}\n`;
|
|
1704
1712
|
}
|
|
1705
1713
|
if (emptyCount > 0) {
|
|
1706
1714
|
output += `\nš” Use faf_readme or faf_human_add to fill empty slots`;
|
|
@@ -1723,7 +1731,7 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
|
|
|
1723
1731
|
return {
|
|
1724
1732
|
content: [{
|
|
1725
1733
|
type: 'text',
|
|
1726
|
-
text: `š FAF Context Set:\n\nā
Active project: ${newPath}\n${fafResult ? `ā
project.faf found: ${fafResult.filename}` : 'ā ļø No project.faf in this directory'}\n\nš”
|
|
1734
|
+
text: `š FAF Context Set:\n\nā
Active project: ${newPath}\n${fafResult ? `ā
project.faf found: ${fafResult.filename}` : 'ā ļø No project.faf in this directory'}\n\nš” Later faf_ project tools use this context (faf_read / faf_write resolve relative paths against the server's working directory)`
|
|
1727
1735
|
}]
|
|
1728
1736
|
};
|
|
1729
1737
|
}
|
|
@@ -1925,7 +1933,7 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
|
|
|
1925
1933
|
targetScore: 100,
|
|
1926
1934
|
questionsRemaining: questions.length,
|
|
1927
1935
|
questions,
|
|
1928
|
-
instructions: '
|
|
1936
|
+
instructions: 'Ask the user these questions, then call faf_go again with the answers parameter.'
|
|
1929
1937
|
}, null, 2)
|
|
1930
1938
|
}]
|
|
1931
1939
|
};
|
|
@@ -1990,7 +1998,7 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
|
|
|
1990
1998
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
1991
1999
|
// Format output
|
|
1992
2000
|
const deltaDisplay = scoreDelta > 0 ? `(+${scoreDelta}%)` : scoreDelta < 0 ? `(${scoreDelta}%)` : '(no change)';
|
|
1993
|
-
let output =
|
|
2001
|
+
let output = `ā”ļø FAF AUTO\n`;
|
|
1994
2002
|
output += `āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n\n`;
|
|
1995
2003
|
output += steps.join('\n') + '\n\n';
|
|
1996
2004
|
output += `āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n`;
|
|
@@ -2020,7 +2028,7 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
|
|
|
2020
2028
|
}
|
|
2021
2029
|
catch (error) {
|
|
2022
2030
|
return {
|
|
2023
|
-
content: [{ type: 'text', text:
|
|
2031
|
+
content: [{ type: 'text', text: `ā”ļø FAF Auto:\n\nā Error: ${error.message}` }],
|
|
2024
2032
|
isError: true
|
|
2025
2033
|
};
|
|
2026
2034
|
}
|
|
@@ -2182,10 +2190,13 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
|
|
|
2182
2190
|
const analysis = await (0, turbocat_bridge_js_1.turboCatDisplay)(cwd);
|
|
2183
2191
|
const elapsed = Date.now() - startTime;
|
|
2184
2192
|
if (args?.json) {
|
|
2193
|
+
// The sum of format priorities is not a FAF score; name it for what
|
|
2194
|
+
// it is so no second "score" reaches clients next to faf_score's.
|
|
2195
|
+
const { totalIntelligenceScore, ...rest } = analysis;
|
|
2185
2196
|
return {
|
|
2186
2197
|
content: [{
|
|
2187
2198
|
type: 'text',
|
|
2188
|
-
text: JSON.stringify(
|
|
2199
|
+
text: JSON.stringify({ ...rest, formatPriorityTotal: totalIntelligenceScore }, null, 2)
|
|
2189
2200
|
}]
|
|
2190
2201
|
};
|
|
2191
2202
|
}
|
|
@@ -2198,8 +2209,7 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
|
|
|
2198
2209
|
for (const format of sorted) {
|
|
2199
2210
|
output += ` ā
${format.fileName}\n`;
|
|
2200
2211
|
}
|
|
2201
|
-
output += `\nš” Stack Signature: ${analysis.stackSignature}\n`;
|
|
2202
|
-
output += `š§ Intelligence Score: ${analysis.totalIntelligenceScore}\n\n`;
|
|
2212
|
+
output += `\nš” Stack Signature: ${analysis.stackSignature}\n\n`;
|
|
2203
2213
|
if (Object.keys(analysis.slotFillRecommendations).length > 0) {
|
|
2204
2214
|
output += `š Recommended Slot Fills:\n`;
|
|
2205
2215
|
for (const [key, value] of Object.entries(analysis.slotFillRecommendations)) {
|
|
@@ -2244,7 +2254,7 @@ Examples:
|
|
|
2244
2254
|
"api-service, REST API for mobile app, python, fastapi, aws"
|
|
2245
2255
|
"cli-tool, developer productivity tool, go"
|
|
2246
2256
|
|
|
2247
|
-
Minimum: name and description. Rest is
|
|
2257
|
+
Minimum: name and description. Rest is optional.`
|
|
2248
2258
|
}]
|
|
2249
2259
|
};
|
|
2250
2260
|
}
|
|
@@ -2265,7 +2275,8 @@ Example: "my-app, e-commerce platform"`
|
|
|
2265
2275
|
}
|
|
2266
2276
|
const projectName = parts[0] || 'my-project';
|
|
2267
2277
|
const projectGoal = parts[1] || 'Build amazing software';
|
|
2268
|
-
|
|
2278
|
+
// Only what the user gave: no language is written unless they named one.
|
|
2279
|
+
const mainLanguage = parts[2] || undefined;
|
|
2269
2280
|
const framework = parts[3] || 'none';
|
|
2270
2281
|
const hosting = parts[4] || 'cloud';
|
|
2271
2282
|
// Check if .faf exists
|
|
@@ -2283,16 +2294,16 @@ Use force: true to overwrite, or use faf_go / faf_human_add to modify.`
|
|
|
2283
2294
|
};
|
|
2284
2295
|
}
|
|
2285
2296
|
// Detect project type from inputs
|
|
2286
|
-
const projectType = this.detectProjectTypeFromQuick(projectGoal, framework, mainLanguage);
|
|
2297
|
+
const projectType = this.detectProjectTypeFromQuick(projectGoal, framework, mainLanguage ?? '');
|
|
2287
2298
|
// Build .faf content
|
|
2288
2299
|
const fafData = {
|
|
2300
|
+
faf_version: '3.0',
|
|
2289
2301
|
project: {
|
|
2290
2302
|
name: projectName,
|
|
2291
2303
|
goal: projectGoal,
|
|
2292
|
-
main_language: mainLanguage
|
|
2304
|
+
...(mainLanguage ? { main_language: mainLanguage } : {})
|
|
2293
2305
|
},
|
|
2294
2306
|
type: projectType,
|
|
2295
|
-
generated: new Date().toISOString(),
|
|
2296
2307
|
version: version_1.VERSION,
|
|
2297
2308
|
initialized_by: 'faf-mcp-quick'
|
|
2298
2309
|
};
|
|
@@ -2310,7 +2321,8 @@ Use force: true to overwrite, or use faf_go / faf_human_add to modify.`
|
|
|
2310
2321
|
let output = `ā” FAF Quick - Created in ${elapsed}ms!\n\n`;
|
|
2311
2322
|
output += `š¦ Project: ${projectName}\n`;
|
|
2312
2323
|
output += `šÆ Purpose: ${projectGoal}\n`;
|
|
2313
|
-
|
|
2324
|
+
const stackShown = [mainLanguage, framework !== 'none' ? framework : undefined].filter(Boolean).join(' + ');
|
|
2325
|
+
output += `š» Stack: ${stackShown || '(not given)'}\n`;
|
|
2314
2326
|
output += `š Type: ${projectType}\n\n`;
|
|
2315
2327
|
output += `ā
Created: ${fafPath}\n\n`;
|
|
2316
2328
|
output += `Next steps:\n`;
|
|
@@ -2449,7 +2461,7 @@ Use force: true to overwrite, or use faf_go / faf_human_add to modify.`
|
|
|
2449
2461
|
results.push({
|
|
2450
2462
|
status: 'warning',
|
|
2451
2463
|
message: `Score could be better: ${score}% (${slotSummary})`,
|
|
2452
|
-
fix: '
|
|
2464
|
+
fix: 'Run faf_go ā the target is 100%'
|
|
2453
2465
|
});
|
|
2454
2466
|
}
|
|
2455
2467
|
else {
|
|
@@ -2538,7 +2550,7 @@ Use force: true to overwrite, or use faf_go / faf_human_add to modify.`
|
|
|
2538
2550
|
}
|
|
2539
2551
|
output += `\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n\n`;
|
|
2540
2552
|
if (!hasErrors && !hasWarnings) {
|
|
2541
|
-
output += `ā
Perfect health
|
|
2553
|
+
output += `ā
Perfect health ā no issues found.`;
|
|
2542
2554
|
}
|
|
2543
2555
|
else if (!hasErrors) {
|
|
2544
2556
|
output += `šÆ Good health with minor improvements suggested.`;
|
|
@@ -2680,9 +2692,12 @@ Use force: true to overwrite, or use faf_go / faf_human_add to modify.`
|
|
|
2680
2692
|
}
|
|
2681
2693
|
const outputPath = args?.path ? this.getProjectPath(args.path) : undefined;
|
|
2682
2694
|
try {
|
|
2695
|
+
// --force goes last: the engine reads url and the output path from the
|
|
2696
|
+
// positional args, flags excluded.
|
|
2683
2697
|
const result = await this.engineAdapter.callEngine('git', [
|
|
2684
2698
|
url,
|
|
2685
2699
|
...(outputPath ? [outputPath] : []),
|
|
2700
|
+
...(args?.force ? ['--force'] : []),
|
|
2686
2701
|
]);
|
|
2687
2702
|
if (!result.success) {
|
|
2688
2703
|
return {
|
|
@@ -2692,7 +2707,7 @@ Use force: true to overwrite, or use faf_go / faf_human_add to modify.`
|
|
|
2692
2707
|
}
|
|
2693
2708
|
const data = result.data;
|
|
2694
2709
|
let output = `GitHub Context:\n\nā
${data?.message || 'Done'}\nā±ļø ${result.duration}ms`;
|
|
2695
|
-
// Include
|
|
2710
|
+
// Include the authored .faf content if no output path (preview mode)
|
|
2696
2711
|
if (!outputPath && data?.data?.fafContent) {
|
|
2697
2712
|
output += `\n\n--- Authored project.faf ---\n${data.data.fafContent}`;
|
|
2698
2713
|
}
|