Added goose doc map md file for goose agent to find relevant doc easily. (#6598)
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { globby } = require('globby');
|
||||
const matter = require('gray-matter');
|
||||
|
||||
const DOCS_DIR = path.join(__dirname, '..', 'docs');
|
||||
const OUTPUT_FILE = path.join(__dirname, '..', 'static', 'goose-docs-map.md');
|
||||
|
||||
function getTitle(frontmatter, content) {
|
||||
if (frontmatter.title) {
|
||||
return frontmatter.title;
|
||||
}
|
||||
|
||||
const lines = content.split('\n');
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('# ')) {
|
||||
return line.slice(2).trim();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Extract H2-H6 headings as nested bullet list
|
||||
const MIN_HEADING_LEVEL = 2;
|
||||
const MAX_HEADING_LEVEL = 6;
|
||||
|
||||
function getHeadings(content) {
|
||||
const bullets = [];
|
||||
const headingPattern = new RegExp(`^(#{${MIN_HEADING_LEVEL},${MAX_HEADING_LEVEL}}) (.+)$`);
|
||||
|
||||
for (const line of content.split('\n')) {
|
||||
const match = line.match(headingPattern);
|
||||
if (!match) continue;
|
||||
|
||||
const level = match[1].length;
|
||||
const indent = ' '.repeat(level - MIN_HEADING_LEVEL);
|
||||
bullets.push(`${indent}* ${match[2]}`);
|
||||
}
|
||||
|
||||
return bullets.join('\n');
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const sections = [
|
||||
{ name: 'Getting Started', pattern: 'getting-started/*.{md,mdx}' },
|
||||
{ name: 'Guides', pattern: 'guides/**/*.{md,mdx}' },
|
||||
];
|
||||
|
||||
let output = `# goose Documentation Map
|
||||
|
||||
> Auto-generated. Last updated: ${new Date().toISOString().split('T')[0]}
|
||||
|
||||
`;
|
||||
|
||||
for (const section of sections) {
|
||||
const files = await globby(section.pattern, { cwd: DOCS_DIR });
|
||||
output += `## ${section.name}\n\n`;
|
||||
|
||||
for (const file of files.sort()) {
|
||||
try {
|
||||
const raw = fs.readFileSync(path.join(DOCS_DIR, file), 'utf-8');
|
||||
const { data, content } = matter(raw);
|
||||
const title = getTitle(data, content);
|
||||
if (!title) {
|
||||
console.warn(`[generate-docs-map] Warning: No title found for ${file}, skipping`);
|
||||
continue;
|
||||
}
|
||||
const headings = getHeadings(content);
|
||||
const urlPath = `docs/${file.replace('.mdx', '.md')}`;
|
||||
|
||||
output += `### [${title}](${urlPath})\n\n`;
|
||||
if (headings) output += `${headings}\n\n`;
|
||||
|
||||
} catch (err) {
|
||||
console.warn(`[generate-docs-map] Warning: Could not process ${file}, skipping`, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output += `---\n\n> Full docs: https://block.github.io/goose/\n`;
|
||||
|
||||
fs.writeFileSync(OUTPUT_FILE, output);
|
||||
console.log(`[generate-docs-map] Generated: ${OUTPUT_FILE}`);
|
||||
}
|
||||
|
||||
// Run main if executed directly
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
|
||||
module.exports = { getTitle, getHeadings };
|
||||
@@ -0,0 +1,59 @@
|
||||
const { test, describe } = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
const { getTitle, getHeadings } = require('./generate-docs-map');
|
||||
|
||||
describe('getTitle', () => {
|
||||
test('returns frontmatter title when present', () => {
|
||||
const result = getTitle({ title: 'My Custom Title' }, 'some content');
|
||||
assert.strictEqual(result, 'My Custom Title');
|
||||
});
|
||||
|
||||
test('extracts H1 heading when no frontmatter title', () => {
|
||||
const content = '# Hello World\n\nSome paragraph text';
|
||||
const result = getTitle({}, content);
|
||||
assert.strictEqual(result, 'Hello World');
|
||||
});
|
||||
|
||||
test('returns null when no title found', () => {
|
||||
const result = getTitle({}, 'no heading here');
|
||||
assert.strictEqual(result, null);
|
||||
});
|
||||
|
||||
test('prefers frontmatter over H1', () => {
|
||||
const content = '# H1 Title\n\nContent';
|
||||
const result = getTitle({ title: 'Frontmatter Title' }, content);
|
||||
assert.strictEqual(result, 'Frontmatter Title');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getHeadings', () => {
|
||||
test('extracts H2 headings', () => {
|
||||
const content = '# Title\n\n## Section One\n\nText\n\n## Section Two';
|
||||
const result = getHeadings(content);
|
||||
assert.strictEqual(result, '* Section One\n* Section Two');
|
||||
});
|
||||
|
||||
test('creates nested bullets for H3-H6', () => {
|
||||
const content = '## Level 2\n### Level 3\n#### Level 4';
|
||||
const result = getHeadings(content);
|
||||
assert.strictEqual(result, '* Level 2\n * Level 3\n * Level 4');
|
||||
});
|
||||
|
||||
test('ignores H1 headings', () => {
|
||||
const content = '# Title\n## Real Section';
|
||||
const result = getHeadings(content);
|
||||
assert.strictEqual(result, '* Real Section');
|
||||
});
|
||||
|
||||
test('returns empty string when no headings', () => {
|
||||
const content = 'Just some text without any headings';
|
||||
const result = getHeadings(content);
|
||||
assert.strictEqual(result, '');
|
||||
});
|
||||
|
||||
test('handles H6 with correct indentation', () => {
|
||||
const content = '## H2\n###### H6';
|
||||
const result = getHeadings(content);
|
||||
assert.strictEqual(result, '* H2\n * H6');
|
||||
});
|
||||
});
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
# Verify that the docs map was generated in the build output
|
||||
|
||||
BUILD_DIR="${1:-build}"
|
||||
DOCS_MAP_FILE="goose-docs-map.md"
|
||||
|
||||
if [ ! -f "$BUILD_DIR/$DOCS_MAP_FILE" ]; then
|
||||
echo "Error: $DOCS_MAP_FILE not found in $BUILD_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ $DOCS_MAP_FILE found in $BUILD_DIR"
|
||||
Reference in New Issue
Block a user