faf-mcp 2.3.1 → 3.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 +26 -3
- package/CLAUDE.md +6 -6
- package/README.md +71 -72
- package/dist/src/faf-core/commands/bi-sync.d.ts +1 -3
- package/dist/src/faf-core/commands/bi-sync.js +21 -64
- package/dist/src/faf-core/commands/bi-sync.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 +15 -0
- package/dist/src/faf-core/parsers/agents-parser.js +29 -123
- package/dist/src/faf-core/parsers/agents-parser.js.map +1 -1
- package/dist/src/faf-core/parsers/conductor-parser.js +1 -1
- package/dist/src/faf-core/parsers/conductor-parser.js.map +1 -1
- package/dist/src/faf-core/parsers/cursorrules-parser.d.ts +15 -0
- package/dist/src/faf-core/parsers/cursorrules-parser.js +54 -111
- package/dist/src/faf-core/parsers/cursorrules-parser.js.map +1 -1
- package/dist/src/faf-core/parsers/faf-git-generator.js +0 -1
- package/dist/src/faf-core/parsers/faf-git-generator.js.map +1 -1
- package/dist/src/faf-core/parsers/gemini-parser.d.ts +10 -0
- package/dist/src/faf-core/parsers/gemini-parser.js +19 -77
- package/dist/src/faf-core/parsers/gemini-parser.js.map +1 -1
- package/dist/src/handlers/engine-adapter.d.ts +13 -12
- package/dist/src/handlers/engine-adapter.js +103 -682
- 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 +41 -5
- package/dist/src/handlers/tools.js +213 -258
- 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 +2 -0
- package/dist/src/utils/faf-cli-bridge.js +16 -0
- package/dist/src/utils/faf-cli-bridge.js.map +1 -1
- package/package.json +16 -13
- package/project.faf +11 -6
- 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/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
|
@@ -206,9 +206,9 @@ class FafToolHandler {
|
|
|
206
206
|
},
|
|
207
207
|
{
|
|
208
208
|
name: 'faf_sync',
|
|
209
|
-
description: '
|
|
209
|
+
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_bi_sync for CLAUDE.md, AGENTS.md, .cursorrules and GEMINI.md.',
|
|
210
210
|
annotations: {
|
|
211
|
-
title: '
|
|
211
|
+
title: 'Reconcile .faf with repo manifests',
|
|
212
212
|
readOnlyHint: false,
|
|
213
213
|
destructiveHint: false,
|
|
214
214
|
openWorldHint: false
|
|
@@ -216,6 +216,7 @@ class FafToolHandler {
|
|
|
216
216
|
inputSchema: {
|
|
217
217
|
type: 'object',
|
|
218
218
|
properties: {
|
|
219
|
+
apply: { type: 'boolean', description: 'Write the detected changes into project.faf (default: dry-run report only)' },
|
|
219
220
|
path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
|
|
220
221
|
},
|
|
221
222
|
additionalProperties: false
|
|
@@ -223,9 +224,9 @@ class FafToolHandler {
|
|
|
223
224
|
},
|
|
224
225
|
{
|
|
225
226
|
name: 'faf_bi_sync',
|
|
226
|
-
description: '
|
|
227
|
+
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.',
|
|
227
228
|
annotations: {
|
|
228
|
-
title: '
|
|
229
|
+
title: 'Sync .faf to CLAUDE.md and IDE formats',
|
|
229
230
|
readOnlyHint: false,
|
|
230
231
|
destructiveHint: false,
|
|
231
232
|
openWorldHint: false
|
|
@@ -233,9 +234,6 @@ class FafToolHandler {
|
|
|
233
234
|
inputSchema: {
|
|
234
235
|
type: 'object',
|
|
235
236
|
properties: {
|
|
236
|
-
auto: { type: 'boolean', description: 'Enable automatic synchronization' },
|
|
237
|
-
watch: { type: 'boolean', description: 'Start real-time file watching for changes' },
|
|
238
|
-
force: { type: 'boolean', description: 'Force overwrite conflicting changes' },
|
|
239
237
|
agents: { type: 'boolean', description: 'Also sync to AGENTS.md (OpenAI/Codex format)' },
|
|
240
238
|
cursor: { type: 'boolean', description: 'Also sync to .cursorrules (Cursor IDE format)' },
|
|
241
239
|
gemini: { type: 'boolean', description: 'Also sync to GEMINI.md (Google Gemini format)' },
|
|
@@ -247,7 +245,7 @@ class FafToolHandler {
|
|
|
247
245
|
},
|
|
248
246
|
{
|
|
249
247
|
name: 'faf_clear',
|
|
250
|
-
description: '
|
|
248
|
+
description: 'Remove the ~/.faf-cli-cache directory left by older faf-mcp versions. This version persists nothing outside your project files.',
|
|
251
249
|
annotations: {
|
|
252
250
|
title: 'Clear .faf Data',
|
|
253
251
|
readOnlyHint: false,
|
|
@@ -257,17 +255,15 @@ class FafToolHandler {
|
|
|
257
255
|
inputSchema: {
|
|
258
256
|
type: 'object',
|
|
259
257
|
properties: {
|
|
260
|
-
cache: { type: 'boolean', description: '
|
|
261
|
-
|
|
262
|
-
backups: { type: 'boolean', description: 'Clear backup files only' },
|
|
263
|
-
all: { type: 'boolean', description: 'Clear everything (default)' }
|
|
258
|
+
cache: { type: 'boolean', description: 'Remove ~/.faf-cli-cache (written by faf-mcp < 3.0)' },
|
|
259
|
+
all: { type: 'boolean', description: 'Remove everything older versions persisted (default; today that is the cache)' }
|
|
264
260
|
},
|
|
265
261
|
additionalProperties: false
|
|
266
262
|
}
|
|
267
263
|
},
|
|
268
264
|
{
|
|
269
265
|
name: 'faf_debug',
|
|
270
|
-
description: 'Debug faf-mcp environment - show working directory, permissions, and
|
|
266
|
+
description: 'Debug faf-mcp environment - show working directory, write permissions, the bundled faf-cli version, and whether a project.faf is present',
|
|
271
267
|
annotations: {
|
|
272
268
|
title: 'Debug Info',
|
|
273
269
|
readOnlyHint: true,
|
|
@@ -486,8 +482,10 @@ class FafToolHandler {
|
|
|
486
482
|
inputSchema: {
|
|
487
483
|
type: 'object',
|
|
488
484
|
properties: {
|
|
489
|
-
path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
|
|
490
|
-
force
|
|
485
|
+
path: { type: 'string', description: 'Project path. Sets session context for subsequent calls.' }
|
|
486
|
+
// `force` used to be declared here; handleFafAuto never read it.
|
|
487
|
+
// faf_auto merges into an existing project.faf non-destructively
|
|
488
|
+
// and there is no overwrite mode to switch on.
|
|
491
489
|
},
|
|
492
490
|
additionalProperties: false
|
|
493
491
|
}
|
|
@@ -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.`;
|
|
@@ -977,7 +975,7 @@ package_manager: ${projectData.package_manager}` : ''}
|
|
|
977
975
|
return {
|
|
978
976
|
content: [{
|
|
979
977
|
type: 'text',
|
|
980
|
-
text: `🚀 Claude FAF Initialization:\n\n✅ Created project.faf\n\n${pathConfirmation}${sourceExplanation}\n\n🍊 Vitamin Context activated!\n⚡ FAFFLESS AI ready!${chromeDetection.detected ? '\n\n🎯 Friday Feature: Chrome Extension detected!\n📈 Auto-filled 7 slots for 90%+ score!' : ''}${chromeDetection.corrected ? `\n📝 Auto-corrected: "${args?.description}" → "${chromeDetection.corrected}"` : ''}\n\n🏁 Next steps:\n • Run faf_score for AI-readiness score\n • Run
|
|
978
|
+
text: `🚀 Claude FAF Initialization:\n\n✅ Created project.faf\n\n${pathConfirmation}${sourceExplanation}\n\n🍊 Vitamin Context activated!\n⚡ FAFFLESS AI ready!${chromeDetection.detected ? '\n\n🎯 Friday Feature: Chrome Extension detected!\n📈 Auto-filled 7 slots for 90%+ score!' : ''}${chromeDetection.corrected ? `\n📝 Auto-corrected: "${args?.description}" → "${chromeDetection.corrected}"` : ''}\n\n🏁 Next steps:\n • Run faf_score for AI-readiness score\n • Run faf_bi_sync to create CLAUDE.md\n • Run faf_go to fill the gaps`
|
|
981
979
|
}]
|
|
982
980
|
};
|
|
983
981
|
}
|
|
@@ -991,25 +989,65 @@ package_manager: ${projectData.package_manager}` : ''}
|
|
|
991
989
|
};
|
|
992
990
|
}
|
|
993
991
|
}
|
|
992
|
+
/**
|
|
993
|
+
* v3.0: was `this.engineAdapter.callEngine('trust')` — no bundled 'trust'
|
|
994
|
+
* branch exists in engine-adapter.ts, so this fell straight through to
|
|
995
|
+
* the ambient-PATH shell-out fallback (`which faf`, then execAsync).
|
|
996
|
+
* faf-cli itself has no `trust` subcommand at all — on any host where an
|
|
997
|
+
* unrelated `faf`-named binary sits first on PATH (verified live on this
|
|
998
|
+
* machine: Rust "FAFb"), this either hard-errors on an "unrecognized
|
|
999
|
+
* subcommand" or, worse, silently "succeeds" with that tool's own output.
|
|
1000
|
+
* Rewired onto the bundled faf-cli bridge — bundled faf-cli only, never
|
|
1001
|
+
* ambient PATH — composing `validateFaf` (structural integrity) +
|
|
1002
|
+
* `scoreFafYaml` (trust metrics) directly, matching the tool's own
|
|
1003
|
+
* description ("structural integrity and field consistency... trust
|
|
1004
|
+
* metrics"). No CLI subcommand to shell out to even exists for this.
|
|
1005
|
+
*/
|
|
994
1006
|
async handleFafTrust(_args) {
|
|
995
|
-
const
|
|
996
|
-
|
|
1007
|
+
const cwd = this.getProjectPath();
|
|
1008
|
+
const { findFafFile: findFaf, readFaf, readFafRaw, validateFaf, scoreFafYaml } = await faf_cli_bridge_js_1.fafCli;
|
|
1009
|
+
const fafPath = findFaf(cwd);
|
|
1010
|
+
if (!fafPath) {
|
|
997
1011
|
return {
|
|
998
1012
|
content: [{
|
|
999
1013
|
type: 'text',
|
|
1000
|
-
text: `🔒 Claude FAF Trust Validation:\n\
|
|
1014
|
+
text: `🔒 Claude FAF Trust Validation:\n\n❌ No .faf found in ${cwd}\n💡 Run faf_init to create one — then faf_trust reports real trust metrics.`
|
|
1015
|
+
}]
|
|
1016
|
+
};
|
|
1017
|
+
}
|
|
1018
|
+
let raw;
|
|
1019
|
+
let data;
|
|
1020
|
+
try {
|
|
1021
|
+
raw = readFafRaw(fafPath);
|
|
1022
|
+
data = readFaf(fafPath);
|
|
1023
|
+
}
|
|
1024
|
+
catch (error) {
|
|
1025
|
+
return {
|
|
1026
|
+
content: [{
|
|
1027
|
+
type: 'text',
|
|
1028
|
+
text: `🔒 Claude FAF Trust Validation:\n\n❌ Could not read ${fafPath}: ${error?.message ?? String(error)}`
|
|
1001
1029
|
}],
|
|
1002
1030
|
isError: true
|
|
1003
1031
|
};
|
|
1004
1032
|
}
|
|
1005
|
-
const
|
|
1006
|
-
|
|
1007
|
-
|
|
1033
|
+
const validation = validateFaf(data);
|
|
1034
|
+
let output = validation.valid
|
|
1035
|
+
? `✅ Structurally sound — no malformed, missing, or contradictory fields found.`
|
|
1036
|
+
: `❌ ${validation.errors.length} issue${validation.errors.length === 1 ? '' : 's'} found:\n` +
|
|
1037
|
+
validation.errors.map((e) => ` • ${e}`).join('\n');
|
|
1038
|
+
try {
|
|
1039
|
+
const score = scoreFafYaml(raw);
|
|
1040
|
+
output += `\n\n📊 ${score.score}/100 (${score.populated}/${score.active} slots populated) — ${score.tier.name}`;
|
|
1041
|
+
}
|
|
1042
|
+
catch {
|
|
1043
|
+
// Score is a bonus signal here; a validation-only result is still honest.
|
|
1044
|
+
}
|
|
1008
1045
|
return {
|
|
1009
1046
|
content: [{
|
|
1010
1047
|
type: 'text',
|
|
1011
1048
|
text: `🔒 Claude FAF Trust Validation:\n\n${output}`
|
|
1012
|
-
}]
|
|
1049
|
+
}],
|
|
1050
|
+
isError: !validation.valid
|
|
1013
1051
|
};
|
|
1014
1052
|
}
|
|
1015
1053
|
async handleFafSync(args) {
|
|
@@ -1017,7 +1055,9 @@ package_manager: ${projectData.package_manager}` : ''}
|
|
|
1017
1055
|
if (args?.path) {
|
|
1018
1056
|
this.getProjectPath(args.path);
|
|
1019
1057
|
}
|
|
1020
|
-
|
|
1058
|
+
// `apply` maps to the bundled sync command's --auto; without it the
|
|
1059
|
+
// command only reports what would change.
|
|
1060
|
+
const result = await this.engineAdapter.callEngine('sync', args?.apply ? ['--auto'] : []);
|
|
1021
1061
|
if (!result.success) {
|
|
1022
1062
|
return {
|
|
1023
1063
|
content: [{
|
|
@@ -1042,15 +1082,23 @@ package_manager: ${projectData.package_manager}` : ''}
|
|
|
1042
1082
|
if (args?.path) {
|
|
1043
1083
|
this.getProjectPath(args.path);
|
|
1044
1084
|
}
|
|
1085
|
+
// auto / watch / force used to be declared here too; none of them was
|
|
1086
|
+
// ever read by syncBiDirectional, so the schema no longer promises them.
|
|
1045
1087
|
const biSyncArgs = [];
|
|
1046
|
-
|
|
1047
|
-
|
|
1088
|
+
// v3.0 fix: the tool's own schema advertises agents/cursor/gemini/all,
|
|
1089
|
+
// but they were never forwarded to engine-adapter's bi-sync dispatch —
|
|
1090
|
+
// faf_bi_sync silently ignored them and only ever wrote CLAUDE.md.
|
|
1091
|
+
if (args?.agents) {
|
|
1092
|
+
biSyncArgs.push('--agents');
|
|
1048
1093
|
}
|
|
1049
|
-
if (args?.
|
|
1050
|
-
biSyncArgs.push('--
|
|
1094
|
+
if (args?.cursor) {
|
|
1095
|
+
biSyncArgs.push('--cursor');
|
|
1051
1096
|
}
|
|
1052
|
-
if (args?.
|
|
1053
|
-
biSyncArgs.push('--
|
|
1097
|
+
if (args?.gemini) {
|
|
1098
|
+
biSyncArgs.push('--gemini');
|
|
1099
|
+
}
|
|
1100
|
+
if (args?.all) {
|
|
1101
|
+
biSyncArgs.push('--all');
|
|
1054
1102
|
}
|
|
1055
1103
|
const result = await this.engineAdapter.callEngine('bi-sync', biSyncArgs);
|
|
1056
1104
|
if (!result.success) {
|
|
@@ -1072,37 +1120,46 @@ package_manager: ${projectData.package_manager}` : ''}
|
|
|
1072
1120
|
}]
|
|
1073
1121
|
};
|
|
1074
1122
|
}
|
|
1123
|
+
/**
|
|
1124
|
+
* v3.0: was `this.engineAdapter.callEngine('clear', clearArgs)` — no
|
|
1125
|
+
* bundled 'clear' branch exists in engine-adapter.ts, so this fell
|
|
1126
|
+
* straight through to the ambient-PATH shell-out fallback. Worse: even
|
|
1127
|
+
* when a real faf-cli WAS first on PATH, its actual `clear` subcommand
|
|
1128
|
+
* only clears `faf-git-*` temp directories from the OS tmpdir (see
|
|
1129
|
+
* ~/FAF/cli/src/commands/clear.ts) — entirely unrelated to this tool's
|
|
1130
|
+
* own cache/todos/backups semantics. There was never a correct backing
|
|
1131
|
+
* implementation for this tool's contract, on any PATH.
|
|
1132
|
+
*
|
|
1133
|
+
* Rewired as a genuinely local operation — no CLI dependency, bundled or
|
|
1134
|
+
* ambient, at all. `cache` clears the one real cache path this codebase
|
|
1135
|
+
* writes (~/.faf-cli-cache, faf-core/utils/technical-credit.ts). The
|
|
1136
|
+
* schema used to advertise `todos` and `backups` as well; no persisted todo
|
|
1137
|
+
* list or backup-file store exists anywhere in faf-mcp, so those flags are
|
|
1138
|
+
* gone rather than answered with a made-up "cleared".
|
|
1139
|
+
*/
|
|
1075
1140
|
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
|
-
};
|
|
1141
|
+
const wantCache = !!args?.cache;
|
|
1142
|
+
const wantAll = !!args?.all || !wantCache;
|
|
1143
|
+
const lines = [];
|
|
1144
|
+
if (wantCache || wantAll) {
|
|
1145
|
+
// HOME/USERPROFILE first (Node's own os.homedir() precedence; Bun's ignores a
|
|
1146
|
+
// runtime override) so a test can redirect the home dir instead of
|
|
1147
|
+
// deleting from the developer's real one.
|
|
1148
|
+
const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
1149
|
+
const cacheDir = pathModule.join(home, '.faf-cli-cache');
|
|
1150
|
+
const creditCache = pathModule.join(cacheDir, 'technical-credit.json');
|
|
1151
|
+
if (fs.existsSync(creditCache)) {
|
|
1152
|
+
fs.rmSync(creditCache, { force: true });
|
|
1153
|
+
lines.push(`✅ cache: cleared ${creditCache}`);
|
|
1154
|
+
}
|
|
1155
|
+
else {
|
|
1156
|
+
lines.push('✅ cache: nothing to clear');
|
|
1157
|
+
}
|
|
1098
1158
|
}
|
|
1099
|
-
const output = typeof result.data === 'string'
|
|
1100
|
-
? result.data
|
|
1101
|
-
: result.data?.output || JSON.stringify(result.data, null, 2);
|
|
1102
1159
|
return {
|
|
1103
1160
|
content: [{
|
|
1104
1161
|
type: 'text',
|
|
1105
|
-
text: `🧹 Claude FAF Clear:\n\n${
|
|
1162
|
+
text: `🧹 Claude FAF Clear:\n\n${lines.join('\n')}`
|
|
1106
1163
|
}]
|
|
1107
1164
|
};
|
|
1108
1165
|
}
|
|
@@ -1180,18 +1237,15 @@ REMEMBER: Always use ".faf" with the dot - it's a FORMAT!
|
|
|
1180
1237
|
try {
|
|
1181
1238
|
const fs = await import('fs');
|
|
1182
1239
|
const path = await import('path');
|
|
1183
|
-
const { exec } = await import('child_process');
|
|
1184
|
-
const { promisify } = await import('util');
|
|
1185
|
-
const _execAsync = promisify(exec);
|
|
1186
1240
|
const cwd = this.engineAdapter.getWorkingDirectory();
|
|
1187
1241
|
const debugInfo = {
|
|
1188
1242
|
workingDirectory: cwd,
|
|
1189
1243
|
canWrite: false,
|
|
1190
|
-
|
|
1191
|
-
|
|
1244
|
+
// Every tool runs on the faf-cli bundled in node_modules — report
|
|
1245
|
+
// that, not whatever `which faf` happens to find on PATH.
|
|
1246
|
+
fafVersion: (0, faf_cli_bridge_js_1.bundledFafCliVersion)(),
|
|
1192
1247
|
permissions: {},
|
|
1193
1248
|
enginePath: this.engineAdapter.getEnginePath(),
|
|
1194
|
-
pathEnv: process.env.PATH?.split(':') || []
|
|
1195
1249
|
};
|
|
1196
1250
|
// Check write permissions
|
|
1197
1251
|
try {
|
|
@@ -1203,21 +1257,6 @@ REMEMBER: Always use ".faf" with the dot - it's a FORMAT!
|
|
|
1203
1257
|
catch (error) {
|
|
1204
1258
|
debugInfo.permissions.writeError = error instanceof Error ? error.message : String(error);
|
|
1205
1259
|
}
|
|
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
1260
|
// Check for existing FAF file (v1.2.0: project.faf, *.faf, or .faf)
|
|
1222
1261
|
const fafResult = await (0, faf_file_finder_js_1.findFafFile)(cwd);
|
|
1223
1262
|
const hasFaf = fafResult !== null;
|
|
@@ -1226,15 +1265,12 @@ REMEMBER: Always use ".faf" with the dot - it's a FORMAT!
|
|
|
1226
1265
|
📂 Working Directory: ${debugInfo.workingDirectory}
|
|
1227
1266
|
✏️ Write Permissions: ${debugInfo.canWrite ? '✅ Yes' : '❌ No'}
|
|
1228
1267
|
${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 ? '...' : ''}
|
|
1268
|
+
📋 faf-cli (bundled): ${debugInfo.fafVersion ? `v${debugInfo.fafVersion}` : '❌ not found in node_modules — reinstall faf-mcp'}
|
|
1269
|
+
📄 FAF File: ${hasFaf ? `✅ ${fafResult.filename} exists` : '❌ Not found (run faf_init)'}
|
|
1233
1270
|
|
|
1234
1271
|
💡 Quick Start:
|
|
1235
|
-
1. If
|
|
1236
|
-
2.
|
|
1237
|
-
3. For optimization: use faf_enhance tool with model="claude"
|
|
1272
|
+
1. If .faf file missing: use faf_init or faf_auto
|
|
1273
|
+
2. To check AI-readiness: use faf_score
|
|
1238
1274
|
`;
|
|
1239
1275
|
return {
|
|
1240
1276
|
content: [{
|
|
@@ -1780,7 +1816,7 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
|
|
|
1780
1816
|
// they're SOURCED by Turbo-Cat (faf-cli's separate STACK_INTERVIEW if ever
|
|
1781
1817
|
// needed), never asked of a human. (Decision: single-source the 8-Q 6Ws
|
|
1782
1818
|
// Interview, wolfejam 2026-06-10 — language is not on the human side.)
|
|
1783
|
-
const { SIX_WS_INTERVIEW } = await faf_cli_bridge_js_1.fafCli;
|
|
1819
|
+
const { SIX_WS_INTERVIEW, isPlaceholder, scoreFafYaml } = await faf_cli_bridge_js_1.fafCli;
|
|
1784
1820
|
const QUESTION_REGISTRY = Object.fromEntries(SIX_WS_INTERVIEW.map((q) => [q.path, q]));
|
|
1785
1821
|
// Priority order for questions
|
|
1786
1822
|
const priorityOrder = [
|
|
@@ -1825,16 +1861,18 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
|
|
|
1825
1861
|
}
|
|
1826
1862
|
current[parts[parts.length - 1]] = value;
|
|
1827
1863
|
};
|
|
1828
|
-
//
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1864
|
+
// Empty = what the scorer calls empty. faf-cli's isPlaceholder is the one
|
|
1865
|
+
// rule the kernel scores by; a local list ('TBD', 'Unknown', ...) drifted
|
|
1866
|
+
// from it in both directions and made faf_go's numbers disagree with
|
|
1867
|
+
// faf_score on the same file. slotignored is inactive, never asked.
|
|
1868
|
+
const isEmpty = (value) => isPlaceholder(value) && value !== 'slotignored';
|
|
1869
|
+
// One score function, period: the real scorer on the real bytes.
|
|
1870
|
+
const scoreOf = (raw) => scoreFafYaml(raw).score;
|
|
1871
|
+
const tierLabel = (score) => score >= 100 ? '✪ Trophy — 100% AI-Readiness' :
|
|
1872
|
+
score >= 99 ? '★ Gold' :
|
|
1873
|
+
score >= 95 ? '◆ Silver' :
|
|
1874
|
+
score >= 85 ? '◇ Bronze — production ready' :
|
|
1875
|
+
score >= 70 ? '● Green — solid foundation' : '📈 Keep going!';
|
|
1838
1876
|
// PHASE 2: Apply answers if provided
|
|
1839
1877
|
if (args?.answers && typeof args.answers === 'object') {
|
|
1840
1878
|
const answers = args.answers;
|
|
@@ -1845,15 +1883,10 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
|
|
|
1845
1883
|
appliedCount++;
|
|
1846
1884
|
}
|
|
1847
1885
|
}
|
|
1848
|
-
// Write updated file
|
|
1886
|
+
// Write updated file, then score the bytes that were written.
|
|
1849
1887
|
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!';
|
|
1888
|
+
const newScore = scoreOf(fs.readFileSync(fafResult.path, 'utf-8'));
|
|
1889
|
+
const celebration = tierLabel(newScore);
|
|
1857
1890
|
return {
|
|
1858
1891
|
content: [{
|
|
1859
1892
|
type: 'text',
|
|
@@ -1864,12 +1897,10 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
|
|
|
1864
1897
|
// PHASE 1: Analyze and return questions — the canonical 6Ws (SIX_WS_INTERVIEW),
|
|
1865
1898
|
// scoped to slots that are empty AND active (slotignored is never asked).
|
|
1866
1899
|
const missingFields = SIX_WS_INTERVIEW
|
|
1867
|
-
.filter((q) =>
|
|
1900
|
+
.filter((q) => isEmpty(getNestedValue(fafData, q.path)))
|
|
1868
1901
|
.map((q) => q.path);
|
|
1869
|
-
//
|
|
1870
|
-
const
|
|
1871
|
-
const filledFields = totalFields - missingFields.length;
|
|
1872
|
-
const currentScore = Math.round((filledFields / totalFields) * 100);
|
|
1902
|
+
// Current score — the same number faf_score reports for this file.
|
|
1903
|
+
const currentScore = scoreOf(fafContent);
|
|
1873
1904
|
// Already at 100%?
|
|
1874
1905
|
if (currentScore >= 100) {
|
|
1875
1906
|
return {
|
|
@@ -1892,7 +1923,7 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
|
|
|
1892
1923
|
text: JSON.stringify({
|
|
1893
1924
|
score: currentScore,
|
|
1894
1925
|
message: `Score is ${currentScore}%. All fields filled but content may need enhancement.`,
|
|
1895
|
-
suggestion: 'Use
|
|
1926
|
+
suggestion: 'Use faf_go or faf_human_add to improve content quality.',
|
|
1896
1927
|
context: 'faf_go'
|
|
1897
1928
|
}, null, 2)
|
|
1898
1929
|
}]
|
|
@@ -1945,120 +1976,52 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
|
|
|
1945
1976
|
/**
|
|
1946
1977
|
* faf_auto - ONE COMMAND TO RULE THEM ALL
|
|
1947
1978
|
* Zero to Championship in one command
|
|
1948
|
-
*
|
|
1979
|
+
*
|
|
1980
|
+
* v3.0: composes faf-cli's own `faf auto` chain — `updateExistingFaf` on an
|
|
1981
|
+
* existing file, `assembleFreshFaf` on a new one, `writeFaf` for the bytes,
|
|
1982
|
+
* `renderClaudeMd`/`writeClaudeMd` for CLAUDE.md — plus the same
|
|
1983
|
+
* `scoreFafYaml` faf_score reads. Previously this handler ran a bespoke
|
|
1984
|
+
* bootstrap template plus a local `calculateSimpleScore` pseudo-scorer,
|
|
1985
|
+
* which could (and did) report a different score than faf_score for the
|
|
1986
|
+
* identical file in the same server run. NEVER reimplement detection or
|
|
1987
|
+
* scoring here — compose faf-cli, exactly like faf_score/faf_formats do.
|
|
1949
1988
|
*/
|
|
1950
1989
|
async handleFafAuto(args) {
|
|
1951
1990
|
const startTime = Date.now();
|
|
1952
1991
|
const cwd = this.getProjectPath(args?.path);
|
|
1953
|
-
const yaml = await import('yaml');
|
|
1954
1992
|
const path = await import('path');
|
|
1955
1993
|
try {
|
|
1956
1994
|
const steps = [];
|
|
1957
|
-
|
|
1958
|
-
// Step 1:
|
|
1959
|
-
const
|
|
1995
|
+
const { findFafFile: findFaf, readFaf, readFafRaw, scoreFafYaml, assembleFreshFaf, updateExistingFaf, writeFaf, renderClaudeMd, writeClaudeMd, } = await faf_cli_bridge_js_1.fafCli;
|
|
1996
|
+
// Step 1: locate any existing .faf and its real "before" score.
|
|
1997
|
+
const existingPath = findFaf(cwd);
|
|
1998
|
+
const beforeScore = existingPath ? scoreFafYaml(readFafRaw(existingPath)).score : 0;
|
|
1999
|
+
steps.push(existingPath ? `✅ Found ${path.basename(existingPath)}` : '✅ Will create project.faf');
|
|
2000
|
+
// Step 2: exactly what faf-cli's own `faf auto` does — an existing file
|
|
2001
|
+
// is updated with updateExistingFaf (existing values win; interrogated →
|
|
2002
|
+
// detected → Turbo-Cat → Relentless fill the empties), a new one is
|
|
2003
|
+
// assembled with assembleFreshFaf — and written with faf-cli's writer,
|
|
2004
|
+
// so the bytes match `faf auto` (no runtime `_meta` block, same layout).
|
|
1960
2005
|
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');
|
|
1980
|
-
}
|
|
1981
|
-
else {
|
|
1982
|
-
fafPath = fafResult.path;
|
|
1983
|
-
steps.push(`✅ Found ${fafResult.filename}`);
|
|
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`);
|
|
2006
|
+
if (existingPath) {
|
|
2007
|
+
fafPath = existingPath;
|
|
2008
|
+
writeFaf(fafPath, updateExistingFaf(cwd, readFaf(existingPath)));
|
|
2009
|
+
steps.push('✅ Merged fresh facts into existing project.faf (existing values preserved)');
|
|
2008
2010
|
}
|
|
2009
2011
|
else {
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
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');
|
|
2012
|
+
fafPath = path.join(cwd, 'project.faf');
|
|
2013
|
+
writeFaf(fafPath, assembleFreshFaf(cwd));
|
|
2014
|
+
steps.push('✅ Created project.faf');
|
|
2056
2015
|
}
|
|
2057
|
-
// Step
|
|
2058
|
-
|
|
2059
|
-
const
|
|
2060
|
-
|
|
2061
|
-
|
|
2016
|
+
// Step 3: CLAUDE.md — faf-cli's render of the file just written (the
|
|
2017
|
+
// same bytes `faf sync` writes), injected non-destructively.
|
|
2018
|
+
const claudeExisted = fs.existsSync(path.join(cwd, 'CLAUDE.md'));
|
|
2019
|
+
writeClaudeMd(cwd, renderClaudeMd(readFaf(fafPath)));
|
|
2020
|
+
steps.push(claudeExisted ? '✅ Updated CLAUDE.md (faf-managed block)' : '✅ Created CLAUDE.md');
|
|
2021
|
+
// Step 4: final score — same real scorer faf_score reads, on the file
|
|
2022
|
+
// just written. By construction this can never disagree with faf_score.
|
|
2023
|
+
const newScore = scoreFafYaml(readFafRaw(fafPath)).score;
|
|
2024
|
+
const scoreDelta = newScore - beforeScore;
|
|
2062
2025
|
// Calculate elapsed time
|
|
2063
2026
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
2064
2027
|
// Format output
|
|
@@ -2068,7 +2031,7 @@ ${fafData.stack_signature || 'Auto-detected stack'}
|
|
|
2068
2031
|
output += steps.join('\n') + '\n\n';
|
|
2069
2032
|
output += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
|
|
2070
2033
|
output += `⏱️ Completed in ${elapsed}s\n`;
|
|
2071
|
-
output += `📊 Before: ${
|
|
2034
|
+
output += `📊 Before: ${beforeScore}% | After: ${newScore}% ${deltaDisplay}\n`;
|
|
2072
2035
|
output += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n`;
|
|
2073
2036
|
if (newScore >= 99) {
|
|
2074
2037
|
output += `🏆 CHAMPIONSHIP ACHIEVED! Your AI has complete context.\n`;
|
|
@@ -2082,7 +2045,7 @@ ${fafData.stack_signature || 'Auto-detected stack'}
|
|
|
2082
2045
|
else {
|
|
2083
2046
|
output += `🚀 Good start! Run faf_go for guided improvement.\n`;
|
|
2084
2047
|
}
|
|
2085
|
-
output += `\n💡 Next: faf_score
|
|
2048
|
+
output += `\n💡 Next: faf_score | faf_go | faf_bi_sync`;
|
|
2086
2049
|
return { content: [{ type: 'text', text: output }] };
|
|
2087
2050
|
}
|
|
2088
2051
|
catch (error) {
|
|
@@ -2113,11 +2076,10 @@ ${fafData.stack_signature || 'Auto-detected stack'}
|
|
|
2113
2076
|
}]
|
|
2114
2077
|
};
|
|
2115
2078
|
}
|
|
2116
|
-
// .faf exists but no DNA - create initial DNA
|
|
2117
|
-
|
|
2118
|
-
const
|
|
2119
|
-
const
|
|
2120
|
-
const currentScore = this.calculateSimpleScore(fafData);
|
|
2079
|
+
// .faf exists but no DNA - create initial DNA. Real scorer — same
|
|
2080
|
+
// one faf_score reads — never a local pseudo-score (v3.0 fix).
|
|
2081
|
+
const { readFafRaw, scoreFafYaml } = await faf_cli_bridge_js_1.fafCli;
|
|
2082
|
+
const currentScore = scoreFafYaml(readFafRaw(fafResult.path)).score;
|
|
2121
2083
|
const dna = {
|
|
2122
2084
|
birthCertificate: {
|
|
2123
2085
|
born: new Date().toISOString(),
|
|
@@ -2149,8 +2111,24 @@ ${fafData.stack_signature || 'Auto-detected stack'}
|
|
|
2149
2111
|
const dna = JSON.parse(dnaContent);
|
|
2150
2112
|
// Build journey string
|
|
2151
2113
|
const birthScore = dna.birthCertificate?.birthDNA || 0;
|
|
2152
|
-
|
|
2114
|
+
let currentScore = dna.current?.score || 0;
|
|
2153
2115
|
const milestones = dna.milestones || [];
|
|
2116
|
+
dna.milestones = milestones;
|
|
2117
|
+
// "Current" is the live score of the file on disk, not whatever was
|
|
2118
|
+
// stored last time. Nothing else records growth into .faf-dna, so this
|
|
2119
|
+
// is where the journey gets its next point.
|
|
2120
|
+
const fafResult = await (0, faf_file_finder_js_1.findFafFile)(cwd);
|
|
2121
|
+
if (fafResult) {
|
|
2122
|
+
const { readFafRaw, scoreFafYaml } = await faf_cli_bridge_js_1.fafCli;
|
|
2123
|
+
const live = scoreFafYaml(readFafRaw(fafResult.path)).score;
|
|
2124
|
+
if (live !== currentScore) {
|
|
2125
|
+
const now = new Date().toISOString();
|
|
2126
|
+
dna.current = { ...(dna.current ?? {}), score: live, lastSync: now };
|
|
2127
|
+
milestones.push({ type: 'sync', score: live, date: now, version: dna.current.version ?? 'v1.0.0' });
|
|
2128
|
+
fs.writeFileSync(dnaPath, JSON.stringify(dna, null, 2));
|
|
2129
|
+
currentScore = live;
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2154
2132
|
// Find key milestones
|
|
2155
2133
|
const _birth = milestones.find((m) => m.type === 'birth');
|
|
2156
2134
|
const peak = milestones.find((m) => m.type === 'peak');
|
|
@@ -2192,7 +2170,7 @@ ${fafData.stack_signature || 'Auto-detected stack'}
|
|
|
2192
2170
|
}
|
|
2193
2171
|
output += `\n🧬 MILESTONES\n`;
|
|
2194
2172
|
const milestoneIcons = {
|
|
2195
|
-
birth: '🐣', first_save: '💾', doubled: '2️⃣',
|
|
2173
|
+
birth: '🐣', first_save: '💾', doubled: '2️⃣', sync: '🔄',
|
|
2196
2174
|
championship: '🏆', elite: '⭐', peak: '🏔️', perfect: '💎'
|
|
2197
2175
|
};
|
|
2198
2176
|
for (const m of milestones) {
|
|
@@ -2271,33 +2249,6 @@ ${fafData.stack_signature || 'Auto-detected stack'}
|
|
|
2271
2249
|
}
|
|
2272
2250
|
}
|
|
2273
2251
|
/**
|
|
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
2252
|
/**
|
|
2302
2253
|
* faf_quick - Lightning-fast .faf creation
|
|
2303
2254
|
* One-liner format: "name, description, language, framework, hosting"
|
|
@@ -2357,7 +2308,7 @@ Example: "my-app, e-commerce platform"`
|
|
|
2357
2308
|
|
|
2358
2309
|
⚠️ project.faf already exists at: ${fafPath}
|
|
2359
2310
|
|
|
2360
|
-
Use force: true to overwrite, or use
|
|
2311
|
+
Use force: true to overwrite, or use faf_go / faf_human_add to modify.`
|
|
2361
2312
|
}]
|
|
2362
2313
|
};
|
|
2363
2314
|
}
|
|
@@ -2394,7 +2345,6 @@ Use force: true to overwrite, or use faf_enhance to modify.`
|
|
|
2394
2345
|
output += `✅ Created: ${fafPath}\n\n`;
|
|
2395
2346
|
output += `Next steps:\n`;
|
|
2396
2347
|
output += ` • faf_score - Check AI-readiness\n`;
|
|
2397
|
-
output += ` • faf_enhance - Improve context\n`;
|
|
2398
2348
|
output += ` • faf_go - Guided interview to 100%`;
|
|
2399
2349
|
return { content: [{ type: 'text', text: output }] };
|
|
2400
2350
|
}
|
|
@@ -2502,7 +2452,7 @@ Use force: true to overwrite, or use faf_enhance to modify.`
|
|
|
2502
2452
|
results.push({
|
|
2503
2453
|
status: 'warning',
|
|
2504
2454
|
message: `Missing important fields: ${missingFields.join(', ')}`,
|
|
2505
|
-
fix: 'Run:
|
|
2455
|
+
fix: 'Run: faf_go to add missing info'
|
|
2506
2456
|
});
|
|
2507
2457
|
}
|
|
2508
2458
|
else {
|
|
@@ -2511,26 +2461,31 @@ Use force: true to overwrite, or use faf_enhance to modify.`
|
|
|
2511
2461
|
message: '.faf structure is valid'
|
|
2512
2462
|
});
|
|
2513
2463
|
}
|
|
2514
|
-
// Check 4: Score
|
|
2515
|
-
|
|
2464
|
+
// Check 4: Score — real scorer (same one faf_score reads), never
|
|
2465
|
+
// a local pseudo-score (v3.0 fix). Slot breakdown included so
|
|
2466
|
+
// faf_doctor's "score could be better" fix advice is actionable.
|
|
2467
|
+
const { scoreFafYaml } = await faf_cli_bridge_js_1.fafCli;
|
|
2468
|
+
const scoreResult = scoreFafYaml(content);
|
|
2469
|
+
const score = scoreResult.score;
|
|
2470
|
+
const slotSummary = `${scoreResult.populated}/${scoreResult.active} slots populated`;
|
|
2516
2471
|
if (score < 30) {
|
|
2517
2472
|
results.push({
|
|
2518
2473
|
status: 'error',
|
|
2519
|
-
message: `Score too low: ${score}
|
|
2520
|
-
fix: 'Run:
|
|
2474
|
+
message: `Score too low: ${score}% (${slotSummary})`,
|
|
2475
|
+
fix: 'Run: faf_go to improve context'
|
|
2521
2476
|
});
|
|
2522
2477
|
}
|
|
2523
2478
|
else if (score < 70) {
|
|
2524
2479
|
results.push({
|
|
2525
2480
|
status: 'warning',
|
|
2526
|
-
message: `Score could be better: ${score}
|
|
2481
|
+
message: `Score could be better: ${score}% (${slotSummary})`,
|
|
2527
2482
|
fix: 'Target 70%+ for championship AI context'
|
|
2528
2483
|
});
|
|
2529
2484
|
}
|
|
2530
2485
|
else {
|
|
2531
2486
|
results.push({
|
|
2532
2487
|
status: 'ok',
|
|
2533
|
-
message: `Great score: ${score}
|
|
2488
|
+
message: `Great score: ${score}% (${slotSummary})`
|
|
2534
2489
|
});
|
|
2535
2490
|
}
|
|
2536
2491
|
}
|