mirror of
https://github.com/software-mansion/argent.git
synced 2026-09-14 19:27:14 +08:00
511b857cb6
* ci: add description quality workflow for tools and skills Adds a GitHub Actions workflow that gates PRs on tool description quality using SpiderShield (threshold 3.0/10 against current 3.3 baseline) and informally grades skill SKILL.md frontmatter with a self-contained Node.js grader. Includes extract-tools.mjs for converting ToolDefinition objects to MCP tools/list JSON and grade-skills.mjs for scoring skill files. * ci: fix profiler skill description and enforce 10.0 skill quality gate * ci: split description quality into two separate scoped workflows * ci: raise tool description quality gate to 10.0
87 lines
2.4 KiB
JavaScript
87 lines
2.4 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Extracts tool id + description from all ToolDefinition objects in
|
|
* packages/tool-server/src/tools/**\/*.ts and outputs MCP tools/list JSON
|
|
* suitable for `spidershield scan . --tools-json <file>`.
|
|
*
|
|
* Usage:
|
|
* node scripts/extract-tools.mjs > tools.json
|
|
*/
|
|
|
|
import { readFileSync, readdirSync, statSync } from "node:fs";
|
|
import { join, extname } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { dirname } from "node:path";
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dir = dirname(__filename);
|
|
const toolsRoot = join(__dir, "..", "packages", "tool-server", "src", "tools");
|
|
|
|
function walk(dir) {
|
|
const entries = [];
|
|
for (const name of readdirSync(dir)) {
|
|
const full = join(dir, name);
|
|
const stat = statSync(full);
|
|
if (stat.isDirectory()) {
|
|
entries.push(...walk(full));
|
|
} else if (extname(name) === ".ts" && !name.endsWith(".d.ts")) {
|
|
entries.push(full);
|
|
}
|
|
}
|
|
return entries;
|
|
}
|
|
|
|
function extractFromFile(filePath) {
|
|
const src = readFileSync(filePath, "utf8");
|
|
const tools = [];
|
|
|
|
// Match: id: "...", followed anywhere by description: `...` or "..."
|
|
// We iterate over all id: occurrences in the file.
|
|
const idPattern = /\bid:\s*["']([^"']+)["']/g;
|
|
let idMatch;
|
|
while ((idMatch = idPattern.exec(src)) !== null) {
|
|
const id = idMatch[1];
|
|
const afterId = src.slice(idMatch.index);
|
|
|
|
// Look for description within the next 2000 chars (handles multi-line)
|
|
const descWindow = afterId.slice(0, 2000);
|
|
|
|
// Template literal description
|
|
let descMatch = descWindow.match(/\bdescription:\s*`([\s\S]*?)`/);
|
|
if (!descMatch) {
|
|
// Double-quoted description
|
|
descMatch = descWindow.match(/\bdescription:\s*"((?:[^"\\]|\\.)*)"/);
|
|
}
|
|
if (!descMatch) {
|
|
// Single-quoted description
|
|
descMatch = descWindow.match(/\bdescription:\s*'((?:[^'\\]|\\.)*)'/);
|
|
}
|
|
|
|
if (descMatch) {
|
|
tools.push({
|
|
name: id,
|
|
description: descMatch[1].trim(),
|
|
});
|
|
}
|
|
}
|
|
|
|
return tools;
|
|
}
|
|
|
|
const files = walk(toolsRoot);
|
|
const tools = [];
|
|
for (const f of files) {
|
|
tools.push(...extractFromFile(f));
|
|
}
|
|
|
|
// Deduplicate by name (take first occurrence)
|
|
const seen = new Set();
|
|
const unique = tools.filter((t) => {
|
|
if (seen.has(t.name)) return false;
|
|
seen.add(t.name);
|
|
return true;
|
|
});
|
|
|
|
// MCP tools/list format
|
|
console.log(JSON.stringify({ tools: unique }, null, 2));
|