mirror of
https://github.com/supabase/agent-skills.git
synced 2026-03-27 10:09:26 +08:00
feat: add subdirectory support for reference files (#18)
- Add getMarkdownFilesRecursive() for recursive file scanning - Add parseAllSections() to parse _sections.md from subdirectories - Add parseSectionsFromFile() helper function - Update buildSkill() and validateSkill() to use new functions - Export new functions for use by validate.ts This allows organizing reference files in product-specific subdirectories (e.g., references/db/ for database files) while keeping _sections.md in each subdirectory. Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,10 @@
|
|||||||
import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
import {
|
||||||
|
existsSync,
|
||||||
|
readdirSync,
|
||||||
|
readFileSync,
|
||||||
|
statSync,
|
||||||
|
writeFileSync,
|
||||||
|
} from "node:fs";
|
||||||
import { basename, join } from "node:path";
|
import { basename, join } from "node:path";
|
||||||
import {
|
import {
|
||||||
discoverSkills,
|
discoverSkills,
|
||||||
@@ -222,15 +228,100 @@ export function generateSectionMap(
|
|||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively get all markdown files from a directory
|
||||||
|
*/
|
||||||
|
function getMarkdownFilesRecursive(dir: string): string[] {
|
||||||
|
const files: string[] = [];
|
||||||
|
|
||||||
|
if (!existsSync(dir)) {
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
const entries = readdirSync(dir);
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
// Skip files starting with underscore
|
||||||
|
if (entry.startsWith("_")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fullPath = join(dir, entry);
|
||||||
|
const stat = statSync(fullPath);
|
||||||
|
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
// Recursively scan subdirectories
|
||||||
|
files.push(...getMarkdownFilesRecursive(fullPath));
|
||||||
|
} else if (entry.endsWith(".md")) {
|
||||||
|
files.push(fullPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse all _sections.md files from references directory and subdirectories
|
||||||
|
*/
|
||||||
|
function parseAllSections(referencesDir: string): Section[] {
|
||||||
|
const allSections: Section[] = [];
|
||||||
|
|
||||||
|
// Parse root _sections.md
|
||||||
|
const rootSectionsFile = join(referencesDir, "_sections.md");
|
||||||
|
if (existsSync(rootSectionsFile)) {
|
||||||
|
allSections.push(...parseSectionsFromFile(rootSectionsFile));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan subdirectories for _sections.md files
|
||||||
|
if (existsSync(referencesDir)) {
|
||||||
|
const entries = readdirSync(referencesDir);
|
||||||
|
for (const entry of entries) {
|
||||||
|
const fullPath = join(referencesDir, entry);
|
||||||
|
if (statSync(fullPath).isDirectory()) {
|
||||||
|
const subSectionsFile = join(fullPath, "_sections.md");
|
||||||
|
if (existsSync(subSectionsFile)) {
|
||||||
|
allSections.push(...parseSectionsFromFile(subSectionsFile));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allSections;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse section definitions from a specific _sections.md file
|
||||||
|
*/
|
||||||
|
function parseSectionsFromFile(filePath: string): Section[] {
|
||||||
|
const content = readFileSync(filePath, "utf-8");
|
||||||
|
const sections: Section[] = [];
|
||||||
|
|
||||||
|
const sectionMatches = content.matchAll(
|
||||||
|
/##\s+(\d+)\.\s+([^\n(]+)\s*\((\w+)\)\s*\n\*\*Impact:\*\*\s*(\w+(?:-\w+)?)\s*\n\*\*Description:\*\*\s*([^\n]+)/g,
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const match of sectionMatches) {
|
||||||
|
sections.push({
|
||||||
|
number: parseInt(match[1], 10),
|
||||||
|
title: match[2].trim(),
|
||||||
|
prefix: match[3].trim(),
|
||||||
|
impact: match[4].trim() as Section["impact"],
|
||||||
|
description: match[5].trim(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return sections;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build AGENTS.md for a specific skill
|
* Build AGENTS.md for a specific skill
|
||||||
*/
|
*/
|
||||||
function buildSkill(paths: SkillPaths): void {
|
function buildSkill(paths: SkillPaths): void {
|
||||||
console.log(`[${paths.name}] Building AGENTS.md...`);
|
console.log(`[${paths.name}] Building AGENTS.md...`);
|
||||||
|
|
||||||
// Load metadata and sections
|
// Load metadata and sections (including from subdirectories)
|
||||||
const metadata = loadMetadata(paths.skillFile, paths.name);
|
const metadata = loadMetadata(paths.skillFile, paths.name);
|
||||||
const sections = parseSections(paths.referencesDir);
|
const sections = parseAllSections(paths.referencesDir);
|
||||||
const sectionMap = generateSectionMap(sections);
|
const sectionMap = generateSectionMap(sections);
|
||||||
const skillTitle = skillNameToTitle(paths.name);
|
const skillTitle = skillNameToTitle(paths.name);
|
||||||
|
|
||||||
@@ -244,10 +335,8 @@ function buildSkill(paths: SkillPaths): void {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all reference files
|
// Get all reference files recursively
|
||||||
const referenceFiles = readdirSync(paths.referencesDir)
|
const referenceFiles = getMarkdownFilesRecursive(paths.referencesDir);
|
||||||
.filter((f) => f.endsWith(".md") && !f.startsWith("_"))
|
|
||||||
.map((f) => join(paths.referencesDir, f));
|
|
||||||
|
|
||||||
if (referenceFiles.length === 0) {
|
if (referenceFiles.length === 0) {
|
||||||
console.log(` No reference files found. Generating empty AGENTS.md.`);
|
console.log(` No reference files found. Generating empty AGENTS.md.`);
|
||||||
@@ -436,4 +525,9 @@ if (isMainModule) {
|
|||||||
console.log("✅ Done!");
|
console.log("✅ Done!");
|
||||||
}
|
}
|
||||||
|
|
||||||
export { buildSkill, parseSections };
|
export {
|
||||||
|
buildSkill,
|
||||||
|
getMarkdownFilesRecursive,
|
||||||
|
parseAllSections,
|
||||||
|
parseSections,
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import { existsSync, readdirSync } from "node:fs";
|
import { existsSync } from "node:fs";
|
||||||
import { basename, join } from "node:path";
|
import { basename } from "node:path";
|
||||||
import { generateSectionMap, parseSections } from "./build.js";
|
import {
|
||||||
|
generateSectionMap,
|
||||||
|
getMarkdownFilesRecursive,
|
||||||
|
parseAllSections,
|
||||||
|
parseSections,
|
||||||
|
} from "./build.js";
|
||||||
import {
|
import {
|
||||||
discoverSkills,
|
discoverSkills,
|
||||||
getSkillPaths,
|
getSkillPaths,
|
||||||
@@ -149,14 +154,12 @@ function validateSkill(paths: SkillPaths): boolean {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get section map
|
// Get section map (including from subdirectories)
|
||||||
const sections = parseSections(paths.referencesDir);
|
const sections = parseAllSections(paths.referencesDir);
|
||||||
const sectionMap = generateSectionMap(sections);
|
const sectionMap = generateSectionMap(sections);
|
||||||
|
|
||||||
// Get all markdown files (excluding _ prefixed files)
|
// Get all markdown files recursively (excluding _ prefixed files)
|
||||||
const files = readdirSync(paths.referencesDir)
|
const files = getMarkdownFilesRecursive(paths.referencesDir);
|
||||||
.filter((f) => f.endsWith(".md") && !f.startsWith("_"))
|
|
||||||
.map((f) => join(paths.referencesDir, f));
|
|
||||||
|
|
||||||
if (files.length === 0) {
|
if (files.length === 0) {
|
||||||
console.log(` No rule files found.`);
|
console.log(` No rule files found.`);
|
||||||
|
|||||||
Reference in New Issue
Block a user