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.
Files changed (35) hide show
  1. package/CHANGELOG.md +18 -1
  2. package/CLAUDE.md +6 -4
  3. package/README.md +8 -6
  4. package/dist/src/cli.js +2 -15
  5. package/dist/src/cli.js.map +1 -1
  6. package/dist/src/faf-core/commands/bi-sync.js +3 -2
  7. package/dist/src/faf-core/commands/bi-sync.js.map +1 -1
  8. package/dist/src/faf-core/extract/turbocat-bridge.d.ts +34 -0
  9. package/dist/src/faf-core/extract/turbocat-bridge.js +84 -0
  10. package/dist/src/faf-core/extract/turbocat-bridge.js.map +1 -0
  11. package/dist/src/faf-core/inject.d.ts +19 -0
  12. package/dist/src/faf-core/inject.js +57 -0
  13. package/dist/src/faf-core/inject.js.map +1 -0
  14. package/dist/src/faf-core/parsers/agents-parser.js +3 -2
  15. package/dist/src/faf-core/parsers/agents-parser.js.map +1 -1
  16. package/dist/src/faf-core/parsers/cursorrules-parser.js +6 -2
  17. package/dist/src/faf-core/parsers/cursorrules-parser.js.map +1 -1
  18. package/dist/src/faf-core/parsers/gemini-parser.js +3 -2
  19. package/dist/src/faf-core/parsers/gemini-parser.js.map +1 -1
  20. package/dist/src/handlers/championship-tools.js +11 -11
  21. package/dist/src/handlers/championship-tools.js.map +1 -1
  22. package/dist/src/handlers/fileHandler.js +31 -22
  23. package/dist/src/handlers/fileHandler.js.map +1 -1
  24. package/dist/src/handlers/tools.d.ts +0 -4
  25. package/dist/src/handlers/tools.js +128 -372
  26. package/dist/src/handlers/tools.js.map +1 -1
  27. package/dist/src/server.d.ts +2 -4
  28. package/dist/src/server.js +1 -92
  29. package/dist/src/server.js.map +1 -1
  30. package/dist/src/utils/safe-path.d.ts +66 -0
  31. package/dist/src/utils/safe-path.js +203 -0
  32. package/dist/src/utils/safe-path.js.map +1 -0
  33. package/package.json +3 -7
  34. package/project.faf +4 -3
  35. 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
- // Expand tilde
70
- const expandedPath = explicitPath.startsWith('~')
71
- ? pathModule.join(os.homedir(), explicitPath.slice(1))
72
- : explicitPath;
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 content from any file on the local filesystem',
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 content to any file on the local filesystem',
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
- name: 'faf_chat',
363
- description: '๐Ÿ—ฃ๏ธ Natural language project.faf generation - Ask 6W questions (Who/What/Why/Where/When/How) to build complete human context ๐Ÿงกโšก๏ธ',
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
- switch (name) {
709
- case 'faf_status':
710
- return await this.handleFafStatus(args);
711
- case 'faf_score':
712
- return await this.handleFafScore(args);
713
- case 'faf_init':
714
- return await this.handleFafInit(args);
715
- case 'faf_trust':
716
- return await this.handleFafTrust(args);
717
- case 'faf_sync':
718
- return await this.handleFafSync(args);
719
- case 'faf_enhance':
720
- return await this.handleFafEnhance(args);
721
- case 'faf_bi_sync':
722
- return await this.handleFafBiSync(args);
723
- case 'faf_clear':
724
- return await this.handleFafClear(args);
725
- case 'faf_debug':
726
- return await this.handleFafDebug(args);
727
- case 'faf_about':
728
- return await this.handleFafAbout(args);
729
- case 'faf_what':
730
- return await this.handleFafWhat(args);
731
- case 'faf_read': {
732
- // Handle faf_read specially to set context when reading project.faf files
733
- const readResult = await fileHandler_1.fileHandlers.faf_read(args);
734
- // If reading a project.faf file, set the session context
735
- if (args?.path && (args.path.includes('project.faf') || args.path.endsWith('.faf'))) {
736
- this.getProjectPath(args.path);
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
- return readResult;
739
- }
740
- case 'faf_chat':
741
- return await this.handleFafChat(args);
742
- case 'faf_friday':
743
- return await this.handleFafFriday(args);
744
- case 'faf_write':
745
- return await fileHandler_1.fileHandlers.faf_write(args);
746
- case 'faf_list':
747
- return await this.handleFafList(args);
748
- case 'faf_guide':
749
- return await this.handleFafGuide(args);
750
- case 'faf_readme':
751
- return await this.handleFafReadme(args);
752
- case 'faf_human_add':
753
- return await this.handleFafHumanAdd(args);
754
- case 'faf_check':
755
- return await this.handleFafCheck(args);
756
- case 'faf_context':
757
- return await this.handleFafContext(args);
758
- case 'faf_go':
759
- return await this.handleFafGo(args);
760
- case 'faf_auto':
761
- return await this.handleFafAuto(args);
762
- case 'faf_dna':
763
- return await this.handleFafDna(args);
764
- case 'faf_formats':
765
- return await this.handleFafFormats(args);
766
- case 'faf_quick':
767
- return await this.handleFafQuick(args);
768
- case 'faf_doctor':
769
- return await this.handleFafDoctor(args);
770
- // v4.5.0 Interop tools
771
- case 'faf_agents':
772
- return await this.handleFafAgents(args);
773
- case 'faf_cursor':
774
- return await this.handleFafCursor(args);
775
- case 'faf_gemini':
776
- return await this.handleFafGemini(args);
777
- case 'faf_conductor':
778
- return await this.handleFafConductor(args);
779
- case 'faf_git':
780
- return await this.handleFafGit(args);
781
- default:
782
- throw new Error(`Unknown tool: ${name}`);
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
- try {
1311
- const result = await this.engineAdapter.callEngine('chat');
1312
- if (!result.success) {
1313
- return {
1314
- content: [{
1315
- type: 'text',
1316
- text: `Error running faf chat: ${result.error || 'Unknown error'}`
1317
- }],
1318
- isError: true
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
- // Question registry - maps field paths to questions
1892
- const QUESTION_REGISTRY = {
1893
- 'project.goal': {
1894
- question: 'What does this project do? (one sentence)',
1895
- header: 'Goal',
1896
- type: 'text',
1897
- required: true
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
- const missingFields = [];
2093
- for (const fieldPath of Object.keys(QUESTION_REGISTRY)) {
2094
- const value = getNestedValue(fafData, fieldPath);
2095
- if (isEmpty(value)) {
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 this.discoverFormatsInternal(cwd);
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(formatsResult.slotFillRecommendations)) {
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 this.discoverFormatsInternal(cwd);
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
  */