GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Phase 4: Agent Spec CI/CD Integration & Governance (#2526) · lightspeedwp/.github@70108c8

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
  • .github
    • scripts
      • generate-agent-index.js
    • workflows
      • agent-spec-validation.yml
  • CHANGELOG.md
  • docs
    • AGENT-DEVELOPER-GUIDE.md
    • AGENT-INDEX.md
  • hooks
    • pre-commit-agent-spec-validation.sh
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎.github/scripts/generate-agent-index.js‎

Copy file name to clipboard
+342Lines changed: 342 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,342 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* Agent Index Generator
5+
* Generates searchable agent index from all .agent.md specification files
6+
* Outputs to docs/AGENT-INDEX.md
7+
*/
8+
9+
import fs from "fs";
10+
import path from "path";
11+
import * as YAML from "js-yaml";
12+
import { fileURLToPath } from "url";
13+
14+
const __filename = fileURLToPath(import.meta.url);
15+
const __dirname = path.dirname(__filename);
16+
17+
const REPO_ROOT = path.join(__dirname, "../../");
18+
const OUTPUT_FILE = path.join(__dirname, "../../docs/AGENT-INDEX.md");
19+
const AGENT_LOCATIONS = [
20+
path.join(REPO_ROOT, "agents"),
21+
path.join(REPO_ROOT, ".github/agents"),
22+
];
23+
24+
// Category metadata
25+
const CATEGORY_INFO = {
26+
configuration: { icon: "⚙️", description: "Configuration and setup agents" },
27+
analysis: { icon: "🔍", description: "Analysis and research agents" },
28+
integration: { icon: "🔗", description: "Integration and API agents" },
29+
planning: { icon: "📋", description: "Planning and roadmap agents" },
30+
governance: { icon: "📋", description: "Governance and process agents" },
31+
automation: { icon: "⚡", description: "Automation and workflow agents" },
32+
tooling: { icon: "🛠️", description: "Development tools and utilities" },
33+
mode: { icon: "🎯", description: "Operational modes and frameworks" },
34+
};
35+
36+
// Parse YAML frontmatter
37+
function parseFrontmatter(content) {
38+
const match = content.match(/^---\n([\s\S]*?)\n---/);
39+
if (!match) return null;
40+
41+
try {
42+
return YAML.load(match[1]);
43+
} catch (error) {
44+
console.error(`Error parsing frontmatter: ${error.message}`);
45+
return null;
46+
}
47+
}
48+
49+
// Parse agent spec file
50+
function parseAgentSpec(filePath, repoRoot) {
51+
try {
52+
const content = fs.readFileSync(filePath, "utf8");
53+
const frontmatter = parseFrontmatter(content);
54+
55+
if (!frontmatter) {
56+
console.warn(`Invalid frontmatter in ${filePath}`);
57+
return null;
58+
}
59+
60+
// Extract first paragraph of content after frontmatter
61+
const contentMatch = content.match(
62+
/^---\n[\s\S]*?\n---\n+([\s\S]*?)(?:\n##|$)/,
63+
);
64+
const summary = contentMatch ? contentMatch[1].trim().split("\n")[0] : "";
65+
66+
// Calculate relative path from repo root for correct linking
67+
const relPath = path.relative(repoRoot, filePath);
68+
69+
return {
70+
name: frontmatter.name || path.basename(filePath, ".agent.md"),
71+
description: frontmatter.description || summary || "",
72+
file: path.basename(filePath),
73+
path: relPath,
74+
category: frontmatter.category || "unknown",
75+
status: frontmatter.status || "active",
76+
version: frontmatter.version || "1.0.0",
77+
implementation: frontmatter.implementation || null,
78+
tags: frontmatter.tags || [],
79+
created_date: frontmatter.created_date || "",
80+
last_updated: frontmatter.last_updated || "",
81+
author: frontmatter.author || "Unknown",
82+
language: frontmatter.language || "en",
83+
};
84+
} catch (error) {
85+
console.error(`Error parsing ${filePath}: ${error.message}`);
86+
return null;
87+
}
88+
}
89+
90+
// Collect all agent specs
91+
function collectAgentSpecs() {
92+
const specs = [];
93+
94+
// Find all .agent.md files
95+
function walkDir(dir) {
96+
if (!fs.existsSync(dir)) {
97+
return;
98+
}
99+
100+
try {
101+
const files = fs.readdirSync(dir);
102+
103+
for (const file of files) {
104+
const filePath = path.join(dir, file);
105+
const stat = fs.statSync(filePath);
106+
107+
if (stat.isDirectory()) {
108+
walkDir(filePath);
109+
} else if (file.endsWith(".agent.md")) {
110+
const spec = parseAgentSpec(filePath, REPO_ROOT);
111+
if (spec) {
112+
specs.push(spec);
113+
}
114+
}
115+
}
116+
} catch (error) {
117+
console.warn(`Error reading directory ${dir}: ${error.message}`);
118+
}
119+
}
120+
121+
// Search all agent locations
122+
for (const location of AGENT_LOCATIONS) {
123+
walkDir(location);
124+
}
125+
126+
// Also search plugins directories (handle glob-like pattern)
127+
const pluginsDir = path.join(REPO_ROOT, "plugins");
128+
if (fs.existsSync(pluginsDir)) {
129+
const pluginDirs = fs.readdirSync(pluginsDir);
130+
for (const pluginDir of pluginDirs) {
131+
const agentsPath = path.join(pluginsDir, pluginDir, "agents");
132+
walkDir(agentsPath);
133+
}
134+
}
135+
136+
return specs.sort((a, b) => a.name.localeCompare(b.name));
137+
}
138+
139+
// Generate markdown index
140+
function generateIndex(specs) {
141+
const today = new Date().toISOString().split("T")[0];
142+
let markdown = `---
143+
file_type: documentation
144+
title: Agent Index
145+
description: Searchable index of all agent specifications
146+
created_date: ${today}
147+
last_updated: ${today}
148+
author: Agent Index Generator
149+
language: en
150+
status: active
151+
---
152+
153+
# Agent Index
154+
155+
Complete searchable index of all ${specs.length} agent specifications in the LightSpeed \`.github\` control plane.
156+
157+
**Generated**: ${today}
158+
159+
## Quick Stats
160+
161+
| Metric | Count |
162+
| --- | --- |
163+
| Total Agents | ${specs.length} |
164+
| Active Agents | ${specs.filter((s) => s.status === "active").length} |
165+
| Draft Agents | ${specs.filter((s) => s.status === "draft").length} |
166+
| Deprecated Agents | ${specs.filter((s) => s.status === "deprecated").length} |
167+
168+
## Agents by Category
169+
170+
`;
171+
172+
// Group by category
173+
const byCategory = {};
174+
for (const spec of specs) {
175+
const cat = spec.category || "unknown";
176+
if (!byCategory[cat]) {
177+
byCategory[cat] = [];
178+
}
179+
byCategory[cat].push(spec);
180+
}
181+
182+
// Generate category sections
183+
for (const [category, agents] of Object.entries(byCategory)) {
184+
const catInfo = CATEGORY_INFO[category] || {
185+
icon: "📦",
186+
description: category,
187+
};
188+
markdown += `\n### ${catInfo.icon} ${category.charAt(0).toUpperCase() + category.slice(1)}\n\n`;
189+
markdown += `${catInfo.description}\n\n`;
190+
191+
markdown += `| Agent | Status | Version | Tags |\n`;
192+
markdown += `| --- | --- | --- | --- |\n`;
193+
194+
for (const agent of agents) {
195+
const statusBadge =
196+
agent.status === "active"
197+
? "✅ Active"
198+
: agent.status === "draft"
199+
? "📝 Draft"
200+
: "🚫 Deprecated";
201+
const tags =
202+
agent.tags.length > 0
203+
? agent.tags.map((t) => `\`${t}\``).join(", ")
204+
: "—";
205+
const specLink = `[${agent.name}](../${agent.path})`;
206+
207+
markdown += `| ${specLink} | ${statusBadge} | ${agent.version} | ${tags} |\n`;
208+
}
209+
210+
markdown += "\n";
211+
}
212+
213+
// Add all agents section
214+
markdown += `\n## All Agents (Alphabetical)\n\n`;
215+
216+
markdown += `| Agent | Category | Status | Version | Updated |\n`;
217+
markdown += `| --- | --- | --- | --- | --- |\n`;
218+
219+
for (const spec of specs) {
220+
const statusBadge =
221+
spec.status === "active" ? "✅" : spec.status === "draft" ? "📝" : "🚫";
222+
const specLink = `[${spec.name}](../${spec.path})`;
223+
const catInfo = CATEGORY_INFO[spec.category] || { icon: "📦" };
224+
225+
markdown += `| ${specLink} | ${catInfo.icon} ${spec.category} | ${statusBadge} ${spec.status} | ${spec.version} | ${spec.last_updated} |\n`;
226+
}
227+
228+
// Add discovery section
229+
markdown += `\n## Discovery\n\n`;
230+
markdown += `### By Implementation Status\n\n`;
231+
markdown += `**With Implementation Directory** (${specs.filter((s) => s.implementation).length})\n\n`;
232+
233+
for (const spec of specs.filter((s) => s.implementation)) {
234+
// Construct implementation directory path relative to spec file location
235+
const specDir = path.dirname(spec.path);
236+
const implPath = path.join(specDir, spec.implementation);
237+
markdown += `- [${spec.name}](../${spec.path}) → [\`${spec.implementation}/\`](../${implPath}/)\n`;
238+
}
239+
240+
markdown += `\n**Specification-Only** (${specs.filter((s) => !s.implementation).length})\n\n`;
241+
242+
for (const spec of specs.filter((s) => !s.implementation)) {
243+
markdown += `- [${spec.name}](../${spec.path}) — ${spec.description}\n`;
244+
}
245+
246+
// Add tags section
247+
const allTags = new Set();
248+
for (const spec of specs) {
249+
for (const tag of spec.tags) {
250+
allTags.add(tag);
251+
}
252+
}
253+
254+
if (allTags.size > 0) {
255+
markdown += `\n## Agents by Tag\n\n`;
256+
257+
for (const tag of Array.from(allTags).sort()) {
258+
const tagged = specs.filter((s) => s.tags.includes(tag));
259+
markdown += `### \`${tag}\`\n\n`;
260+
261+
for (const spec of tagged) {
262+
markdown += `- [${spec.name}](../${spec.path})\n`;
263+
}
264+
265+
markdown += "\n";
266+
}
267+
}
268+
269+
// Add author section
270+
const byAuthor = {};
271+
for (const spec of specs) {
272+
const author = spec.author || "Unknown";
273+
if (!byAuthor[author]) {
274+
byAuthor[author] = [];
275+
}
276+
byAuthor[author].push(spec);
277+
}
278+
279+
markdown += `\n## Agents by Author\n\n`;
280+
for (const [author, agents] of Object.entries(byAuthor).sort()) {
281+
markdown += `### ${author} (${agents.length})\n\n`;
282+
for (const agent of agents) {
283+
markdown += `- [${agent.name}](../${agent.path}) — ${agent.description}\n`;
284+
}
285+
markdown += "\n";
286+
}
287+
288+
// Add footer
289+
markdown += `\n---\n\n`;
290+
markdown += `## Related Documentation\n\n`;
291+
markdown += `- [Agent Developer Guide](./AGENT-DEVELOPER-GUIDE.md)\n`;
292+
markdown += `- [Agent Specification Audit - Phase 3 Results](../.github/reports/audit/AGENT-SPECS-PHASE3-RESULTS.md)\n`;
293+
markdown += `- [CONTRIBUTING.md](../CONTRIBUTING.md)\n\n`;
294+
markdown += `**Generated**: ${new Date().toISOString()}\n`;
295+
markdown += `**Total Agents**: ${specs.length}\n`;
296+
297+
return markdown;
298+
}
299+
300+
// Main execution
301+
function main() {
302+
console.log("Generating agent index...");
303+
304+
const specs = collectAgentSpecs();
305+
console.log(`Found ${specs.length} agent specifications`);
306+
307+
const markdown = generateIndex(specs);
308+
309+
// Ensure directory exists
310+
const outputDir = path.dirname(OUTPUT_FILE);
311+
if (!fs.existsSync(outputDir)) {
312+
fs.mkdirSync(outputDir, { recursive: true });
313+
}
314+
315+
// Write output
316+
fs.writeFileSync(OUTPUT_FILE, markdown, "utf8");
317+
console.log(`✅ Agent index generated: ${OUTPUT_FILE}`);
318+
319+
// Print summary
320+
console.log("\n📊 Agent Index Summary:");
321+
console.log(` Total agents: ${specs.length}`);
322+
console.log(
323+
` Active: ${specs.filter((s) => s.status === "active").length}`,
324+
);
325+
console.log(` Draft: ${specs.filter((s) => s.status === "draft").length}`);
326+
console.log(
327+
` Deprecated: ${specs.filter((s) => s.status === "deprecated").length}`,
328+
);
329+
330+
const categories = {};
331+
for (const spec of specs) {
332+
categories[spec.category] = (categories[spec.category] || 0) + 1;
333+
}
334+
console.log("\n📂 By Category:");
335+
for (const [cat, count] of Object.entries(categories).sort()) {
336+
console.log(` ${cat}: ${count}`);
337+
}
338+
339+
return 0;
340+
}
341+
342+
process.exit(main());

0 commit comments

Comments
0 (0)
You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.