import { execFileSync } from "node:child_process"; import { existsSync, readdirSync, readFileSync } from "node:fs"; import { join, resolve } from "node:path"; const repoRoot = resolve(import.meta.dirname, ".."); const sfRoot = join(repoRoot, "src", "resources", "extensions", "sf"); const extensionsRoot = join(repoRoot, "src", "resources", "extensions"); const manifestPath = join(sfRoot, "extension-manifest.json"); const RESOURCE_SOURCE_RE = /\.(?:js|mjs|cjs|json|md|yaml|yml|d\.ts)$/; const DYNAMIC_TOOL_NAMES = ["bash", "edit", "read", "write"]; const BASE_DIRECT_COMMAND_NAMES = ["kill", "wt"]; const BASE_RUNTIME_COMMAND_NAMES = new Set([ "settings", "model", "scoped-models", "export", "share", "copy", "name", "session", "changelog", "hotkeys", "fork", "tree", "provider", "login", "logout", "new", "compact", "resume", "reload", "thinking", "edit-mode", "terminal", "stop", "exit", "quit", ]); const HIDDEN_OR_ALIAS_SUBCOMMANDS = new Set([ "?", "agent", // internal persistent-agent diagnostics, not part of the product command catalog "auto", "footer-config", // alias for /statusline "h", "stop", // platform-intercepted via BASE_RUNTIME_COMMANDS — never reaches SF handler "undo-turn", // alias for /rewind "wt", ]); function _rel(path) { return path.replace(`${repoRoot}/`, ""); } function read(path) { return readFileSync(path, "utf8"); } function readJsonOrNull(path) { try { return JSON.parse(read(path)); } catch { return null; } } function uniqueSorted(values) { return [...new Set(values)].sort((a, b) => a.localeCompare(b)); } function failSection(title, values) { return [`${title}:`, ...values.map((value) => ` - ${value}`)].join("\n"); } function ignoredResourceSources() { const output = execFileSync( "git", [ "ls-files", "-o", "-i", "--exclude-standard", "src/resources/extensions/**", ], { cwd: repoRoot, encoding: "utf8" }, ); return output .split(/\r?\n/) .filter(Boolean) .filter((path) => RESOURCE_SOURCE_RE.test(path)); } function untrackedResourceSources() { const output = execFileSync( "git", ["ls-files", "-o", "--exclude-standard", "src/resources/extensions/**"], { cwd: repoRoot, encoding: "utf8" }, ); return output .split(/\r?\n/) .filter(Boolean) .filter((path) => RESOURCE_SOURCE_RE.test(path)); } function isLoadableExtensionDir(dirPath) { const packageJsonPath = join(dirPath, "package.json"); if (existsSync(packageJsonPath)) { const pkg = readJsonOrNull(packageJsonPath); if (pkg?.pi && typeof pkg.pi === "object") { return Array.isArray(pkg.pi.extensions) && pkg.pi.extensions.length > 0; } } return ( existsSync(join(dirPath, "index.js")) || existsSync(join(dirPath, "index.ts")) ); } function manifestlessLoadableExtensions() { return readdirSync(extensionsRoot, { withFileTypes: true }) .filter((entry) => entry.isDirectory()) .map((entry) => entry.name) .filter((name) => { const dirPath = join(extensionsRoot, name); return ( isLoadableExtensionDir(dirPath) && !existsSync(join(dirPath, "extension-manifest.json")) ); }) .sort((a, b) => a.localeCompare(b)); } function parseManifest() { const raw = JSON.parse(read(manifestPath)); return { tools: uniqueSorted(raw?.provides?.tools ?? []), commands: uniqueSorted(raw?.provides?.commands ?? []), }; } function parseRegisteredTools() { const files = [ "bootstrap/db-tools.js", "bootstrap/exec-tools.js", "bootstrap/journal-tools.js", "bootstrap/judgment-tools.js", "bootstrap/memory-tools.js", "bootstrap/product-audit-tool.js", "bootstrap/query-tools.js", "subagent/index.js", "tools/sift-search-tool.js", ]; const names = new Set(DYNAMIC_TOOL_NAMES); for (const file of files) { const source = read(join(sfRoot, file)); for (const match of source.matchAll(/\bname:\s*["`]([^"`]+)["`]/g)) { names.add(match[1]); } } return uniqueSorted(names); } function parseTopLevelCatalogCommands() { const source = read(join(sfRoot, "commands", "catalog.js")); const start = source.indexOf("export const TOP_LEVEL_SUBCOMMANDS"); const end = source.indexOf("const NESTED_COMPLETIONS"); if (start === -1 || end === -1 || end <= start) { throw new Error( "Could not locate TOP_LEVEL_SUBCOMMANDS in commands/catalog.js", ); } return uniqueSorted( [...source.slice(start, end).matchAll(/\bcmd:\s*"([^"]+)"/g)].map( (match) => match[1], ), ); } function parsePublicDirectCommands() { const source = read(join(sfRoot, "commands", "catalog.js")); const start = source.indexOf("export const PUBLIC_DIRECT_COMMANDS"); const end = source.indexOf("export const PUBLIC_TOP_LEVEL_SUBCOMMANDS"); if (start === -1 || end === -1 || end <= start) { throw new Error( "Could not locate PUBLIC_DIRECT_COMMANDS in commands/catalog.js", ); } return new Set( [...source.slice(start, end).matchAll(/"([^"]+)"/g)].map( (match) => match[1], ), ); } function parseHandledTopLevelCommands() { const handlerFiles = [ "core.js", "autonomous.js", "parallel.js", "workflow.js", "ops.js", ]; const commands = new Set(); for (const file of handlerFiles) { const source = read(join(sfRoot, "commands", "handlers", file)); for (const match of source.matchAll(/trimmed\s*(?:===|!==)\s*"([^"]+)"/g)) { commands.add(match[1].trim().split(/\s+/)[0]); } for (const match of source.matchAll(/trimmed\.startsWith\(\s*"([^"]+)"/g)) { commands.add(match[1].trim().split(/\s+/)[0]); } } return uniqueSorted(commands); } function parseDirectRegisteredCommands() { const files = [ "commands/legacy/audit.js", "commands/legacy/create-extension.js", "commands/legacy/create-slash-command.js", "guards/inturn.js", "notifications/notify.js", "permissions/index.js", "subagent/index.js", "ui/color-band.js", "ui/emoji.js", "ui/usage-bar.js", ]; const commands = new Set(); for (const file of files) { const source = read(join(sfRoot, file)); for (const match of source.matchAll(/pi\.registerCommand\(\s*"([^"]+)"/g)) { commands.add(match[1]); } } return uniqueSorted(commands); } function main() { const failures = []; const ignoredSources = ignoredResourceSources(); if (ignoredSources.length > 0) { failures.push( failSection( `Runtime extension source files are hidden by .gitignore (${ignoredSources.length})`, ignoredSources .slice(0, 40) .concat( ignoredSources.length > 40 ? [`... ${ignoredSources.length - 40} more`] : [], ), ), ); } const untrackedSources = untrackedResourceSources(); if (untrackedSources.length > 0) { failures.push( failSection( `Runtime extension source files are visible but untracked (${untrackedSources.length})`, untrackedSources .slice(0, 40) .concat( untrackedSources.length > 40 ? [`... ${untrackedSources.length - 40} more`] : [], ), ), ); } const manifestlessExtensions = manifestlessLoadableExtensions(); if (manifestlessExtensions.length > 0) { failures.push( failSection( `Loadable bundled extensions missing extension-manifest.json (${manifestlessExtensions.length})`, manifestlessExtensions, ), ); } const manifest = parseManifest(); const registeredTools = parseRegisteredTools(); const catalogCommands = parseTopLevelCatalogCommands(); const publicDirectCommands = parsePublicDirectCommands(); const directCommandNames = uniqueSorted( BASE_DIRECT_COMMAND_NAMES.concat( catalogCommands.filter( (command) => publicDirectCommands.has(command) && !BASE_RUNTIME_COMMAND_NAMES.has(command), ), parseDirectRegisteredCommands().filter( (command) => !BASE_RUNTIME_COMMAND_NAMES.has(command), ), ), ); const missingManifestTools = registeredTools.filter( (tool) => !manifest.tools.includes(tool), ); const staleManifestTools = manifest.tools.filter( (tool) => !registeredTools.includes(tool), ); if (missingManifestTools.length > 0) { failures.push( failSection( "Registered tools missing from extension-manifest.json", missingManifestTools, ), ); } if (staleManifestTools.length > 0) { failures.push( failSection( "Manifest tools not registered by SF bootstrap", staleManifestTools, ), ); } const missingManifestCommands = directCommandNames.filter( (command) => !manifest.commands.includes(command), ); const staleManifestCommands = manifest.commands.filter( (command) => !directCommandNames.includes(command), ); if (missingManifestCommands.length > 0) { failures.push( failSection( "Direct commands missing from extension-manifest.json", missingManifestCommands, ), ); } if (staleManifestCommands.length > 0) { failures.push( failSection( "Manifest direct commands not registered by SF bootstrap", staleManifestCommands, ), ); } const handledCommands = parseHandledTopLevelCommands().filter( (command) => !HIDDEN_OR_ALIAS_SUBCOMMANDS.has(command), ); const missingCatalogCommands = handledCommands.filter( (command) => !catalogCommands.includes(command), ); const unroutedCatalogCommands = catalogCommands.filter( (command) => command !== "help" && !HIDDEN_OR_ALIAS_SUBCOMMANDS.has(command) && !handledCommands.includes(command), ); if (missingCatalogCommands.length > 0) { failures.push( failSection( "Handled SF commands missing from TOP_LEVEL_SUBCOMMANDS", missingCatalogCommands, ), ); } if (unroutedCatalogCommands.length > 0) { failures.push( failSection( "Catalog SF commands with no routed handler", unroutedCatalogCommands, ), ); } if (failures.length > 0) { console.error(failures.join("\n\n")); process.exit(1); } console.log( `SF extension inventory OK: ${registeredTools.length} tools, ${directCommandNames.length} direct commands, ${catalogCommands.length} catalog commands.`, ); } main();