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.
Files changed (35) hide show
  1. package/CHANGELOG.md +30 -1
  2. package/CLAUDE.md +2 -2
  3. package/README.md +36 -18
  4. package/dist/src/cli.js +2 -1
  5. package/dist/src/cli.js.map +1 -1
  6. package/dist/src/faf-core/commands/agents.js +19 -5
  7. package/dist/src/faf-core/commands/agents.js.map +1 -1
  8. package/dist/src/faf-core/commands/claude.d.ts +0 -1
  9. package/dist/src/faf-core/commands/claude.js +1 -2
  10. package/dist/src/faf-core/commands/claude.js.map +1 -1
  11. package/dist/src/faf-core/commands/conductor.js +35 -10
  12. package/dist/src/faf-core/commands/conductor.js.map +1 -1
  13. package/dist/src/faf-core/commands/cursor.js +19 -5
  14. package/dist/src/faf-core/commands/cursor.js.map +1 -1
  15. package/dist/src/faf-core/commands/gemini.js +19 -5
  16. package/dist/src/faf-core/commands/gemini.js.map +1 -1
  17. package/dist/src/faf-core/commands/git-context.d.ts +12 -3
  18. package/dist/src/faf-core/commands/git-context.js +28 -6
  19. package/dist/src/faf-core/commands/git-context.js.map +1 -1
  20. package/dist/src/faf-core/commands/sync.d.ts +8 -0
  21. package/dist/src/faf-core/commands/sync.js +20 -23
  22. package/dist/src/faf-core/commands/sync.js.map +1 -1
  23. package/dist/src/faf-core/fix-once/yaml.js +12 -13
  24. package/dist/src/faf-core/fix-once/yaml.js.map +1 -1
  25. package/dist/src/faf-core/parsers/faf-git-generator.d.ts +0 -1
  26. package/dist/src/faf-core/parsers/faf-git-generator.js +13 -34
  27. package/dist/src/faf-core/parsers/faf-git-generator.js.map +1 -1
  28. package/dist/src/handlers/engine-adapter.js +5 -3
  29. package/dist/src/handlers/engine-adapter.js.map +1 -1
  30. package/dist/src/handlers/tools.js +95 -80
  31. package/dist/src/handlers/tools.js.map +1 -1
  32. package/package.json +6 -6
  33. package/project.faf +2 -2
  34. package/scripts/sync-version.js +2 -1
  35. 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 for structural integrity and field consistency. Returns trust metrics flagging malformed, missing, or contradictory data. Use this to confirm the context is sound before relying on it or syncing it to other formats.',
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 the ~/.faf-cli-cache directory left by older faf-mcp versions. This version persists nothing outside your project files.',
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 persisted (default; today that is the cache)' }
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 project root (cwd / FAF_ALLOWED_ROOTS). Paths that escape the project are refused.',
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 .faf File',
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 project root (cwd / FAF_ALLOWED_ROOTS). Paths that escape the project are refused.',
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 .faf File',
301
+ title: 'Write Project File',
302
302
  readOnlyHint: false,
303
- destructiveHint: false,
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 .faf Files',
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/excellent. Returns the ratings; with protect it locks good/excellent fields from being overwritten, with unlock it releases them. Use this to gauge context quality and guard your best answers.',
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: 'Lock good/excellent fields from being overwritten' },
428
- unlock: { type: 'boolean', description: 'Remove all field protections' },
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_ calls resolve against. Returns the current context path. Call this once at the start of a session so the other tools target the right project.',
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 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.',
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: true,
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) and surface which project.faf stack slots to fill. Returns the detected formats and slot-fill recommendations. Use this to populate the technical context.',
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, empty or weak slots, and common configuration issues, each with how to fix it. Returns a prioritized checklist. Use this when your score is low and you need to know why.',
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: 'Force overwrite existing files' },
578
- merge: { type: 'boolean', description: 'Merge imported data with existing .faf instead of replacing' },
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: 'Force overwrite existing files' },
599
- merge: { type: 'boolean', description: 'Merge imported data with existing .faf instead of replacing' },
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: 'Force overwrite existing files' },
620
- merge: { type: 'boolean', description: 'Merge imported data with existing .faf instead of replacing' },
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: false,
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: 'Merge imported data with existing .faf instead of replacing' },
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: false,
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. Slot-by-slot detail: \`faf score\` (CLI).`;
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
- ? `āœ… Structurally sound — no malformed, missing, or contradictory fields found.`
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
- const output = typeof result.data === 'string'
1036
- ? result.data
1037
- : result.data?.output || JSON.stringify(result.data, null, 2);
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?.output || JSON.stringify(result.data, null, 2);
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 universal,
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 'faf' on any project to create one.
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
- const testFile = path.join(cwd, '.claude-faf-test');
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 ? 'āœ… Yes' : 'āŒ No'}
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
- - āœ… \`/Users/wolfejam/Projects/my-app/\`
1298
+ - āœ… \`~/Projects/my-app/\`
1288
1299
  - āŒ \`/mnt/user-data/\` (container paths)
1289
1300
  - āŒ \`/home/claude/\` (container paths)
1290
1301
 
1291
1302
  ## Commands
1292
- All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
1293
-
1294
- **Core:**
1295
- - \`faf init\` - create FAF (infer path from context)
1296
- - \`faf score\` - show AI-readiness
1297
- - \`faf sync\` - synchronize files
1298
- - \`faf quick\` - rapid FAF creation
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āœ… All fields unlocked\nšŸ“ Updated: ${fafResult.filename}`
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' || qualities[f] === 'excellent');
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 qualify for protection (need good or excellent quality)`
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āœ… Protected ${toProtect.length} field(s): ${toProtect.join(', ')}\nšŸ“ Updated: ${fafResult.filename}`
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: 'ā—¼ļø', excellent: 'šŸ’Ž'
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' || qualities[f] === 'excellent').length;
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 += `šŸ”’ Protected: ${protectedFields.join(', ')}\n`;
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šŸ’” Subsequent faf_* calls will use this context`
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: 'Use AskUserQuestion to ask these questions, then call faf_go again with the answers parameter to apply them.'
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 = `šŸŽļøāš”ļø FAF AUTO - CHAMPIONSHIP MODE!\n`;
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: `šŸŽļø FAF Auto:\n\nāŒ Error: ${error.message}` }],
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(analysis, null, 2)
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 auto-detected!`
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
- const mainLanguage = parts[2] || 'TypeScript';
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
- output += `šŸ’» Stack: ${mainLanguage}${framework !== 'none' ? ` + ${framework}` : ''}\n`;
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: 'Target 70%+ for championship AI context'
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! Your FAF setup is championship-ready!`;
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 generated .faf content if no output path (preview mode)
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
  }