@totemsdk/mcp-server 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -21
- package/NOTICE +60 -0
- package/README.md +1 -1
- package/data/sdk-index.json +1 -1
- package/dist/composition.d.ts +77 -0
- package/dist/composition.js +375 -0
- package/dist/contracts.d.ts +18 -0
- package/dist/contracts.js +108 -0
- package/dist/indexer.js +99 -8
- package/dist/recipes.d.ts +15 -0
- package/dist/recipes.js +37 -0
- package/dist/resources.js +103 -13
- package/dist/tools.js +194 -57
- package/dist/types.d.ts +34 -0
- package/dist/workflow.d.ts +36 -0
- package/dist/workflow.js +200 -0
- package/package.json +6 -4
package/dist/tools.js
CHANGED
|
@@ -5,6 +5,8 @@ exports.handleToolCall = handleToolCall;
|
|
|
5
5
|
const template_catalog_js_1 = require("./template-catalog.js");
|
|
6
6
|
const indexer_js_1 = require("./indexer.js");
|
|
7
7
|
const index_store_js_1 = require("./index-store.js");
|
|
8
|
+
const composition_js_1 = require("./composition.js");
|
|
9
|
+
const contracts_js_1 = require("./contracts.js");
|
|
8
10
|
/**
|
|
9
11
|
* The single source of truth for the MCP tool catalog. `index.ts` advertises
|
|
10
12
|
* these and `handleToolCall` dispatches them; the `mcp.test.ts` drift guard
|
|
@@ -13,8 +15,8 @@ const index_store_js_1 = require("./index-store.js");
|
|
|
13
15
|
exports.TOOL_DEFINITIONS = [
|
|
14
16
|
{ name: 'search-symbol', description: 'Search for a symbol (function, type, class) across all packages', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Partial symbol name to search' } }, required: ['query'] } },
|
|
15
17
|
{ name: 'find-type', description: 'Find type definitions (interfaces, classes, type aliases) matching a pattern', inputSchema: { type: 'object', properties: { pattern: { type: 'string', description: 'Type name pattern to search' } }, required: ['pattern'] } },
|
|
16
|
-
{ name: 'dependency-graph', description: 'Get dependency graph for a package — inbound dependents
|
|
17
|
-
{ name: 'validate-import', description: 'Check whether a cross-package import is valid', inputSchema: { type: 'object', properties: { from: { type: 'string', description: 'Source package name' }, to: { type: 'string', description: 'Target package name' }, symbol: { type: 'string', description: 'Optional: specific symbol to check' } }, required: ['from', 'to'] } },
|
|
18
|
+
{ name: 'dependency-graph', description: 'Get dependency graph for a package — inbound dependents, outbound dependencies, typed edge kinds/ranges, cycles and SCCs', inputSchema: { type: 'object', properties: { package: { type: 'string', description: 'Package name (e.g. @totemsdk/edge-opcua)' }, direction: { type: 'string', enum: ['in', 'out', 'all'], description: 'Dependency direction' }, format: { type: 'string', enum: ['text', 'json'], description: 'Output format (default text)' } }, required: ['package'] } },
|
|
19
|
+
{ name: 'validate-import', description: 'Check whether a cross-package import is valid, including dependency kind, export subpath and condition', inputSchema: { type: 'object', properties: { from: { type: 'string', description: 'Source package name' }, to: { type: 'string', description: 'Target package name' }, symbol: { type: 'string', description: 'Optional: specific symbol to check' }, subpath: { type: 'string', description: 'Optional export subpath (e.g. ./artifacts)' }, condition: { type: 'string', description: 'Optional runtime condition (e.g. import, require, types)' } }, required: ['from', 'to'] } },
|
|
18
20
|
{ name: 'scaffold-adapter', description: 'Generate boilerplate for a new edge protocol adapter', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'Package name suffix' }, protocol: { type: 'string', description: 'Protocol name (PascalCase)' }, commands: { type: 'array', items: { type: 'string' }, description: 'Transport port methods' } }, required: ['name', 'protocol'] } },
|
|
19
21
|
{ name: 'scaffold-package', description: 'Generate boilerplate for a new @totemsdk package', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'Package name (without @totemsdk/ prefix)' }, deps: { type: 'array', items: { type: 'string' }, description: 'Dependency package names' } }, required: ['name'] } },
|
|
20
22
|
{ name: 'package-stats', description: 'Get statistics about a package — export counts, Rust/Go, tests, deps', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'Package name' } }, required: ['name'] } },
|
|
@@ -23,6 +25,9 @@ exports.TOOL_DEFINITIONS = [
|
|
|
23
25
|
{ name: 'read-source', description: "Read a source file from a package's src/ directory (e.g. package '@totemsdk/core', path 'treekey.ts')", inputSchema: { type: 'object', properties: { package: { type: 'string', description: 'Package name (e.g. @totemsdk/core)' }, path: { type: 'string', description: 'Path relative to the package src/ directory' } }, required: ['package', 'path'] } },
|
|
24
26
|
{ name: 'list-packages', description: 'List SDK packages, optionally filtered by domain or name substring', inputSchema: { type: 'object', properties: { domain: { type: 'string', description: 'Optional domain filter (e.g. edge/runtime... see totemsdk://domain-map)' }, filter: { type: 'string', description: 'Optional package-name substring filter' } } } },
|
|
25
27
|
{ name: 'search-packages', description: 'Search packages by keyword across name, description, keywords and exports (e.g. "wots lease coordination")', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Free-text query' }, domain: { type: 'string', description: 'Optional domain filter' }, limit: { type: 'number', description: 'Max results (default 15)' } }, required: ['query'] } },
|
|
28
|
+
{ name: 'inspect-contract', description: 'Inspect bounded package contract metadata by package, export subpath and symbol', inputSchema: { type: 'object', properties: { package: { type: 'string' }, subpath: { type: 'string', description: 'Export subpath, default "."' }, symbol: { type: 'string', description: 'Exact symbol name' }, offset: { type: 'number' }, limit: { type: 'number', description: 'Max 100' }, includeDocs: { type: 'boolean' } }, required: ['package'] } },
|
|
29
|
+
{ name: 'plan-composition', description: 'Plan a justified SDK composition across the full catalogue, with per-package dispositions, dependency closure, capability map, maturity and warnings', inputSchema: { type: 'object', properties: { brief: { type: 'string' }, runtime: { type: 'string', enum: ['node-cjs', 'node-esm', 'bare', 'browser', 'unknown'] }, requiredPackages: { type: 'array', items: { type: 'string' } }, suggestedPackages: { type: 'array', items: { type: 'string' } }, optionalPackages: { type: 'array', items: { type: 'string' } }, excludedPackages: { type: 'array', items: { type: 'string' } }, requiredCapabilities: { type: 'array', items: { type: 'string' } } } } },
|
|
30
|
+
{ name: 'scaffold-app', description: 'Generate a structured application file map from a composition plan (generator only; no writes or installs)', inputSchema: { type: 'object', properties: { appName: { type: 'string' }, runtime: { type: 'string', enum: ['node-cjs', 'node-esm'] }, plan: { type: 'object' } }, required: ['plan'] } },
|
|
26
31
|
{ name: 'refresh-index', description: 'Rebuild the SDK index from the filesystem (development convenience) and report counts', inputSchema: { type: 'object', properties: {} } },
|
|
27
32
|
];
|
|
28
33
|
function handleToolCall(name, args, index) {
|
|
@@ -36,13 +41,23 @@ function handleToolCall(name, args, index) {
|
|
|
36
41
|
case 'package-stats': return packageStats(args, index);
|
|
37
42
|
case 'list-exports': return listExports(args, index);
|
|
38
43
|
case 'suggest-template': return suggestTemplate(args);
|
|
39
|
-
case 'read-source': return readSource(args);
|
|
44
|
+
case 'read-source': return readSource(args, index);
|
|
40
45
|
case 'list-packages': return listPackages(args, index);
|
|
41
46
|
case 'search-packages': return searchPackages(args, index);
|
|
47
|
+
case 'inspect-contract': return (0, contracts_js_1.inspectContract)(args, index);
|
|
48
|
+
case 'plan-composition': return jsonResult((0, composition_js_1.planComposition)(args, index));
|
|
49
|
+
case 'scaffold-app':
|
|
50
|
+
if (!args.plan || typeof args.plan !== 'object') {
|
|
51
|
+
return { content: [{ type: 'text', text: 'plan is required (call plan-composition first)' }], isError: true };
|
|
52
|
+
}
|
|
53
|
+
return jsonResult((0, composition_js_1.scaffoldApp)(args, index));
|
|
42
54
|
case 'refresh-index': return refreshIndexTool();
|
|
43
55
|
default: return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
|
|
44
56
|
}
|
|
45
57
|
}
|
|
58
|
+
function jsonResult(value) {
|
|
59
|
+
return { content: [{ type: 'text', text: JSON.stringify(value, null, 2) }] };
|
|
60
|
+
}
|
|
46
61
|
function refreshIndexTool() {
|
|
47
62
|
const idx = (0, index_store_js_1.refreshIndex)();
|
|
48
63
|
return {
|
|
@@ -95,14 +110,25 @@ function searchPackages(args, index) {
|
|
|
95
110
|
}],
|
|
96
111
|
};
|
|
97
112
|
}
|
|
98
|
-
function readSource(args) {
|
|
113
|
+
function readSource(args, index) {
|
|
99
114
|
const pkg = args.package;
|
|
100
115
|
const file = args.path;
|
|
101
116
|
if (!pkg || !file)
|
|
102
117
|
return { content: [{ type: 'text', text: 'package and path are required' }], isError: true };
|
|
103
118
|
const content = (0, indexer_js_1.readSourceFile)(pkg, file);
|
|
104
119
|
if (content === null) {
|
|
105
|
-
|
|
120
|
+
const indexed = index.packages?.[pkg];
|
|
121
|
+
const contractUri = `totemsdk://packages/${encodeURIComponent(pkg)}/contracts`;
|
|
122
|
+
return {
|
|
123
|
+
content: [{
|
|
124
|
+
type: 'text',
|
|
125
|
+
text: `Source unavailable: ${pkg}/src/${file}. `
|
|
126
|
+
+ (indexed
|
|
127
|
+
? `This build does not ship implementation source; read the exact contracts at ${contractUri}.`
|
|
128
|
+
: `Package '${pkg}' is not in the selected SDK index.`),
|
|
129
|
+
}],
|
|
130
|
+
isError: true,
|
|
131
|
+
};
|
|
106
132
|
}
|
|
107
133
|
return { content: [{ type: 'text', text: content }] };
|
|
108
134
|
}
|
|
@@ -207,43 +233,133 @@ function typeEntries(pkg) {
|
|
|
207
233
|
...pkg.exports.classes.map(n => ({ name: n, kind: 'class' })),
|
|
208
234
|
];
|
|
209
235
|
}
|
|
236
|
+
function dependencyEdges(index, name, direction) {
|
|
237
|
+
const edges = [];
|
|
238
|
+
const kinds = ['runtime', 'peer', 'optional', 'dev'];
|
|
239
|
+
if (direction === 'out' || direction === 'all') {
|
|
240
|
+
const pkg = index.packages[name];
|
|
241
|
+
for (const kind of kinds) {
|
|
242
|
+
for (const [to, range] of Object.entries(pkg?.dependencyGroups?.[kind] ?? {})) {
|
|
243
|
+
edges.push({ from: name, to, kind, range: String(range) });
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (direction === 'in' || direction === 'all') {
|
|
248
|
+
for (const [from, pkg] of Object.entries(index.packages)) {
|
|
249
|
+
for (const kind of kinds) {
|
|
250
|
+
const range = pkg.dependencyGroups?.[kind]?.[name];
|
|
251
|
+
if (range !== undefined)
|
|
252
|
+
edges.push({ from, to: name, kind, range: String(range) });
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return edges;
|
|
257
|
+
}
|
|
258
|
+
function stronglyConnected(index) {
|
|
259
|
+
const nodes = Object.keys(index.packages);
|
|
260
|
+
const adjacency = new Map();
|
|
261
|
+
for (const name of nodes) {
|
|
262
|
+
const out = new Set();
|
|
263
|
+
for (const kind of ['runtime', 'peer', 'optional']) {
|
|
264
|
+
for (const dep of Object.keys(index.packages[name]?.dependencyGroups?.[kind] ?? {})) {
|
|
265
|
+
if (index.packages[dep])
|
|
266
|
+
out.add(dep);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
adjacency.set(name, [...out]);
|
|
270
|
+
}
|
|
271
|
+
let counter = 0;
|
|
272
|
+
const indices = new Map();
|
|
273
|
+
const low = new Map();
|
|
274
|
+
const stack = [];
|
|
275
|
+
const onStack = new Set();
|
|
276
|
+
const components = [];
|
|
277
|
+
const visit = (node) => {
|
|
278
|
+
indices.set(node, counter);
|
|
279
|
+
low.set(node, counter);
|
|
280
|
+
counter++;
|
|
281
|
+
stack.push(node);
|
|
282
|
+
onStack.add(node);
|
|
283
|
+
for (const next of adjacency.get(node) ?? []) {
|
|
284
|
+
if (!indices.has(next)) {
|
|
285
|
+
visit(next);
|
|
286
|
+
low.set(node, Math.min(low.get(node), low.get(next)));
|
|
287
|
+
}
|
|
288
|
+
else if (onStack.has(next))
|
|
289
|
+
low.set(node, Math.min(low.get(node), indices.get(next)));
|
|
290
|
+
}
|
|
291
|
+
if (low.get(node) === indices.get(node)) {
|
|
292
|
+
const component = [];
|
|
293
|
+
let current;
|
|
294
|
+
do {
|
|
295
|
+
current = stack.pop();
|
|
296
|
+
onStack.delete(current);
|
|
297
|
+
component.push(current);
|
|
298
|
+
} while (current !== node);
|
|
299
|
+
if (component.length > 1 || (adjacency.get(node) ?? []).includes(node))
|
|
300
|
+
components.push(component.sort());
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
for (const node of nodes)
|
|
304
|
+
if (!indices.has(node))
|
|
305
|
+
visit(node);
|
|
306
|
+
return components.sort((a, b) => a[0].localeCompare(b[0]));
|
|
307
|
+
}
|
|
210
308
|
function dependencyGraph(args, index) {
|
|
211
309
|
const pkgName = args.package;
|
|
212
310
|
const direction = args.direction || 'out';
|
|
213
311
|
if (!pkgName || !index.packages[pkgName]) {
|
|
214
312
|
return { content: [{ type: 'text', text: `Package '${pkgName}' not found` }], isError: true };
|
|
215
313
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const totemDeps = pkg.dependencies.filter(d => d.startsWith('@totemsdk/'));
|
|
219
|
-
const externalDeps = pkg.dependencies.filter(d => !d.startsWith('@totemsdk/'));
|
|
220
|
-
return {
|
|
221
|
-
content: [{
|
|
222
|
-
type: 'text',
|
|
223
|
-
text: `# ${pkgName} — Outbound Dependencies\n\n` +
|
|
224
|
-
(totemDeps.length ? `**@totemsdk/* deps:**\n${totemDeps.map(d => ` - ${d}`).join('\n')}\n\n` : '') +
|
|
225
|
-
(externalDeps.length ? `**External deps:**\n${externalDeps.map(d => ` - ${d}`).join('\n')}\n` : '_(no external deps)_'),
|
|
226
|
-
}],
|
|
227
|
-
};
|
|
314
|
+
if (!['in', 'out', 'all'].includes(direction)) {
|
|
315
|
+
return { content: [{ type: 'text', text: 'Invalid direction. Use "in", "out", or "all".' }], isError: true };
|
|
228
316
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
text: `# ${pkgName} — Inbound Dependents\n\n` +
|
|
239
|
-
(dependents.length ? dependents.map(d => ` - ${d}`).join('\n') : '_(no dependents)_'),
|
|
240
|
-
}],
|
|
241
|
-
};
|
|
317
|
+
const edges = dependencyEdges(index, pkgName, direction);
|
|
318
|
+
if (args.format === 'json') {
|
|
319
|
+
return { content: [{ type: 'text', text: JSON.stringify({
|
|
320
|
+
package: pkgName,
|
|
321
|
+
direction,
|
|
322
|
+
edges,
|
|
323
|
+
nodes: [...new Set(edges.flatMap((edge) => [edge.from, edge.to]))].sort(),
|
|
324
|
+
stronglyConnectedComponents: stronglyConnected(index),
|
|
325
|
+
}, null, 2) }] };
|
|
242
326
|
}
|
|
243
|
-
|
|
327
|
+
const group = (kind) => {
|
|
328
|
+
const list = edges.filter((edge) => edge.kind === kind);
|
|
329
|
+
if (list.length === 0)
|
|
330
|
+
return '';
|
|
331
|
+
const heading = direction === 'in' && kind === 'runtime'
|
|
332
|
+
? 'Inbound runtime'
|
|
333
|
+
: `${kind[0].toUpperCase()}${kind.slice(1)}`;
|
|
334
|
+
return `**${heading}:**\n${list.map((edge) => ` - ${edge.from} → ${edge.to} (${edge.range})`).join('\n')}\n`;
|
|
335
|
+
};
|
|
336
|
+
return { content: [{ type: 'text', text: `# ${pkgName} — Dependencies (${direction})\n\n` +
|
|
337
|
+
(['runtime', 'peer', 'optional', 'dev'].map(group).filter(Boolean).join('\n') || '_(none)_') }] };
|
|
338
|
+
}
|
|
339
|
+
function hasExportPath(exportsMap, subpath, condition) {
|
|
340
|
+
if (!exportsMap)
|
|
341
|
+
return subpath === '.';
|
|
342
|
+
const value = exportsMap[subpath];
|
|
343
|
+
if (value === undefined)
|
|
344
|
+
return false;
|
|
345
|
+
if (condition === undefined)
|
|
346
|
+
return true;
|
|
347
|
+
const visit = (node) => {
|
|
348
|
+
if (typeof node === 'string' || node === null)
|
|
349
|
+
return true;
|
|
350
|
+
if (Array.isArray(node))
|
|
351
|
+
return node.some(visit);
|
|
352
|
+
if (typeof node !== 'object')
|
|
353
|
+
return false;
|
|
354
|
+
const record = node;
|
|
355
|
+
if (record[condition] !== undefined)
|
|
356
|
+
return true;
|
|
357
|
+
return false;
|
|
358
|
+
};
|
|
359
|
+
return visit(value);
|
|
244
360
|
}
|
|
245
361
|
function validateImport(args, index) {
|
|
246
|
-
const { from: fromPkg, to: toPkg, symbol } = args;
|
|
362
|
+
const { from: fromPkg, to: toPkg, symbol, subpath, condition } = args;
|
|
247
363
|
if (!fromPkg || !toPkg) {
|
|
248
364
|
return { content: [{ type: 'text', text: 'from and to package names are required' }], isError: true };
|
|
249
365
|
}
|
|
@@ -253,33 +369,43 @@ function validateImport(args, index) {
|
|
|
253
369
|
return { content: [{ type: 'text', text: `Source package '${fromPkg}' not found` }], isError: true };
|
|
254
370
|
if (!toPkgData)
|
|
255
371
|
return { content: [{ type: 'text', text: `Target package '${toPkg}' not found` }], isError: true };
|
|
256
|
-
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
...toPkgData.exports.functions,
|
|
262
|
-
...toPkgData.exports.types,
|
|
263
|
-
...toPkgData.exports.classes,
|
|
264
|
-
...toPkgData.exports.interfaces,
|
|
265
|
-
...toPkgData.exports.consts,
|
|
266
|
-
];
|
|
267
|
-
result.symbolFound = allExports.includes(symbol);
|
|
268
|
-
if (!result.symbolFound) {
|
|
269
|
-
result.valid = false;
|
|
270
|
-
result.reason = `'${toPkg}' does not export '${symbol}'`;
|
|
271
|
-
}
|
|
372
|
+
let dependencyKind = 'none';
|
|
373
|
+
for (const kind of ['runtime', 'peer', 'optional', 'dev']) {
|
|
374
|
+
if (fromPkgData.dependencyGroups?.[kind]?.[toPkg] !== undefined) {
|
|
375
|
+
dependencyKind = kind;
|
|
376
|
+
break;
|
|
272
377
|
}
|
|
273
378
|
}
|
|
274
|
-
|
|
379
|
+
const result = {
|
|
380
|
+
valid: dependencyKind !== 'none' && dependencyKind !== 'dev',
|
|
381
|
+
importPath: subpath ? `${toPkg}/${subpath.replace(/^\.\//, '')}` : toPkg,
|
|
382
|
+
symbolFound: false,
|
|
383
|
+
dependencyKind,
|
|
384
|
+
...(subpath ? { subpath } : {}),
|
|
385
|
+
...(condition ? { condition } : {}),
|
|
386
|
+
};
|
|
387
|
+
if (dependencyKind === 'none')
|
|
275
388
|
result.reason = `'${fromPkg}' does not depend on '${toPkg}'`;
|
|
389
|
+
else if (dependencyKind === 'dev')
|
|
390
|
+
result.reason = `'${toPkg}' is a development-only dependency of '${fromPkg}'`;
|
|
391
|
+
if (result.valid && subpath) {
|
|
392
|
+
if (!hasExportPath(toPkgData.exportsMap, subpath, condition)) {
|
|
393
|
+
result.valid = false;
|
|
394
|
+
result.reason = `'${toPkg}' does not export subpath '${subpath}'${condition ? ` under condition '${condition}'` : ''}`;
|
|
395
|
+
}
|
|
276
396
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
397
|
+
if (result.valid && symbol) {
|
|
398
|
+
const allExports = [
|
|
399
|
+
...toPkgData.exports.functions, ...toPkgData.exports.types, ...toPkgData.exports.classes,
|
|
400
|
+
...toPkgData.exports.interfaces, ...toPkgData.exports.consts,
|
|
401
|
+
];
|
|
402
|
+
result.symbolFound = allExports.includes(symbol);
|
|
403
|
+
if (!result.symbolFound) {
|
|
404
|
+
result.valid = false;
|
|
405
|
+
result.reason = `'${toPkg}' does not export '${symbol}'`;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
283
409
|
}
|
|
284
410
|
function scaffoldAdapter(args) {
|
|
285
411
|
const { name, protocol, commands } = args;
|
|
@@ -399,7 +525,7 @@ function scaffoldPackage(args) {
|
|
|
399
525
|
},
|
|
400
526
|
},
|
|
401
527
|
scripts: { build: 'tsc', clean: 'rm -rf dist', test: 'jest --passWithNoTests' },
|
|
402
|
-
files: ['dist', 'README.md', 'LICENSE'],
|
|
528
|
+
files: ['dist', 'README.md', 'LICENSE', 'NOTICE'],
|
|
403
529
|
dependencies: Object.fromEntries(depList.map(d => [d, '^0.1.0'])),
|
|
404
530
|
devDependencies: {
|
|
405
531
|
'@types/jest': '^30.0.0',
|
|
@@ -408,9 +534,20 @@ function scaffoldPackage(args) {
|
|
|
408
534
|
typescript: '^7.0.2',
|
|
409
535
|
},
|
|
410
536
|
publishConfig: { access: 'public' },
|
|
411
|
-
license: '
|
|
537
|
+
license: 'Apache-2.0',
|
|
412
538
|
}, null, 2),
|
|
413
539
|
});
|
|
540
|
+
files.push({
|
|
541
|
+
path: `NOTICE`,
|
|
542
|
+
content: [
|
|
543
|
+
`Totem SDK`,
|
|
544
|
+
`Copyright 2024-2026 Totem SDK Contributors`,
|
|
545
|
+
``,
|
|
546
|
+
`This package is licensed under the Apache License, Version 2.0.`,
|
|
547
|
+
`See LICENSE and the repository NOTICE for third-party attributions.`,
|
|
548
|
+
``,
|
|
549
|
+
].join('\n'),
|
|
550
|
+
});
|
|
414
551
|
files.push({
|
|
415
552
|
path: `tsconfig.json`,
|
|
416
553
|
content: JSON.stringify({
|
package/dist/types.d.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
export type DependencyKind = 'runtime' | 'peer' | 'optional' | 'dev';
|
|
2
|
+
export interface DependencyEdge {
|
|
3
|
+
from: string;
|
|
4
|
+
to: string;
|
|
5
|
+
kind: DependencyKind;
|
|
6
|
+
range: string;
|
|
7
|
+
}
|
|
1
8
|
export interface PackageIndex {
|
|
2
9
|
name: string;
|
|
3
10
|
dir: string;
|
|
@@ -6,6 +13,10 @@ export interface PackageIndex {
|
|
|
6
13
|
keywords?: string[];
|
|
7
14
|
dependencies: string[];
|
|
8
15
|
devDependencies: string[];
|
|
16
|
+
/** Declared dependency groups with ranges, when known. */
|
|
17
|
+
dependencyGroups?: Partial<Record<DependencyKind, Record<string, string>>>;
|
|
18
|
+
/** Package.json export subpaths and their runtime conditions. */
|
|
19
|
+
exportsMap?: Record<string, unknown>;
|
|
9
20
|
hasRust: boolean;
|
|
10
21
|
hasGo: boolean;
|
|
11
22
|
hasTests: boolean;
|
|
@@ -15,6 +26,8 @@ export interface PackageIndex {
|
|
|
15
26
|
[name: string]: SymbolMeta;
|
|
16
27
|
};
|
|
17
28
|
domain: string;
|
|
29
|
+
/** Sourced maturity label from the workspace gate, when known. */
|
|
30
|
+
maturity?: string;
|
|
18
31
|
}
|
|
19
32
|
export interface PackageExports {
|
|
20
33
|
functions: string[];
|
|
@@ -43,7 +56,25 @@ export interface SymbolEntry {
|
|
|
43
56
|
export interface DomainMap {
|
|
44
57
|
[domain: string]: string[];
|
|
45
58
|
}
|
|
59
|
+
export interface IndexCoverage {
|
|
60
|
+
/** Entries in the selected catalogue (SDK_MANIFEST.json). */
|
|
61
|
+
manifestCount: number;
|
|
62
|
+
/** Package directories discovered in the workspace. */
|
|
63
|
+
discoveredCount: number;
|
|
64
|
+
/** Catalogue entries with no indexed package. */
|
|
65
|
+
missingFromIndex: string[];
|
|
66
|
+
/** Indexed packages not declared in the catalogue. */
|
|
67
|
+
extraInIndex: string[];
|
|
68
|
+
}
|
|
46
69
|
export interface SdkIndex {
|
|
70
|
+
/** Index schema version; bump when consumers must adapt. */
|
|
71
|
+
schemaVersion?: number;
|
|
72
|
+
/** Where this index came from. */
|
|
73
|
+
source?: 'monorepo' | 'bundled';
|
|
74
|
+
/** Git revision of the scanned workspace, when known. */
|
|
75
|
+
sourceRevision?: string;
|
|
76
|
+
/** Catalogue/index coverage reconciliation. */
|
|
77
|
+
coverage?: IndexCoverage;
|
|
47
78
|
generatedAt: number;
|
|
48
79
|
packages: {
|
|
49
80
|
[name: string]: PackageIndex;
|
|
@@ -69,6 +100,9 @@ export interface ValidImportResult {
|
|
|
69
100
|
valid: boolean;
|
|
70
101
|
importPath?: string;
|
|
71
102
|
symbolFound?: boolean;
|
|
103
|
+
dependencyKind?: 'runtime' | 'peer' | 'optional' | 'dev' | 'none';
|
|
104
|
+
subpath?: string;
|
|
105
|
+
condition?: string;
|
|
72
106
|
reason?: string;
|
|
73
107
|
}
|
|
74
108
|
export interface ScaffoldResult {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Verified workflow assembly for generated applications. */
|
|
2
|
+
import type { SdkIndex } from './types.js';
|
|
3
|
+
export type WorkflowStepKind = 'code' | 'host-port' | 'unresolved';
|
|
4
|
+
export interface WorkflowStep {
|
|
5
|
+
id: string;
|
|
6
|
+
capability: string;
|
|
7
|
+
package: string;
|
|
8
|
+
contract: string;
|
|
9
|
+
kind: WorkflowStepKind;
|
|
10
|
+
authority: 'none' | 'signature' | 'policy' | 'human' | 'provider';
|
|
11
|
+
fixture?: string;
|
|
12
|
+
dependsOn?: string[];
|
|
13
|
+
limitations?: string[];
|
|
14
|
+
}
|
|
15
|
+
export interface WorkflowPlan {
|
|
16
|
+
steps: WorkflowStep[];
|
|
17
|
+
hostPorts: string[];
|
|
18
|
+
fixtures: string[];
|
|
19
|
+
acceptance: string[];
|
|
20
|
+
unresolved: string[];
|
|
21
|
+
}
|
|
22
|
+
export interface AdapterTemplate {
|
|
23
|
+
capability: string;
|
|
24
|
+
contract: string;
|
|
25
|
+
imports: string[];
|
|
26
|
+
body: string[];
|
|
27
|
+
authority?: WorkflowStep['authority'];
|
|
28
|
+
fixture?: string;
|
|
29
|
+
}
|
|
30
|
+
export declare function getWorkflowAdapter(packageName: string): AdapterTemplate | null;
|
|
31
|
+
export declare function buildWorkflow(input: {
|
|
32
|
+
directDependencies: string[];
|
|
33
|
+
maturity: Record<string, string>;
|
|
34
|
+
requiredCapabilities: string[];
|
|
35
|
+
index: SdkIndex;
|
|
36
|
+
}): WorkflowPlan;
|
package/dist/workflow.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** Verified workflow assembly for generated applications. */
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.getWorkflowAdapter = getWorkflowAdapter;
|
|
5
|
+
exports.buildWorkflow = buildWorkflow;
|
|
6
|
+
const ADAPTERS = {
|
|
7
|
+
'@totemsdk/core': {
|
|
8
|
+
capability: 'hashing',
|
|
9
|
+
contract: '@totemsdk/core#sha3_256',
|
|
10
|
+
imports: ["const { sha3_256, toHex } = await import('@totemsdk/core');"],
|
|
11
|
+
body: ["return toHex(sha3_256(new TextEncoder().encode('workflow-step')));"],
|
|
12
|
+
},
|
|
13
|
+
'@totemsdk/edge': {
|
|
14
|
+
capability: 'edge device',
|
|
15
|
+
contract: '@totemsdk/edge#createEdgeDevice',
|
|
16
|
+
imports: ["const { createEdgeDevice } = await import('@totemsdk/edge');"],
|
|
17
|
+
body: ["return createEdgeDevice({ kind: 'device' }).deviceId;"],
|
|
18
|
+
fixture: 'edge-device fixture',
|
|
19
|
+
},
|
|
20
|
+
'@totemsdk/proofgraph': {
|
|
21
|
+
capability: 'evidence graph',
|
|
22
|
+
contract: '@totemsdk/proofgraph#createProofGraph',
|
|
23
|
+
imports: ["const { createProofGraph } = await import('@totemsdk/proofgraph');"],
|
|
24
|
+
body: ["return createProofGraph({ workflow: true }).graphId;"],
|
|
25
|
+
authority: 'signature',
|
|
26
|
+
},
|
|
27
|
+
'@totemsdk/storage': {
|
|
28
|
+
capability: 'artifact storage',
|
|
29
|
+
contract: '@totemsdk/storage/artifacts#ArtifactStore.put',
|
|
30
|
+
imports: [
|
|
31
|
+
"const { ArtifactStore } = await import('@totemsdk/storage/artifacts');",
|
|
32
|
+
"const { LocalFileBackend } = await import('@totemsdk/storage/artifacts/local-fs-backend');",
|
|
33
|
+
],
|
|
34
|
+
body: [
|
|
35
|
+
"const store = new ArtifactStore(new LocalFileBackend('./artifacts'));",
|
|
36
|
+
"const receipt = await store.put('workflow', new TextEncoder().encode('artifact'));",
|
|
37
|
+
"return receipt.ref.digest;",
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
'@totemsdk/replication': {
|
|
41
|
+
capability: 'replica freshness',
|
|
42
|
+
contract: '@totemsdk/replication#readReplicated',
|
|
43
|
+
imports: ["const { readReplicated } = await import('@totemsdk/replication');"],
|
|
44
|
+
body: [
|
|
45
|
+
"const result = await readReplicated('workflow', {",
|
|
46
|
+
" readReplica: async () => ({ value: 'replica', complete: true, observedAt: Date.now() }),",
|
|
47
|
+
'});',
|
|
48
|
+
'return `${result.source}:${result.freshness}`;',
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
'@totemsdk/manifest': {
|
|
52
|
+
capability: 'signed manifest',
|
|
53
|
+
contract: '@totemsdk/manifest#createAnnouncement',
|
|
54
|
+
imports: ["const { createAnnouncement } = await import('@totemsdk/manifest');"],
|
|
55
|
+
body: [
|
|
56
|
+
'const announcement = createAnnouncement({',
|
|
57
|
+
" kind: 'service',",
|
|
58
|
+
" subjectId: 'workflow',",
|
|
59
|
+
" subjectHash: '0x' + '11'.repeat(32),",
|
|
60
|
+
" publisher: 'MxWorkflowPublisher',",
|
|
61
|
+
' sequence: 1,',
|
|
62
|
+
' createdAt: Date.now(),',
|
|
63
|
+
' expiresAt: Date.now() + 86_400_000,',
|
|
64
|
+
" topics: ['totemsdk:workflow'],",
|
|
65
|
+
" nonce: 'workflow',",
|
|
66
|
+
'});',
|
|
67
|
+
'return announcement.announcementId;',
|
|
68
|
+
],
|
|
69
|
+
authority: 'signature',
|
|
70
|
+
},
|
|
71
|
+
'@totemsdk/identity': {
|
|
72
|
+
capability: 'identity binding',
|
|
73
|
+
contract: '@totemsdk/identity#createIdentityDocument',
|
|
74
|
+
imports: ["const { createIdentityDocument } = await import('@totemsdk/identity');"],
|
|
75
|
+
body: [
|
|
76
|
+
'const document = createIdentityDocument({',
|
|
77
|
+
" kind: 'service',",
|
|
78
|
+
" rootAddress: 'MxWorkflowRoot',",
|
|
79
|
+
" controllerAddress: 'MxWorkflowRoot',",
|
|
80
|
+
'});',
|
|
81
|
+
'return document.id;',
|
|
82
|
+
],
|
|
83
|
+
authority: 'signature',
|
|
84
|
+
},
|
|
85
|
+
'@totemsdk/governance': {
|
|
86
|
+
capability: 'governance decision',
|
|
87
|
+
contract: '@totemsdk/governance#createGovernanceConfig',
|
|
88
|
+
imports: ["const { createGovernanceConfig } = await import('@totemsdk/governance');"],
|
|
89
|
+
body: [
|
|
90
|
+
'const config = createGovernanceConfig({',
|
|
91
|
+
" daoId: 'workflow',",
|
|
92
|
+
" name: 'workflow',",
|
|
93
|
+
" voting: { algorithm: 'linear', quorumBps: 5000, passThresholdBps: 5000, votingPeriodMs: 86_400_000 },",
|
|
94
|
+
' membership: {},',
|
|
95
|
+
" authorityScope: 'workflow',",
|
|
96
|
+
" authorityResolver: 'workflow',",
|
|
97
|
+
'});',
|
|
98
|
+
'return config.daoId;',
|
|
99
|
+
],
|
|
100
|
+
authority: 'policy',
|
|
101
|
+
},
|
|
102
|
+
'@totemsdk/omnia': {
|
|
103
|
+
capability: 'payment channel',
|
|
104
|
+
contract: '@totemsdk/omnia#buildEltooScript',
|
|
105
|
+
imports: ["const { buildEltooScript } = await import('@totemsdk/omnia');"],
|
|
106
|
+
body: [
|
|
107
|
+
'return buildEltooScript([',
|
|
108
|
+
" { partyId: 'a', publicKeyDigest: '11'.repeat(32), addressIndex: 0 },",
|
|
109
|
+
" { partyId: 'b', publicKeyDigest: '22'.repeat(32), addressIndex: 0 },",
|
|
110
|
+
']);',
|
|
111
|
+
],
|
|
112
|
+
authority: 'policy',
|
|
113
|
+
fixture: 'channel participants fixture',
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
const HOST_PORTS = {
|
|
117
|
+
'@totemsdk/agent-policy': 'wallet policy/approval port',
|
|
118
|
+
'@totemsdk/authority': 'authority verification port',
|
|
119
|
+
'@totemsdk/proof': 'proof verification port',
|
|
120
|
+
'@totemsdk/qvac': 'QVAC runtime/model host',
|
|
121
|
+
'@totemsdk/edge-mqtt': 'MQTT broker',
|
|
122
|
+
'@totemsdk/edge-modbus': 'Modbus endpoint',
|
|
123
|
+
'@totemsdk/edge-opcua': 'OPC-UA endpoint',
|
|
124
|
+
'@totemsdk/edge-bacnet': 'BACnet endpoint',
|
|
125
|
+
'@totemsdk/edge-lorawan': 'LoRaWAN gateway',
|
|
126
|
+
'@totemsdk/edge-ble': 'BLE host',
|
|
127
|
+
'@totemsdk/edge-ros2': 'ROS 2 host',
|
|
128
|
+
'@totemsdk/omnia-relay': 'Omnia relay URL',
|
|
129
|
+
'@totemsdk/lookup-client': 'lookup node endpoint',
|
|
130
|
+
};
|
|
131
|
+
function getWorkflowAdapter(packageName) {
|
|
132
|
+
return ADAPTERS[packageName] ?? null;
|
|
133
|
+
}
|
|
134
|
+
function buildWorkflow(input) {
|
|
135
|
+
const steps = [];
|
|
136
|
+
const hostPorts = new Set();
|
|
137
|
+
const fixtures = new Set();
|
|
138
|
+
const unresolved = new Set();
|
|
139
|
+
for (const [index, pkg] of input.directDependencies.entries()) {
|
|
140
|
+
const adapter = ADAPTERS[pkg];
|
|
141
|
+
if (adapter) {
|
|
142
|
+
steps.push({
|
|
143
|
+
id: `step-${index + 1}`,
|
|
144
|
+
capability: adapter.capability,
|
|
145
|
+
package: pkg,
|
|
146
|
+
contract: adapter.contract,
|
|
147
|
+
kind: 'code',
|
|
148
|
+
authority: adapter.authority ?? 'none',
|
|
149
|
+
...(adapter.fixture ? { fixture: adapter.fixture } : {}),
|
|
150
|
+
...(index > 0 ? { dependsOn: [`step-${index}`] } : {}),
|
|
151
|
+
});
|
|
152
|
+
if (adapter.fixture)
|
|
153
|
+
fixtures.add(adapter.fixture);
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (HOST_PORTS[pkg]) {
|
|
157
|
+
hostPorts.add(`${pkg}: ${HOST_PORTS[pkg]}`);
|
|
158
|
+
steps.push({
|
|
159
|
+
id: `step-${index + 1}`,
|
|
160
|
+
capability: HOST_PORTS[pkg],
|
|
161
|
+
package: pkg,
|
|
162
|
+
contract: 'host-supplied port',
|
|
163
|
+
kind: 'host-port',
|
|
164
|
+
authority: 'provider',
|
|
165
|
+
...(index > 0 ? { dependsOn: [`step-${index}`] } : {}),
|
|
166
|
+
});
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
unresolved.add(pkg);
|
|
170
|
+
steps.push({
|
|
171
|
+
id: `step-${index + 1}`,
|
|
172
|
+
capability: 'unresolved package workflow',
|
|
173
|
+
package: pkg,
|
|
174
|
+
contract: 'no verified adapter in the MCP catalogue',
|
|
175
|
+
kind: 'unresolved',
|
|
176
|
+
authority: 'none',
|
|
177
|
+
limitations: ['No verified workflow adapter for this package.'],
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
if (steps.length === 0) {
|
|
181
|
+
steps.push({
|
|
182
|
+
id: 'step-1',
|
|
183
|
+
capability: 'composition',
|
|
184
|
+
package: 'none',
|
|
185
|
+
contract: 'no direct packages selected',
|
|
186
|
+
kind: 'unresolved',
|
|
187
|
+
authority: 'none',
|
|
188
|
+
limitations: ['Select a composition before generating a workflow.'],
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
const acceptance = [
|
|
192
|
+
'Generated app installs and typechecks with the pinned dependency set.',
|
|
193
|
+
'The workflow executes every code step and reports fixture/host-port steps explicitly.',
|
|
194
|
+
'At least one negative or unauthorized input is rejected by the selected policy boundary.',
|
|
195
|
+
];
|
|
196
|
+
if (input.requiredCapabilities.some((capability) => /evidence|proof/i.test(capability))) {
|
|
197
|
+
acceptance.push('Tampered evidence is rejected, not treated as a valid attestation.');
|
|
198
|
+
}
|
|
199
|
+
return { steps, hostPorts: [...hostPorts], fixtures: [...fixtures], acceptance, unresolved: [...unresolved] };
|
|
200
|
+
}
|