@@ -673,7 +673,10 @@ ${fernStructure}
673673${ existingContent }
674674
675675## Instructions
676- Update this file to address the documentation request. Use the Slack discussion context to understand the specific pain points and requirements mentioned by users. Follow Fern documentation best practices and maintain consistency with the existing structure.
676+ ${ context . isNewFile ?
677+ 'Create a new documentation file to address the documentation request. Use the Slack discussion context to understand the specific pain points and requirements mentioned by users. Follow Fern documentation best practices and create a well-structured guide.' :
678+ 'Update this file to address the documentation request. Use the Slack discussion context to understand the specific pain points and requirements mentioned by users. Follow Fern documentation best practices and maintain consistency with the existing structure.'
679+ }
677680
678681CRITICAL MDX SYNTAX REQUIREMENTS:
679682- ALL opening tags MUST have corresponding closing tags (e.g., <ParamField> must have </ParamField>)
@@ -756,9 +759,11 @@ ${fernStructure}
756759${ chunk . content }
757760
758761## Instructions
759- ${ chunk . isComplete ?
760- 'This is the final chunk of the file. Update this section to address the documentation request.' :
761- `This is chunk ${ i + 1 } of ${ chunks . length } from a larger file. Update only this section as needed to address the documentation request. Do not add or remove section headers unless specifically needed for this chunk.`
762+ ${ context . isNewFile ?
763+ `This is chunk ${ i + 1 } of ${ chunks . length } for a new documentation file. Create comprehensive content for this section to address the documentation request.` :
764+ chunk . isComplete ?
765+ 'This is the final chunk of the file. Update this section to address the documentation request.' :
766+ `This is chunk ${ i + 1 } of ${ chunks . length } from a larger file. Update only this section as needed to address the documentation request. Do not add or remove section headers unless specifically needed for this chunk.`
762767}
763768
764769Focus on:
@@ -1137,6 +1142,7 @@ Output your response as JSON:
11371142 } ) ;
11381143
11391144 // Look for exact or close matches to the suggested path
1145+ let foundMatch = false ;
11401146 for ( const result of targetedResults ) {
11411147 const resultPath = result . pathname || result . url || '' ;
11421148 if ( resultPath . includes ( suggestion . path ) ||
@@ -1149,14 +1155,54 @@ Output your response as JSON:
11491155 reason : suggestion . reason
11501156 } ) ;
11511157 existingPaths . add ( resultPath ) ;
1158+ foundMatch = true ;
11521159 break ;
11531160 }
11541161 }
1162+
1163+ // If no good match found, suggest creating a new file
1164+ if ( ! foundMatch && this . shouldSuggestNewFile ( suggestion , turbopufferResults ) ) {
1165+ console . log ( ` 💡 Suggesting new file creation: ${ suggestion . path } ` ) ;
1166+ enhancedResults . push ( {
1167+ pathname : suggestion . path ,
1168+ url : suggestion . path ,
1169+ title : this . generateTitleFromPath ( suggestion . path ) ,
1170+ isNewFile : true ,
1171+ aiSuggested : true ,
1172+ priority : suggestion . priority ,
1173+ reason : suggestion . reason ,
1174+ document : '' , // Empty content for new file
1175+ } ) ;
1176+ }
11551177 }
11561178
11571179 return enhancedResults ;
11581180 }
11591181
1182+ shouldSuggestNewFile ( suggestion , existingResults ) {
1183+ // Only suggest new files for high priority suggestions
1184+ if ( suggestion . priority !== 'high' ) return false ;
1185+
1186+ // Check if we have very few relevant results (weak matches)
1187+ const highRelevanceResults = existingResults . filter ( r => r . $dist && ( 1 - r . $dist ) > 0.7 ) ;
1188+ if ( highRelevanceResults . length >= 2 ) return false ;
1189+
1190+ // Check if the suggested path looks like it should exist based on the pattern
1191+ const pathSegments = suggestion . path . split ( '/' ) . filter ( Boolean ) ;
1192+ if ( pathSegments . length < 3 ) return false ; // Need at least /learn/product/page
1193+
1194+ return true ;
1195+ }
1196+
1197+ generateTitleFromPath ( path ) {
1198+ const segments = path . split ( '/' ) . filter ( Boolean ) ;
1199+ const lastSegment = segments [ segments . length - 1 ] ;
1200+ return lastSegment
1201+ . split ( '-' )
1202+ . map ( word => word . charAt ( 0 ) . toUpperCase ( ) + word . slice ( 1 ) )
1203+ . join ( ' ' ) ;
1204+ }
1205+
11601206 async generateChangelogEntry ( context ) {
11611207 const prompt = `Generate a changelog entry for the following documentation update:
11621208
@@ -1224,15 +1270,61 @@ Changelog entry:`;
12241270 learnUrl = learnUrl . replace ( / \/ + / g, '/' ) ;
12251271 // Remove trailing .mdx if present
12261272 learnUrl = learnUrl . replace ( / \. m d x $ / , '' ) ;
1227- // Look up the mapping
1273+
1274+ // Look up the mapping first
12281275 const mappedPath = this . learnToFile [ learnUrl ] ;
12291276 if ( mappedPath ) {
1230- console . log ( `[DEBUG] Using mapping: ${ learnUrl } → ${ mappedPath } ` ) ;
1277+ console . log ( `[DEBUG] Using existing mapping: ${ learnUrl } → ${ mappedPath } ` ) ;
12311278 return mappedPath ;
1232- } else {
1233- console . warn ( `[DEBUG] No mapping found for ${ learnUrl } , skipping file creation.` ) ;
1279+ }
1280+
1281+ // Fallback: generate path based on product structure patterns
1282+ const fallbackPath = this . generateFallbackPath ( slug , relPath ) ;
1283+ if ( fallbackPath ) {
1284+ console . log ( `[DEBUG] Using fallback mapping: ${ learnUrl } → ${ fallbackPath } ` ) ;
1285+ return fallbackPath ;
1286+ }
1287+
1288+ console . warn ( `[DEBUG] No mapping found for ${ learnUrl } , skipping file creation.` ) ;
1289+ return null ;
1290+ }
1291+
1292+ // Generate fallback path for new files based on existing patterns
1293+ generateFallbackPath ( slug , relPath ) {
1294+ // Map learn slugs to product directories (from my-mappings.md patterns)
1295+ const productMap = {
1296+ 'sdks' : 'sdks' ,
1297+ 'docs' : 'docs' ,
1298+ 'openapi-definition' : 'openapi-def' ,
1299+ 'fern-definition' : 'fern-def' ,
1300+ 'cli-api' : 'cli-api-reference' ,
1301+ 'asyncapi-definition' : 'asyncapi-def' ,
1302+ 'openrpc-definition' : 'openrpc-def' ,
1303+ 'grpc-definition' : 'grpc-def' ,
1304+ 'ask-fern' : 'ask-fern' ,
1305+ 'home' : 'home'
1306+ } ;
1307+
1308+ const productDir = productMap [ slug ] ;
1309+ if ( ! productDir ) {
12341310 return null ;
12351311 }
1312+
1313+ // Clean up the relative path
1314+ let cleanRelPath = relPath . replace ( / \. m d x $ / , '' ) ;
1315+ if ( ! cleanRelPath . endsWith ( '.mdx' ) ) {
1316+ cleanRelPath += '.mdx' ;
1317+ }
1318+
1319+ // For SDKs, check if it's a generator-specific path
1320+ if ( slug === 'sdks' && cleanRelPath . includes ( 'generators/' ) ) {
1321+ // Handle generator-specific paths: generators/typescript/... -> overview/typescript/...
1322+ const generatorPath = cleanRelPath . replace ( 'generators/' , 'overview/' ) ;
1323+ return `fern/products/${ productDir } /${ generatorPath } ` ;
1324+ }
1325+
1326+ // Default pattern: fern/products/{product}/pages/{path}
1327+ return `fern/products/${ productDir } /pages/${ cleanRelPath } ` ;
12361328 }
12371329
12381330 // Find the appropriate product YAML file based on the file path
@@ -1352,8 +1444,7 @@ Changelog entry:`;
13521444
13531445 // Add the new page
13541446 const newPageEntry = {
1355- page : pageInfo . slug ,
1356- title : pageInfo . title ,
1447+ page : pageInfo . title ,
13571448 path : pageInfo . path
13581449 } ;
13591450
@@ -1611,14 +1702,18 @@ ${truncatedContent || 'No suggested content available'}
16111702 const url = result . url || `https://${ result . domain || '' } ${ result . pathname || '' } ` ;
16121703 const relevance = result . $dist !== undefined ? ( 1 - result . $dist ) . toFixed ( 3 ) : 'N/A' ;
16131704 const aiSuggested = result . aiSuggested ? ' 🤖 AI-suggested' : '' ;
1705+ const isNewFile = result . isNewFile ? ' 📄 NEW FILE' : '' ;
16141706
1615- console . log ( `${ index + 1 } . ${ path } ${ aiSuggested } ` ) ;
1707+ console . log ( `${ index + 1 } . ${ path } ${ aiSuggested } ${ isNewFile } ` ) ;
16161708 console . log ( ` Title: ${ title } ` ) ;
16171709 console . log ( ` URL: ${ url } ` ) ;
16181710 console . log ( ` Relevance Score: ${ relevance } ` ) ;
16191711 if ( result . reason ) {
16201712 console . log ( ` AI Reason: ${ result . reason } ` ) ;
16211713 }
1714+ if ( result . isNewFile ) {
1715+ console . log ( ` 📝 Will create new documentation file` ) ;
1716+ }
16221717 } ) ;
16231718 console . log ( '' ) ;
16241719
@@ -1637,8 +1732,30 @@ ${truncatedContent || 'No suggested content available'}
16371732 }
16381733
16391734 if ( searchResults . length === 0 ) {
1640- console . log ( '❌ No relevant files found' ) ;
1641- return ;
1735+ console . log ( '❌ No relevant files found from search' ) ;
1736+
1737+ // Try to suggest a new file based on the documentation analysis
1738+ if ( documentationAnalysis . suggestedPages && documentationAnalysis . suggestedPages . length > 0 ) {
1739+ console . log ( '💡 Suggesting new file creation based on AI analysis...' ) ;
1740+ const highPrioritySuggestion = documentationAnalysis . suggestedPages . find ( p => p . priority === 'high' ) ||
1741+ documentationAnalysis . suggestedPages [ 0 ] ;
1742+
1743+ searchResults . push ( {
1744+ pathname : highPrioritySuggestion . path ,
1745+ url : highPrioritySuggestion . path ,
1746+ title : this . generateTitleFromPath ( highPrioritySuggestion . path ) ,
1747+ isNewFile : true ,
1748+ aiSuggested : true ,
1749+ priority : highPrioritySuggestion . priority ,
1750+ reason : highPrioritySuggestion . reason ,
1751+ document : '' , // Empty content for new file
1752+ } ) ;
1753+
1754+ console . log ( ` 📄 Suggested new file: ${ highPrioritySuggestion . path } ` ) ;
1755+ } else {
1756+ console . log ( '❌ No relevant files found and no suggestions for new files' ) ;
1757+ return ;
1758+ }
16421759 }
16431760
16441761 console . log ( `📁 Processing ${ searchResults . length } relevant files for documentation updates...` ) ;
@@ -1673,12 +1790,19 @@ ${truncatedContent || 'No suggested content available'}
16731790 console . log ( ` URL: ${ result . url } ` ) ;
16741791
16751792 try {
1676- const currentContent = await this . getCurrentFileContent ( filePath ) ;
1793+ let currentContent ;
1794+ if ( result . isNewFile ) {
1795+ console . log ( ` 💡 New file suggested - generating from scratch` ) ;
1796+ currentContent = '' ;
1797+ } else {
1798+ currentContent = await this . getCurrentFileContent ( filePath ) ;
1799+ }
16771800
16781801 const contextWithDocument = {
16791802 ...context ,
16801803 currentDocument : result . document || '' ,
1681- slackThreadContent
1804+ slackThreadContent,
1805+ isNewFile : result . isNewFile || false
16821806 } ;
16831807
16841808 console . log ( ` 🤖 Generating AI suggestions based on context...` ) ;
@@ -1725,17 +1849,23 @@ ${truncatedContent || 'No suggested content available'}
17251849 continue ; // Skip this file
17261850 }
17271851
1728- if ( suggestedContent && suggestedContent !== currentContent ) {
1852+ if ( suggestedContent && ( suggestedContent !== currentContent || result . isNewFile ) ) {
17291853 analysisResults . push ( {
17301854 filePath,
17311855 currentContent,
17321856 suggestedContent,
17331857 title : result . title ,
1734- url : result . url
1858+ url : result . url ,
1859+ isNewFile : result . isNewFile || false
17351860 } ) ;
17361861
1737- console . log ( ` ✅ Changes suggested for: ${ filePath } ` ) ;
1738- console . log ( ` 📊 Original: ${ currentContent . length } chars → Suggested: ${ suggestedContent . length } chars` ) ;
1862+ if ( result . isNewFile ) {
1863+ console . log ( ` ✅ New file content generated: ${ filePath } ` ) ;
1864+ console . log ( ` 📊 Generated: ${ suggestedContent . length } chars` ) ;
1865+ } else {
1866+ console . log ( ` ✅ Changes suggested for: ${ filePath } ` ) ;
1867+ console . log ( ` 📊 Original: ${ currentContent . length } chars → Suggested: ${ suggestedContent . length } chars` ) ;
1868+ }
17391869 } else {
17401870 console . log ( ` ℹ️ No changes suggested for this file` ) ;
17411871 }
@@ -1826,7 +1956,7 @@ ${truncatedContent || 'No suggested content available'}
18261956 for ( const result of analysisResults ) {
18271957 try {
18281958 let actualPath ;
1829- const isNewFile = result . currentContent . length === 0 ;
1959+ const isNewFile = result . isNewFile || result . currentContent . length === 0 ;
18301960 if ( isNewFile ) {
18311961 // Use mapping to get correct product directory for new files
18321962 let slug = null ;
@@ -1887,30 +2017,42 @@ ${truncatedContent || 'No suggested content available'}
18872017 }
18882018 }
18892019
1890- // Update changelog if requested
2020+ // Create new changelog entry if requested
18912021 if ( context . changelogRequired && changelogEntry ) {
18922022 try {
1893- // Find the main changelog file
1894- const changelogPath = 'CHANGELOG.md' ; // or detect dynamically
2023+ // Create a new changelog entry file instead of updating existing changelog
2024+ const timestamp = new Date ( ) . toISOString ( ) . split ( 'T' ) [ 0 ] ; // YYYY-MM-DD format
2025+ const changelogPath = `changelog-entries/${ timestamp } -issue-${ this . issueNumber } .md` ;
18952026
1896- try {
1897- const currentChangelog = await this . fetchFileContent ( changelogPath ) ;
1898- const updatedChangelog = this . addChangelogEntry ( currentChangelog , changelogEntry ) ;
1899-
1900- console . log ( ` 📋 Updating changelog: ${ changelogPath } ` ) ;
1901- await this . updateFile (
1902- changelogPath ,
1903- updatedChangelog ,
1904- branchName ,
1905- `Add changelog entry for issue #${ this . issueNumber } `
1906- ) ;
1907-
1908- filesUpdated . push ( changelogPath ) ;
1909- } catch ( error ) {
1910- console . error ( ` ⚠️ Could not update changelog: ${ error . message } ` ) ;
1911- }
2027+ const changelogContent = `# Changelog Entry - Issue #${ this . issueNumber }
2028+
2029+ **Date**: ${ timestamp }
2030+ **Priority**: ${ context . priority }
2031+ **Issue**: ${ context . requestDescription }
2032+
2033+ ## Entry
2034+
2035+ ${ changelogEntry }
2036+
2037+ ## Files Updated
2038+
2039+ ${ filesUpdated . map ( file => `- \`${ file } \`` ) . join ( '\n' ) }
2040+
2041+ ---
2042+ *Generated by Fern Scribe for issue #${ this . issueNumber } *
2043+ ` ;
2044+
2045+ console . log ( ` 📋 Creating changelog entry: ${ changelogPath } ` ) ;
2046+ await this . updateFile (
2047+ changelogPath ,
2048+ changelogContent ,
2049+ branchName ,
2050+ `Add changelog entry for issue #${ this . issueNumber } `
2051+ ) ;
2052+
2053+ filesUpdated . push ( changelogPath ) ;
19122054 } catch ( error ) {
1913- console . error ( ` ⚠️ Error processing changelog: ${ error . message } ` ) ;
2055+ console . error ( ` ⚠️ Error creating changelog entry : ${ error . message } ` ) ;
19142056 }
19152057 }
19162058
0 commit comments