singularity-forge/scripts/generate-features-inventory.mjs
Mikael Hugo 02a4339a51 refactor: rename pi-* packages to forge-native names (Phase 1)
Rename all four packages/pi-* directories to forge-native names,
stripping the 'pi' identity and establishing forge's own:

- packages/pi-coding-agent → packages/coding-agent
- packages/pi-ai → packages/ai
- packages/pi-agent-core → packages/agent-core
- packages/pi-tui → packages/tui

Package names updated:
- @singularity-forge/pi-coding-agent → @singularity-forge/coding-agent
- @singularity-forge/pi-ai → @singularity-forge/ai
- @singularity-forge/pi-agent-core → @singularity-forge/agent-core
- @singularity-forge/pi-tui → @singularity-forge/tui

All import references, bare string references, path references,
internal variable names (_bundledPi*), and dist files updated.
@mariozechner/pi-* third-party compat aliases preserved.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-10 11:28:01 +02:00

176 lines
4.9 KiB
JavaScript

import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
import { join, relative, resolve } from "node:path";
const __filename = import.meta.filename;
const __dirname = import.meta.dirname;
const repoRoot = resolve(__dirname, "..");
const featuresPath = join(repoRoot, "FEATURES.md");
const providersPath = join(repoRoot, "packages", "ai", "src", "types.ts");
const extensionsRoot = join(repoRoot, "src", "resources", "extensions");
const sfManifestPath = join(extensionsRoot, "sf", "extension-manifest.json");
const searchProviderPath = resolveExistingPath(
join(
repoRoot,
"src",
"resources",
"extensions",
"search-the-web",
"provider.ts",
),
join(
repoRoot,
"src",
"resources",
"extensions",
"search-the-web",
"provider.js",
),
);
export const START = "<!-- GENERATED_FEATURE_INVENTORY_START -->";
export const END = "<!-- GENERATED_FEATURE_INVENTORY_END -->";
function uniqueSorted(values) {
return [...new Set(values)].sort((a, b) => a.localeCompare(b));
}
function resolveExistingPath(...paths) {
const found = paths.find((path) => existsSync(path));
if (!found) {
throw new Error(
`None of these inventory source paths exist: ${paths.join(", ")}`,
);
}
return found;
}
export function parseKnownProviders() {
const src = readFileSync(providersPath, "utf8");
const match = src.match(/export type KnownProvider =([\s\S]*?);/);
if (!match)
throw new Error(
"Could not find KnownProvider in packages/ai/src/types.ts",
);
const providers = [...match[1].matchAll(/"([^"]+)"/g)].map((m) => m[1]);
return uniqueSorted(providers);
}
export function parseBundledExtensions() {
const entries = readdirSync(extensionsRoot, { withFileTypes: true })
.filter((entry) => entry.isDirectory())
.map((entry) => entry.name)
.filter((name) => {
try {
const manifestPath = join(
extensionsRoot,
name,
"extension-manifest.json",
);
readFileSync(manifestPath, "utf8");
return true;
} catch {
return false;
}
});
return uniqueSorted(entries);
}
export function parseSfNativeTools() {
const manifest = JSON.parse(readFileSync(sfManifestPath, "utf8"));
const tools = manifest?.provides?.tools;
if (!Array.isArray(tools)) {
throw new Error(
"Could not find provides.tools in src/resources/extensions/sf/extension-manifest.json",
);
}
return uniqueSorted(
tools.filter((tool) => typeof tool === "string" && tool.startsWith("sf_")),
);
}
export function parseSearchProviders() {
const src = readFileSync(searchProviderPath, "utf8");
const preferencesMatch = src.match(
/const VALID_PREFERENCES = new Set\(\[([\s\S]*?)\]\)/,
);
const preferenceProviders = preferencesMatch
? [...preferencesMatch[1].matchAll(/["']([^"']+)["']/g)].map((m) => m[1])
: [];
const providers = [
...preferenceProviders,
...src.matchAll(/providers\.push\('([^']+)'\)/g),
...src.matchAll(/provider\?: '([^']+)'/g),
...src.matchAll(/\|\s*"([^"]+)"/g),
]
.map((m) => (typeof m === "string" ? m : m[1]))
.filter((p) => p !== "combosearch" && p !== "minimax" && p !== "auto");
return uniqueSorted(providers);
}
function formatBullets(values, formatter = (value) => `- \`${value}\``) {
return values.map((value) => formatter(value)).join("\n");
}
export function buildSection() {
const extensions = parseBundledExtensions();
const sfNativeTools = parseSfNativeTools();
const searchProviders = parseSearchProviders();
const knownProviders = parseKnownProviders();
return [
"### SF Native Tools",
"",
"Generated from `src/resources/extensions/sf/extension-manifest.json`.",
"",
formatBullets(sfNativeTools),
"",
"### Bundled Extensions",
"",
"Generated from `src/resources/extensions/*/extension-manifest.json`.",
"",
formatBullets(
extensions,
(value) =>
`- \`${value}\` — [extension-manifest.json](${relative(repoRoot, join(extensionsRoot, value, "extension-manifest.json"))})`,
),
"",
"### Search Providers",
"",
"Generated from the `search-the-web` extension provider declarations.",
"",
formatBullets(searchProviders),
"",
"### Known Model Providers",
"",
"Generated from `packages/ai/src/types.ts` (`KnownProvider`).",
"",
formatBullets(knownProviders),
"",
].join("\n");
}
export function updateFeaturesContent(features) {
const startIndex = features.indexOf(START);
const endIndex = features.indexOf(END);
if (startIndex === -1 || endIndex === -1 || endIndex < startIndex) {
throw new Error("FEATURES.md is missing generated inventory markers");
}
const before = features.slice(0, startIndex + START.length);
const after = features.slice(endIndex);
const section = `\n\n${buildSection()}`;
return `${before}${section}\n${after}`;
}
export function main() {
const features = readFileSync(featuresPath, "utf8");
const updated = updateFeaturesContent(features);
writeFileSync(featuresPath, updated);
process.stdout.write(`Updated ${relative(repoRoot, featuresPath)}\n`);
}
if (process.argv[1] && resolve(process.argv[1]) === __filename) {
main();
}