@totemsdk/mcp-server 0.4.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/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 or outbound dependencies', 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' } }, required: ['package'] } },
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
- return { content: [{ type: 'text', text: `Source not found: ${pkg}/src/${file}` }], isError: true };
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
- const pkg = index.packages[pkgName];
217
- if (direction === 'out' || direction === 'all') {
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
- if (direction === 'in' || direction === 'all') {
230
- const dependents = [];
231
- for (const [name, other] of Object.entries(index.packages)) {
232
- if (other.dependencies.includes(pkgName))
233
- dependents.push(name);
234
- }
235
- return {
236
- content: [{
237
- type: 'text',
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
- return { content: [{ type: 'text', text: 'Invalid direction. Use "in", "out", or "all".' }], isError: true };
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
- const hasDep = fromPkgData.dependencies.includes(toPkg);
257
- const result = { valid: hasDep, importPath: toPkg, symbolFound: false };
258
- if (hasDep) {
259
- if (symbol) {
260
- const allExports = [
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
- else {
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
- return {
278
- content: [{
279
- type: 'text',
280
- text: JSON.stringify(result, null, 2),
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: 'MIT',
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;
@@ -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
+ }