faf-mcp 2.1.2 โ 2.2.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 +18 -1
- package/CLAUDE.md +6 -4
- package/README.md +8 -6
- package/dist/src/cli.js +2 -15
- package/dist/src/cli.js.map +1 -1
- package/dist/src/faf-core/commands/bi-sync.js +3 -2
- package/dist/src/faf-core/commands/bi-sync.js.map +1 -1
- package/dist/src/faf-core/extract/turbocat-bridge.d.ts +34 -0
- package/dist/src/faf-core/extract/turbocat-bridge.js +84 -0
- package/dist/src/faf-core/extract/turbocat-bridge.js.map +1 -0
- package/dist/src/faf-core/inject.d.ts +19 -0
- package/dist/src/faf-core/inject.js +57 -0
- package/dist/src/faf-core/inject.js.map +1 -0
- package/dist/src/faf-core/parsers/agents-parser.js +3 -2
- package/dist/src/faf-core/parsers/agents-parser.js.map +1 -1
- package/dist/src/faf-core/parsers/cursorrules-parser.js +6 -2
- package/dist/src/faf-core/parsers/cursorrules-parser.js.map +1 -1
- package/dist/src/faf-core/parsers/gemini-parser.js +3 -2
- package/dist/src/faf-core/parsers/gemini-parser.js.map +1 -1
- package/dist/src/handlers/championship-tools.js +11 -11
- package/dist/src/handlers/championship-tools.js.map +1 -1
- package/dist/src/handlers/fileHandler.js +31 -22
- package/dist/src/handlers/fileHandler.js.map +1 -1
- package/dist/src/handlers/tools.d.ts +0 -4
- package/dist/src/handlers/tools.js +128 -372
- package/dist/src/handlers/tools.js.map +1 -1
- package/dist/src/server.d.ts +2 -4
- package/dist/src/server.js +1 -92
- package/dist/src/server.js.map +1 -1
- package/dist/src/utils/safe-path.d.ts +66 -0
- package/dist/src/utils/safe-path.js +203 -0
- package/dist/src/utils/safe-path.js.map +1 -0
- package/package.json +3 -7
- package/project.faf +4 -3
- package/scripts/check-stylesheet-drift.mjs +1 -1
|
@@ -40,6 +40,7 @@ const os = __importStar(require("os"));
|
|
|
40
40
|
const pathModule = __importStar(require("path"));
|
|
41
41
|
const fuzzy_detector_1 = require("../utils/fuzzy-detector");
|
|
42
42
|
const faf_file_finder_js_1 = require("../utils/faf-file-finder.js");
|
|
43
|
+
const safe_path_1 = require("../utils/safe-path");
|
|
43
44
|
const version_1 = require("../version");
|
|
44
45
|
const path_resolver_1 = require("../utils/path-resolver");
|
|
45
46
|
// v2.1.1: single-source scoring โ same path the championship handler uses.
|
|
@@ -55,6 +56,7 @@ const path_resolver_1 = require("../utils/path-resolver");
|
|
|
55
56
|
// once faf-cli drops the bad `bun` condition. See the bridge header for full
|
|
56
57
|
// rationale and the linked tracked issue.
|
|
57
58
|
const faf_cli_bridge_js_1 = require("../utils/faf-cli-bridge.js");
|
|
59
|
+
const turbocat_bridge_js_1 = require("../faf-core/extract/turbocat-bridge.js");
|
|
58
60
|
class FafToolHandler {
|
|
59
61
|
engineAdapter;
|
|
60
62
|
constructor(engineAdapter) {
|
|
@@ -66,12 +68,10 @@ class FafToolHandler {
|
|
|
66
68
|
*/
|
|
67
69
|
getProjectPath(explicitPath) {
|
|
68
70
|
if (explicitPath) {
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
// Resolve to absolute path
|
|
74
|
-
const resolvedPath = pathModule.resolve(expandedPath);
|
|
71
|
+
// Confine the caller-supplied path. A passed *file* must be a .faf/.fafm
|
|
72
|
+
// context file; absolute/`..` escapes to secrets are refused. Throws
|
|
73
|
+
// PathConfinementError, caught centrally in callTool() (CWE-22/73/200).
|
|
74
|
+
const resolvedPath = (0, safe_path_1.confinePath)(explicitPath);
|
|
75
75
|
// If it's a file path, get the directory
|
|
76
76
|
const projectDir = fs.existsSync(resolvedPath) && fs.statSync(resolvedPath).isFile()
|
|
77
77
|
? pathModule.dirname(resolvedPath)
|
|
@@ -281,7 +281,7 @@ class FafToolHandler {
|
|
|
281
281
|
},
|
|
282
282
|
{
|
|
283
283
|
name: 'faf_read',
|
|
284
|
-
description: 'Read
|
|
284
|
+
description: 'Read a file within the project root (cwd / FAF_ALLOWED_ROOTS). Paths that escape the project are refused.',
|
|
285
285
|
annotations: {
|
|
286
286
|
title: 'Read .faf File',
|
|
287
287
|
readOnlyHint: true,
|
|
@@ -301,7 +301,7 @@ class FafToolHandler {
|
|
|
301
301
|
},
|
|
302
302
|
{
|
|
303
303
|
name: 'faf_write',
|
|
304
|
-
description: 'Write
|
|
304
|
+
description: 'Write a file within the project root (cwd / FAF_ALLOWED_ROOTS). Paths that escape the project are refused.',
|
|
305
305
|
annotations: {
|
|
306
306
|
title: 'Write .faf File',
|
|
307
307
|
readOnlyHint: false,
|
|
@@ -358,20 +358,9 @@ class FafToolHandler {
|
|
|
358
358
|
additionalProperties: false
|
|
359
359
|
}
|
|
360
360
|
},
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
annotations: {
|
|
365
|
-
title: 'Chat about FAF',
|
|
366
|
-
readOnlyHint: true,
|
|
367
|
-
openWorldHint: false
|
|
368
|
-
},
|
|
369
|
-
inputSchema: {
|
|
370
|
-
type: 'object',
|
|
371
|
-
properties: {},
|
|
372
|
-
additionalProperties: false
|
|
373
|
-
}
|
|
374
|
-
},
|
|
361
|
+
// faf_chat โ DEPRECATED, un-advertised. The host IS the chat โ a chat-shim
|
|
362
|
+
// tool is redundant. Dispatch keeps a deprecation stub (below). Fleet sweep โ
|
|
363
|
+
// mirrors grok-faf-mcp + claude-faf-mcp's retire.
|
|
375
364
|
{
|
|
376
365
|
name: 'faf_friday',
|
|
377
366
|
description: '๐ Friday Features - Chrome Extension detection, fuzzy matching & more! ๐งกโก๏ธ',
|
|
@@ -705,81 +694,91 @@ class FafToolHandler {
|
|
|
705
694
|
if (!name || typeof name !== 'string') {
|
|
706
695
|
throw new Error('Tool name must be a non-empty string');
|
|
707
696
|
}
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
697
|
+
try {
|
|
698
|
+
switch (name) {
|
|
699
|
+
case 'faf_status':
|
|
700
|
+
return await this.handleFafStatus(args);
|
|
701
|
+
case 'faf_score':
|
|
702
|
+
return await this.handleFafScore(args);
|
|
703
|
+
case 'faf_init':
|
|
704
|
+
return await this.handleFafInit(args);
|
|
705
|
+
case 'faf_trust':
|
|
706
|
+
return await this.handleFafTrust(args);
|
|
707
|
+
case 'faf_sync':
|
|
708
|
+
return await this.handleFafSync(args);
|
|
709
|
+
case 'faf_enhance':
|
|
710
|
+
return await this.handleFafEnhance(args);
|
|
711
|
+
case 'faf_bi_sync':
|
|
712
|
+
return await this.handleFafBiSync(args);
|
|
713
|
+
case 'faf_clear':
|
|
714
|
+
return await this.handleFafClear(args);
|
|
715
|
+
case 'faf_debug':
|
|
716
|
+
return await this.handleFafDebug(args);
|
|
717
|
+
case 'faf_about':
|
|
718
|
+
return await this.handleFafAbout(args);
|
|
719
|
+
case 'faf_what':
|
|
720
|
+
return await this.handleFafWhat(args);
|
|
721
|
+
case 'faf_read': {
|
|
722
|
+
// Handle faf_read specially to set context when reading project.faf files
|
|
723
|
+
const readResult = await fileHandler_1.fileHandlers.faf_read(args);
|
|
724
|
+
// If reading a project.faf file, set the session context
|
|
725
|
+
if (args?.path && (args.path.includes('project.faf') || args.path.endsWith('.faf'))) {
|
|
726
|
+
this.getProjectPath(args.path);
|
|
727
|
+
}
|
|
728
|
+
return readResult;
|
|
737
729
|
}
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
730
|
+
case 'faf_chat':
|
|
731
|
+
return await this.handleFafChat(args);
|
|
732
|
+
case 'faf_friday':
|
|
733
|
+
return await this.handleFafFriday(args);
|
|
734
|
+
case 'faf_write':
|
|
735
|
+
return await fileHandler_1.fileHandlers.faf_write(args);
|
|
736
|
+
case 'faf_list':
|
|
737
|
+
return await this.handleFafList(args);
|
|
738
|
+
case 'faf_guide':
|
|
739
|
+
return await this.handleFafGuide(args);
|
|
740
|
+
case 'faf_readme':
|
|
741
|
+
return await this.handleFafReadme(args);
|
|
742
|
+
case 'faf_human_add':
|
|
743
|
+
return await this.handleFafHumanAdd(args);
|
|
744
|
+
case 'faf_check':
|
|
745
|
+
return await this.handleFafCheck(args);
|
|
746
|
+
case 'faf_context':
|
|
747
|
+
return await this.handleFafContext(args);
|
|
748
|
+
case 'faf_go':
|
|
749
|
+
return await this.handleFafGo(args);
|
|
750
|
+
case 'faf_auto':
|
|
751
|
+
return await this.handleFafAuto(args);
|
|
752
|
+
case 'faf_dna':
|
|
753
|
+
return await this.handleFafDna(args);
|
|
754
|
+
case 'faf_formats':
|
|
755
|
+
return await this.handleFafFormats(args);
|
|
756
|
+
case 'faf_quick':
|
|
757
|
+
return await this.handleFafQuick(args);
|
|
758
|
+
case 'faf_doctor':
|
|
759
|
+
return await this.handleFafDoctor(args);
|
|
760
|
+
// v4.5.0 Interop tools
|
|
761
|
+
case 'faf_agents':
|
|
762
|
+
return await this.handleFafAgents(args);
|
|
763
|
+
case 'faf_cursor':
|
|
764
|
+
return await this.handleFafCursor(args);
|
|
765
|
+
case 'faf_gemini':
|
|
766
|
+
return await this.handleFafGemini(args);
|
|
767
|
+
case 'faf_conductor':
|
|
768
|
+
return await this.handleFafConductor(args);
|
|
769
|
+
case 'faf_git':
|
|
770
|
+
return await this.handleFafGit(args);
|
|
771
|
+
default:
|
|
772
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
catch (err) {
|
|
776
|
+
// Central catch for path-confinement violations from getProjectPath()
|
|
777
|
+
// (CWE-22/73/200). Anything else propagates unchanged.
|
|
778
|
+
if (err instanceof safe_path_1.PathConfinementError) {
|
|
779
|
+
return { content: [{ type: 'text', text: `PATH DENIED\n\n${err.message}` }], isError: true };
|
|
780
|
+
}
|
|
781
|
+
throw err;
|
|
783
782
|
}
|
|
784
783
|
}
|
|
785
784
|
async handleFafStatus(args) {
|
|
@@ -1307,37 +1306,17 @@ ${debugInfo.permissions.fafError ? ` FAF Error: ${debugInfo.permissions.fafErr
|
|
|
1307
1306
|
}
|
|
1308
1307
|
}
|
|
1309
1308
|
async handleFafChat(_args) {
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
}
|
|
1320
|
-
|
|
1321
|
-
// Format the response text
|
|
1322
|
-
const responseText = typeof result.data === 'string'
|
|
1323
|
-
? result.data
|
|
1324
|
-
: result.data?.output || JSON.stringify(result.data, null, 2);
|
|
1325
|
-
return {
|
|
1326
|
-
content: [{
|
|
1327
|
-
type: 'text',
|
|
1328
|
-
text: responseText
|
|
1329
|
-
}]
|
|
1330
|
-
};
|
|
1331
|
-
}
|
|
1332
|
-
catch (error) {
|
|
1333
|
-
return {
|
|
1334
|
-
content: [{
|
|
1335
|
-
type: 'text',
|
|
1336
|
-
text: `Error running faf chat: ${error instanceof Error ? error.message : String(error)}`
|
|
1337
|
-
}],
|
|
1338
|
-
isError: true
|
|
1339
|
-
};
|
|
1340
|
-
}
|
|
1309
|
+
// DEPRECATED: the host IS the chat โ a chat-shim MCP tool is redundant.
|
|
1310
|
+
// Un-advertised in listTools; this stub stays so anyone still wired gets a
|
|
1311
|
+
// clear signal, not a crash. The old body shelled `faf chat` via the engine
|
|
1312
|
+
// subprocess (a command faf-cli no longer ships) โ removing it ends that dead shell.
|
|
1313
|
+
return {
|
|
1314
|
+
content: [{
|
|
1315
|
+
type: 'text',
|
|
1316
|
+
text: 'faf_chat is retired โ the host is your chat, just talk here. ' +
|
|
1317
|
+
'For FAF: faf_init / faf_score / faf_sync, or "ask questions" to build context.',
|
|
1318
|
+
}],
|
|
1319
|
+
};
|
|
1341
1320
|
}
|
|
1342
1321
|
async handleFafFriday(args) {
|
|
1343
1322
|
const { test } = args || {};
|
|
@@ -1888,127 +1867,14 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
|
|
|
1888
1867
|
}
|
|
1889
1868
|
const fafContent = fs.readFileSync(fafResult.path, 'utf-8');
|
|
1890
1869
|
const fafData = yaml.parse(fafContent) || {};
|
|
1891
|
-
//
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
'project.name': {
|
|
1900
|
-
question: 'What is the name of this project?',
|
|
1901
|
-
header: 'Name',
|
|
1902
|
-
type: 'text',
|
|
1903
|
-
required: true
|
|
1904
|
-
},
|
|
1905
|
-
'project.main_language': {
|
|
1906
|
-
question: 'What is the primary programming language?',
|
|
1907
|
-
header: 'Language',
|
|
1908
|
-
type: 'select',
|
|
1909
|
-
required: true,
|
|
1910
|
-
options: [
|
|
1911
|
-
{ label: 'TypeScript', value: 'TypeScript', description: 'JavaScript with types' },
|
|
1912
|
-
{ label: 'JavaScript', value: 'JavaScript', description: 'Vanilla JS or Node.js' },
|
|
1913
|
-
{ label: 'Python', value: 'Python', description: 'Python 3.x' },
|
|
1914
|
-
{ label: 'Rust', value: 'Rust', description: 'Systems programming' },
|
|
1915
|
-
{ label: 'Go', value: 'Go', description: 'Golang' },
|
|
1916
|
-
{ label: 'Other', value: 'Other', description: 'Specify manually' }
|
|
1917
|
-
]
|
|
1918
|
-
},
|
|
1919
|
-
'human_context.why': {
|
|
1920
|
-
question: 'Why does this project exist? (motivation)',
|
|
1921
|
-
header: 'Why',
|
|
1922
|
-
type: 'text',
|
|
1923
|
-
required: true
|
|
1924
|
-
},
|
|
1925
|
-
'human_context.who': {
|
|
1926
|
-
question: 'Who uses this project? (target audience)',
|
|
1927
|
-
header: 'Who',
|
|
1928
|
-
type: 'text',
|
|
1929
|
-
required: false
|
|
1930
|
-
},
|
|
1931
|
-
'human_context.what': {
|
|
1932
|
-
question: 'What problem does this solve?',
|
|
1933
|
-
header: 'What',
|
|
1934
|
-
type: 'text',
|
|
1935
|
-
required: false
|
|
1936
|
-
},
|
|
1937
|
-
'human_context.where': {
|
|
1938
|
-
question: 'Where does this run? (environment)',
|
|
1939
|
-
header: 'Where',
|
|
1940
|
-
type: 'text',
|
|
1941
|
-
required: false
|
|
1942
|
-
},
|
|
1943
|
-
'human_context.when': {
|
|
1944
|
-
question: 'When was this started or what phase is it in?',
|
|
1945
|
-
header: 'When',
|
|
1946
|
-
type: 'text',
|
|
1947
|
-
required: false
|
|
1948
|
-
},
|
|
1949
|
-
'human_context.how': {
|
|
1950
|
-
question: 'How should AI assist with this project?',
|
|
1951
|
-
header: 'How',
|
|
1952
|
-
type: 'text',
|
|
1953
|
-
required: false
|
|
1954
|
-
},
|
|
1955
|
-
'stack.frontend': {
|
|
1956
|
-
question: 'What frontend framework do you use?',
|
|
1957
|
-
header: 'Frontend',
|
|
1958
|
-
type: 'select',
|
|
1959
|
-
required: false,
|
|
1960
|
-
options: [
|
|
1961
|
-
{ label: 'React', value: 'React', description: 'React.js' },
|
|
1962
|
-
{ label: 'Vue', value: 'Vue', description: 'Vue.js' },
|
|
1963
|
-
{ label: 'Svelte', value: 'Svelte', description: 'Svelte/SvelteKit' },
|
|
1964
|
-
{ label: 'Next.js', value: 'Next.js', description: 'React framework' },
|
|
1965
|
-
{ label: 'None', value: 'None', description: 'No frontend' },
|
|
1966
|
-
{ label: 'Other', value: 'Other', description: 'Specify manually' }
|
|
1967
|
-
]
|
|
1968
|
-
},
|
|
1969
|
-
'stack.backend': {
|
|
1970
|
-
question: 'What backend framework do you use?',
|
|
1971
|
-
header: 'Backend',
|
|
1972
|
-
type: 'select',
|
|
1973
|
-
required: false,
|
|
1974
|
-
options: [
|
|
1975
|
-
{ label: 'Express', value: 'Express', description: 'Node.js Express' },
|
|
1976
|
-
{ label: 'Fastify', value: 'Fastify', description: 'Node.js Fastify' },
|
|
1977
|
-
{ label: 'Django', value: 'Django', description: 'Python Django' },
|
|
1978
|
-
{ label: 'FastAPI', value: 'FastAPI', description: 'Python FastAPI' },
|
|
1979
|
-
{ label: 'None', value: 'None', description: 'No backend' },
|
|
1980
|
-
{ label: 'Other', value: 'Other', description: 'Specify manually' }
|
|
1981
|
-
]
|
|
1982
|
-
},
|
|
1983
|
-
'stack.database': {
|
|
1984
|
-
question: 'What database do you use?',
|
|
1985
|
-
header: 'Database',
|
|
1986
|
-
type: 'select',
|
|
1987
|
-
required: false,
|
|
1988
|
-
options: [
|
|
1989
|
-
{ label: 'PostgreSQL', value: 'PostgreSQL', description: 'Relational database' },
|
|
1990
|
-
{ label: 'MongoDB', value: 'MongoDB', description: 'Document database' },
|
|
1991
|
-
{ label: 'SQLite', value: 'SQLite', description: 'File-based database' },
|
|
1992
|
-
{ label: 'Supabase', value: 'Supabase', description: 'Postgres + auth' },
|
|
1993
|
-
{ label: 'None', value: 'None', description: 'No database' },
|
|
1994
|
-
{ label: 'Other', value: 'Other', description: 'Specify manually' }
|
|
1995
|
-
]
|
|
1996
|
-
},
|
|
1997
|
-
'stack.hosting': {
|
|
1998
|
-
question: 'Where is this hosted/deployed?',
|
|
1999
|
-
header: 'Hosting',
|
|
2000
|
-
type: 'select',
|
|
2001
|
-
required: false,
|
|
2002
|
-
options: [
|
|
2003
|
-
{ label: 'Vercel', value: 'Vercel', description: 'Frontend/serverless' },
|
|
2004
|
-
{ label: 'AWS', value: 'AWS', description: 'Amazon Web Services' },
|
|
2005
|
-
{ label: 'Cloudflare', value: 'Cloudflare', description: 'Workers/Pages' },
|
|
2006
|
-
{ label: 'Railway', value: 'Railway', description: 'App hosting' },
|
|
2007
|
-
{ label: 'Local only', value: 'Local', description: 'Not deployed' },
|
|
2008
|
-
{ label: 'Other', value: 'Other', description: 'Specify manually' }
|
|
2009
|
-
]
|
|
2010
|
-
}
|
|
2011
|
-
};
|
|
1870
|
+
// Single-source the HUMAN interview from faf-cli's canonical SIX_WS_INTERVIEW
|
|
1871
|
+
// (8 = the 6Ws + name + goal; public since 6.9.0). This is THE 6Ws โ human-
|
|
1872
|
+
// only context that can't be derived. main_language + stack are NOT here:
|
|
1873
|
+
// they're SOURCED by Turbo-Cat (faf-cli's separate STACK_INTERVIEW if ever
|
|
1874
|
+
// needed), never asked of a human. (Decision: single-source the 8-Q 6Ws
|
|
1875
|
+
// Interview, wolfejam 2026-06-10 โ language is not on the human side.)
|
|
1876
|
+
const { SIX_WS_INTERVIEW } = await faf_cli_bridge_js_1.fafCli;
|
|
1877
|
+
const QUESTION_REGISTRY = Object.fromEntries(SIX_WS_INTERVIEW.map((q) => [q.path, q]));
|
|
2012
1878
|
// Priority order for questions
|
|
2013
1879
|
const priorityOrder = [
|
|
2014
1880
|
'project.goal',
|
|
@@ -2088,14 +1954,11 @@ All work: \`faf init\`, \`faf init new\`, \`faf init --new\`, \`faf init -new\`
|
|
|
2088
1954
|
}]
|
|
2089
1955
|
};
|
|
2090
1956
|
}
|
|
2091
|
-
// PHASE 1: Analyze and return questions
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
const
|
|
2095
|
-
|
|
2096
|
-
missingFields.push(fieldPath);
|
|
2097
|
-
}
|
|
2098
|
-
}
|
|
1957
|
+
// PHASE 1: Analyze and return questions โ the canonical 6Ws (SIX_WS_INTERVIEW),
|
|
1958
|
+
// scoped to slots that are empty AND active (slotignored is never asked).
|
|
1959
|
+
const missingFields = SIX_WS_INTERVIEW
|
|
1960
|
+
.filter((q) => { const v = getNestedValue(fafData, q.path); return v !== 'slotignored' && isEmpty(v); })
|
|
1961
|
+
.map((q) => q.path);
|
|
2099
1962
|
// Calculate current score
|
|
2100
1963
|
const totalFields = Object.keys(QUESTION_REGISTRY).length;
|
|
2101
1964
|
const filledFields = totalFields - missingFields.length;
|
|
@@ -2216,13 +2079,16 @@ faffless: true
|
|
|
2216
2079
|
const fafContent = fs.readFileSync(fafPath, 'utf-8');
|
|
2217
2080
|
const fafData = yaml.parse(fafContent) || {};
|
|
2218
2081
|
currentScore = this.calculateSimpleScore(fafData);
|
|
2219
|
-
// Step 2: Run TURBO-CAT format discovery
|
|
2220
|
-
const formatsResult = await
|
|
2221
|
-
if (formatsResult.discoveredFormats.length > 0) {
|
|
2222
|
-
// Apply slot fills to .faf
|
|
2082
|
+
// Step 2: Run TURBO-CAT format discovery โ composed from faf-cli's engine.
|
|
2083
|
+
const formatsResult = await (0, turbocat_bridge_js_1.composedTurboCat)(cwd);
|
|
2084
|
+
if (formatsResult && formatsResult.discoveredFormats.length > 0) {
|
|
2085
|
+
// Apply slot fills to .faf. turboCatSlots routes them into the correct
|
|
2086
|
+
// .faf sections; fill the stack section (main_language lives under
|
|
2087
|
+
// project, which is a string here) โ clean keys, no noise.
|
|
2088
|
+
const slots = await (0, turbocat_bridge_js_1.composedTurboCatSlots)(cwd);
|
|
2223
2089
|
if (!fafData.stack)
|
|
2224
2090
|
fafData.stack = {};
|
|
2225
|
-
for (const [key, value] of Object.entries(
|
|
2091
|
+
for (const [key, value] of Object.entries(slots?.stack ?? {})) {
|
|
2226
2092
|
if (!fafData.stack[key] || fafData.stack[key] === 'None') {
|
|
2227
2093
|
fafData.stack[key] = value;
|
|
2228
2094
|
}
|
|
@@ -2458,7 +2324,7 @@ ${fafData.stack_signature || 'Auto-detected stack'}
|
|
|
2458
2324
|
const cwd = this.getProjectPath(args?.path);
|
|
2459
2325
|
const startTime = Date.now();
|
|
2460
2326
|
try {
|
|
2461
|
-
const analysis = await
|
|
2327
|
+
const analysis = await (0, turbocat_bridge_js_1.turboCatDisplay)(cwd);
|
|
2462
2328
|
const elapsed = Date.now() - startTime;
|
|
2463
2329
|
if (args?.json) {
|
|
2464
2330
|
return {
|
|
@@ -2497,116 +2363,6 @@ ${fafData.stack_signature || 'Auto-detected stack'}
|
|
|
2497
2363
|
};
|
|
2498
2364
|
}
|
|
2499
2365
|
}
|
|
2500
|
-
/**
|
|
2501
|
-
* Internal helper: Discover formats in a directory (TURBO-CAT logic)
|
|
2502
|
-
*/
|
|
2503
|
-
async discoverFormatsInternal(projectDir) {
|
|
2504
|
-
const path = await import('path');
|
|
2505
|
-
// Known format files and their categories
|
|
2506
|
-
const KNOWN_FORMATS = {
|
|
2507
|
-
'package.json': { category: 'package-manager', priority: 35 },
|
|
2508
|
-
'tsconfig.json': { category: 'typescript-config', priority: 30 },
|
|
2509
|
-
'Cargo.toml': { category: 'package-manager', priority: 35 },
|
|
2510
|
-
'pyproject.toml': { category: 'package-manager', priority: 35 },
|
|
2511
|
-
'requirements.txt': { category: 'package-manager', priority: 25 },
|
|
2512
|
-
'go.mod': { category: 'package-manager', priority: 35 },
|
|
2513
|
-
'pom.xml': { category: 'package-manager', priority: 35 },
|
|
2514
|
-
'README.md': { category: 'documentation', priority: 20 },
|
|
2515
|
-
'CLAUDE.md': { category: 'ai-context', priority: 40 },
|
|
2516
|
-
'project.faf': { category: 'faf-context', priority: 45 },
|
|
2517
|
-
'.faf': { category: 'faf-context', priority: 45 },
|
|
2518
|
-
'Dockerfile': { category: 'docker', priority: 25 },
|
|
2519
|
-
'docker-compose.yml': { category: 'docker', priority: 25 },
|
|
2520
|
-
'vercel.json': { category: 'deployment', priority: 20 },
|
|
2521
|
-
'netlify.toml': { category: 'deployment', priority: 20 },
|
|
2522
|
-
'.eslintrc.json': { category: 'linting', priority: 15 },
|
|
2523
|
-
'.prettierrc': { category: 'linting', priority: 15 },
|
|
2524
|
-
'jest.config.js': { category: 'testing', priority: 20 },
|
|
2525
|
-
'vitest.config.ts': { category: 'testing', priority: 20 },
|
|
2526
|
-
'svelte.config.js': { category: 'framework', priority: 30 },
|
|
2527
|
-
'next.config.js': { category: 'framework', priority: 30 },
|
|
2528
|
-
'vite.config.ts': { category: 'build', priority: 25 },
|
|
2529
|
-
'webpack.config.js': { category: 'build', priority: 25 },
|
|
2530
|
-
'.github': { category: 'ci-cd', priority: 20 },
|
|
2531
|
-
'manifest.json': { category: 'chrome-extension', priority: 35 }
|
|
2532
|
-
};
|
|
2533
|
-
const discoveredFormats = [];
|
|
2534
|
-
let totalIntelligenceScore = 0;
|
|
2535
|
-
const slotFillRecommendations = {};
|
|
2536
|
-
const extractedContext = {};
|
|
2537
|
-
// Scan directory
|
|
2538
|
-
try {
|
|
2539
|
-
const files = fs.readdirSync(projectDir);
|
|
2540
|
-
for (const file of files) {
|
|
2541
|
-
if (KNOWN_FORMATS[file]) {
|
|
2542
|
-
const format = KNOWN_FORMATS[file];
|
|
2543
|
-
discoveredFormats.push({
|
|
2544
|
-
fileName: file,
|
|
2545
|
-
category: format.category,
|
|
2546
|
-
priority: format.priority
|
|
2547
|
-
});
|
|
2548
|
-
totalIntelligenceScore += format.priority;
|
|
2549
|
-
}
|
|
2550
|
-
}
|
|
2551
|
-
// Extract intelligence from package.json
|
|
2552
|
-
const pkgPath = path.join(projectDir, 'package.json');
|
|
2553
|
-
if (fs.existsSync(pkgPath)) {
|
|
2554
|
-
const pkgContent = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
2555
|
-
const allDeps = { ...pkgContent.dependencies, ...pkgContent.devDependencies };
|
|
2556
|
-
extractedContext.projectName = pkgContent.name;
|
|
2557
|
-
extractedContext.projectDescription = pkgContent.description;
|
|
2558
|
-
// Detect frameworks and fill slots
|
|
2559
|
-
if (allDeps['typescript'] || allDeps['@types/node']) {
|
|
2560
|
-
slotFillRecommendations['mainLanguage'] = 'TypeScript';
|
|
2561
|
-
}
|
|
2562
|
-
if (allDeps['react'] || allDeps['next']) {
|
|
2563
|
-
slotFillRecommendations['frontend'] = allDeps['next'] ? 'Next.js' : 'React';
|
|
2564
|
-
}
|
|
2565
|
-
if (allDeps['vue'] || allDeps['nuxt']) {
|
|
2566
|
-
slotFillRecommendations['frontend'] = allDeps['nuxt'] ? 'Nuxt' : 'Vue';
|
|
2567
|
-
}
|
|
2568
|
-
if (allDeps['svelte'] || allDeps['@sveltejs/kit']) {
|
|
2569
|
-
slotFillRecommendations['frontend'] = allDeps['@sveltejs/kit'] ? 'SvelteKit' : 'Svelte';
|
|
2570
|
-
}
|
|
2571
|
-
if (allDeps['express']) {
|
|
2572
|
-
slotFillRecommendations['backend'] = 'Express';
|
|
2573
|
-
}
|
|
2574
|
-
if (allDeps['fastify']) {
|
|
2575
|
-
slotFillRecommendations['backend'] = 'Fastify';
|
|
2576
|
-
}
|
|
2577
|
-
if (allDeps['vite']) {
|
|
2578
|
-
slotFillRecommendations['build'] = 'Vite';
|
|
2579
|
-
}
|
|
2580
|
-
if (allDeps['jest'] || allDeps['vitest']) {
|
|
2581
|
-
slotFillRecommendations['testing'] = allDeps['vitest'] ? 'Vitest' : 'Jest';
|
|
2582
|
-
}
|
|
2583
|
-
}
|
|
2584
|
-
// Check for deployment indicators
|
|
2585
|
-
if (fs.existsSync(path.join(projectDir, 'vercel.json'))) {
|
|
2586
|
-
slotFillRecommendations['hosting'] = 'Vercel';
|
|
2587
|
-
}
|
|
2588
|
-
else if (fs.existsSync(path.join(projectDir, 'netlify.toml'))) {
|
|
2589
|
-
slotFillRecommendations['hosting'] = 'Netlify';
|
|
2590
|
-
}
|
|
2591
|
-
}
|
|
2592
|
-
catch (error) {
|
|
2593
|
-
// Ignore errors, return empty results
|
|
2594
|
-
}
|
|
2595
|
-
// Generate stack signature
|
|
2596
|
-
const parts = [];
|
|
2597
|
-
if (slotFillRecommendations['mainLanguage'])
|
|
2598
|
-
parts.push(slotFillRecommendations['mainLanguage'].toLowerCase());
|
|
2599
|
-
if (slotFillRecommendations['frontend'])
|
|
2600
|
-
parts.push(slotFillRecommendations['frontend'].toLowerCase());
|
|
2601
|
-
const stackSignature = parts.length > 0 ? parts.join('-') : 'unknown-stack';
|
|
2602
|
-
return {
|
|
2603
|
-
discoveredFormats,
|
|
2604
|
-
totalIntelligenceScore,
|
|
2605
|
-
stackSignature,
|
|
2606
|
-
slotFillRecommendations,
|
|
2607
|
-
extractedContext
|
|
2608
|
-
};
|
|
2609
|
-
}
|
|
2610
2366
|
/**
|
|
2611
2367
|
* Internal helper: Calculate simple score from .faf data
|
|
2612
2368
|
*/
|