Files
mesh-catalog/modules/mesh-catalog/tools/index.ts
T
jschoubben c80d9d0f7c The graph holds what a module declares, not only what it was built on
Build edges are discovered by building; requires and provides are stated by the
module about itself. Both belong in the graph and answer different questions —
and "what provides postgres-database" needed a sweep over every manifest, which
only something holding all of them can do.
2026-09-13 01:44:56 +02:00

79 lines
3.3 KiB
TypeScript

// mesh-catalog's tools — the module graph's query surface (novox/hq ADR 0070).
//
// These are the questions the graph exists to answer, and none of them can be answered anywhere
// else today: what does this mesh know how to run, what is this module made of, what does a change
// to this reach, and what is waiting to be rebuilt.
import { registerModuleTools, type ToolDefinition } from "@novox/mesh-sdk/tools";
import { Graph } from "../store.js";
export function getCatalogueTools(graph: Graph): ToolDefinition[] {
return [
{
name: "catalog_modules",
description:
"Every module this mesh holds, with the commit the catalogue considers current and where it came from.",
input: {},
run: async () => ({ modules: await graph.modules() }),
},
{
name: "catalog_module",
description:
"One module in full — where it came from, what it requires and provides, and what it is made of. The current version unless a commit is named.",
input: {
module: { type: "string", description: "the module's name" },
commit: { type: "string", description: "a particular version (optional)" },
},
run: async (args) => {
const module = String(args.module ?? "");
if (!module) return { error: "name a module" };
const found = await graph.show(module, args.commit ? String(args.commit) : undefined);
return found ? { module: found } : { error: `the catalogue holds no ${module}` };
},
},
{
name: "catalog_provides",
description:
"What provides a provision, and what needs it. Answered from what modules declare about themselves, so it does not require holding every manifest at once.",
input: { provision: { type: "string", description: "the provision, e.g. postgres-database" } },
run: async (args) => {
const provision = String(args.provision ?? "");
if (!provision) return { error: "name a provision" };
return await graph.whoProvides(provision);
},
},
{
name: "catalog_dependents",
description:
"What was built against this module — the modules a change to it reaches. Derived from builds, not from a declared list, so it cannot drift from what the code actually uses.",
input: { module: { type: "string", description: "the module that would change" } },
run: async (args) => {
const module = String(args.module ?? "");
if (!module) return { error: "name a module" };
return { module, dependents: await graph.dependents(module) };
},
},
{
name: "catalog_stale",
description:
"What must be rebuilt and why — every module built against something that has since moved. `buildable` is the subset waiting on nothing that is itself stale, which is the set that can be built right now.",
input: {},
run: async () => ({
stale: await graph.stale(),
buildable: await graph.buildable(),
}),
},
];
}
// Opened from the environment when the runtime asks for the module's tools. When it cannot be —
// no DATABASE_URL — the module contributes no tools rather than taking the whole tool runtime down
// with it, which is the postgres precedent.
registerModuleTools("mesh-catalog", (env) => {
try {
return getCatalogueTools(Graph.fromEnv(env));
} catch {
return [];
}
});