Skip to content

Commit 3db3b8b

Browse files
kgowruKapil GowruKapil Gowru
authored
Scribe turbopuffer context (#316)
Co-authored-by: Kapil Gowru <[email protected]> Co-authored-by: Kapil Gowru <[email protected]>
1 parent 88edbc0 commit 3db3b8b

File tree

1 file changed

+182
-40
lines changed

1 file changed

+182
-40
lines changed

.github/scripts/fern-scribe.js

Lines changed: 182 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -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
678681
CRITICAL 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
764769
Focus 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(/\.mdx$/, '');
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(/\.mdx$/, '');
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

Comments
 (0)