@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/indexer.js
CHANGED
|
@@ -72,6 +72,24 @@ function detectRepoRoot() {
|
|
|
72
72
|
}
|
|
73
73
|
return null;
|
|
74
74
|
}
|
|
75
|
+
/** Read the checked-out Git revision without spawning a process. */
|
|
76
|
+
function readGitRevision(root) {
|
|
77
|
+
try {
|
|
78
|
+
const git = path.join(root, '.git');
|
|
79
|
+
let head = fs.readFileSync(path.join(git, 'HEAD'), 'utf-8').trim();
|
|
80
|
+
if (head.startsWith('ref: ')) {
|
|
81
|
+
const ref = head.slice(5).trim();
|
|
82
|
+
const refPath = path.join(git, ref);
|
|
83
|
+
head = fs.existsSync(refPath)
|
|
84
|
+
? fs.readFileSync(refPath, 'utf-8').trim()
|
|
85
|
+
: '';
|
|
86
|
+
}
|
|
87
|
+
return /^[0-9a-f]{40,64}$/.test(head) ? head : undefined;
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
75
93
|
function loadRepoData(root) {
|
|
76
94
|
const topPkgDir = path.join(root, 'packages');
|
|
77
95
|
const manifest = new Map();
|
|
@@ -97,13 +115,29 @@ function loadRepoData(root) {
|
|
|
97
115
|
catch {
|
|
98
116
|
// Optional artifact — fall back to the regex scan.
|
|
99
117
|
}
|
|
100
|
-
|
|
118
|
+
const maturity = new Map();
|
|
119
|
+
try {
|
|
120
|
+
const gates = JSON.parse(fs.readFileSync(path.join(root, 'scripts', 'workspace-gates.config.json'), 'utf-8'));
|
|
121
|
+
for (const [dir, meta] of Object.entries(gates.packages ?? {})) {
|
|
122
|
+
if (!meta?.maturity)
|
|
123
|
+
continue;
|
|
124
|
+
try {
|
|
125
|
+
const pj = JSON.parse(fs.readFileSync(path.join(root, dir, 'package.json'), 'utf-8'));
|
|
126
|
+
if (pj.name)
|
|
127
|
+
maturity.set(pj.name, meta.maturity);
|
|
128
|
+
}
|
|
129
|
+
catch { /* unreadable package */ }
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch { /* optional */ }
|
|
133
|
+
return { topPkgDir, manifest, maturity, ast };
|
|
101
134
|
}
|
|
102
135
|
function loadBundledIndex() {
|
|
103
136
|
try {
|
|
104
137
|
const idx = JSON.parse(fs.readFileSync(BUNDLED_INDEX_PATH, 'utf-8'));
|
|
105
|
-
if (idx && typeof idx === 'object' && idx.packages && typeof idx.packages === 'object')
|
|
106
|
-
return idx;
|
|
138
|
+
if (idx && typeof idx === 'object' && idx.packages && typeof idx.packages === 'object') {
|
|
139
|
+
return { ...idx, schemaVersion: idx.schemaVersion ?? 2, source: idx.source ?? 'bundled' };
|
|
140
|
+
}
|
|
107
141
|
return null;
|
|
108
142
|
}
|
|
109
143
|
catch {
|
|
@@ -310,10 +344,24 @@ function collectExports(file, symbols, visited) {
|
|
|
310
344
|
const braceMatch = block.match(/\{([^}]+)\}/);
|
|
311
345
|
if (braceMatch) {
|
|
312
346
|
for (const part of braceMatch[1].split(',')) {
|
|
313
|
-
|
|
314
|
-
if (!
|
|
347
|
+
let clause = part.trim();
|
|
348
|
+
if (!clause)
|
|
315
349
|
continue;
|
|
316
|
-
|
|
350
|
+
// Inline type modifiers: `export { type Foo }` / `export { type Foo as Bar }`.
|
|
351
|
+
// Strip the leading `type` keyword before resolving the exposed name,
|
|
352
|
+
// otherwise the symbol is recorded literally as "type Foo".
|
|
353
|
+
let inlineType = false;
|
|
354
|
+
const typeKw = clause.match(/^type\s+([\s\S]+)$/);
|
|
355
|
+
if (typeKw) {
|
|
356
|
+
inlineType = true;
|
|
357
|
+
clause = typeKw[1].trim();
|
|
358
|
+
}
|
|
359
|
+
const name = clause.split(/\s+as\s+/).pop()?.trim() || '';
|
|
360
|
+
// Reject anything that isn't a plain identifier (guards against
|
|
361
|
+
// malformed fragments leaking in as fake symbols).
|
|
362
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(name))
|
|
363
|
+
continue;
|
|
364
|
+
const kind = isType || inlineType || /^[A-Z]/.test(name) ? 'type' : 'function';
|
|
317
365
|
setSymbol(symbols, name, { kind });
|
|
318
366
|
}
|
|
319
367
|
}
|
|
@@ -348,15 +396,18 @@ function mergeAst(symbols, ast) {
|
|
|
348
396
|
}
|
|
349
397
|
/** Scan a monorepo root into an `SdkIndex`. */
|
|
350
398
|
function buildFromMonorepo(root, now = Date.now()) {
|
|
351
|
-
const { topPkgDir, manifest: MANIFEST, ast: AST } = loadRepoData(root);
|
|
399
|
+
const { topPkgDir, manifest: MANIFEST, maturity: MATURITY, ast: AST } = loadRepoData(root);
|
|
352
400
|
const packages = {};
|
|
353
401
|
const symbolIndex = {};
|
|
354
402
|
const domainMap = {};
|
|
403
|
+
const manifestNames = new Set(MANIFEST.keys());
|
|
404
|
+
const discoveredNames = new Set();
|
|
355
405
|
for (const dir of findPackageDirs(topPkgDir)) {
|
|
356
406
|
const pkg = readPackageJson(dir);
|
|
357
407
|
if (!pkg || !pkg.name)
|
|
358
408
|
continue;
|
|
359
409
|
const manifest = MANIFEST.get(pkg.name);
|
|
410
|
+
discoveredNames.add(pkg.name);
|
|
360
411
|
if (MANIFEST.size > 0 && !manifest)
|
|
361
412
|
continue;
|
|
362
413
|
const dirName = path.basename(dir);
|
|
@@ -372,12 +423,20 @@ function buildFromMonorepo(root, now = Date.now()) {
|
|
|
372
423
|
...(Array.isArray(pkg.keywords) ? { keywords: pkg.keywords } : {}),
|
|
373
424
|
dependencies: Object.keys(pkg.dependencies || {}),
|
|
374
425
|
devDependencies: Object.keys(pkg.devDependencies || {}),
|
|
426
|
+
dependencyGroups: {
|
|
427
|
+
runtime: { ...(pkg.dependencies || {}) },
|
|
428
|
+
peer: { ...(pkg.peerDependencies || {}) },
|
|
429
|
+
optional: { ...(pkg.optionalDependencies || {}) },
|
|
430
|
+
dev: { ...(pkg.devDependencies || {}) },
|
|
431
|
+
},
|
|
432
|
+
...(pkg.exports && typeof pkg.exports === 'object' ? { exportsMap: pkg.exports } : {}),
|
|
375
433
|
hasRust: hasSubdir(dir, 'rust') || hasSubdir(dir, 'rust-toolchain'),
|
|
376
434
|
hasGo: hasSubdir(dir, 'go'),
|
|
377
435
|
hasTests: hasTestDir(dir),
|
|
378
436
|
exports,
|
|
379
437
|
symbols: Object.fromEntries(symbols),
|
|
380
438
|
domain,
|
|
439
|
+
...(MATURITY.get(pkg.name) ? { maturity: MATURITY.get(pkg.name) } : {}),
|
|
381
440
|
};
|
|
382
441
|
packages[pkg.name] = idx;
|
|
383
442
|
if (!domainMap[domain])
|
|
@@ -393,7 +452,39 @@ function buildFromMonorepo(root, now = Date.now()) {
|
|
|
393
452
|
});
|
|
394
453
|
}
|
|
395
454
|
}
|
|
396
|
-
|
|
455
|
+
// Normalise workspace protocol ranges to the concrete workspace version so
|
|
456
|
+
// graph output is actionable outside pnpm. External ranges are preserved.
|
|
457
|
+
for (const pkg of Object.values(packages)) {
|
|
458
|
+
for (const kind of ['runtime', 'peer', 'optional', 'dev']) {
|
|
459
|
+
const group = pkg.dependencyGroups?.[kind];
|
|
460
|
+
if (!group)
|
|
461
|
+
continue;
|
|
462
|
+
for (const [dep, range] of Object.entries(group)) {
|
|
463
|
+
if (String(range).startsWith('workspace:')) {
|
|
464
|
+
const version = packages[dep]?.version;
|
|
465
|
+
group[dep] = version ? `^${version}` : String(range);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
const missingFromIndex = [...manifestNames].filter((name) => !packages[name]).sort();
|
|
471
|
+
const extraInIndex = [...discoveredNames].filter((name) => !manifestNames.has(name)).sort();
|
|
472
|
+
const sourceRevision = readGitRevision(root);
|
|
473
|
+
return {
|
|
474
|
+
schemaVersion: 2,
|
|
475
|
+
source: 'monorepo',
|
|
476
|
+
...(sourceRevision ? { sourceRevision } : {}),
|
|
477
|
+
coverage: {
|
|
478
|
+
manifestCount: manifestNames.size,
|
|
479
|
+
discoveredCount: discoveredNames.size,
|
|
480
|
+
missingFromIndex,
|
|
481
|
+
extraInIndex,
|
|
482
|
+
},
|
|
483
|
+
generatedAt: now,
|
|
484
|
+
packages,
|
|
485
|
+
symbolIndex,
|
|
486
|
+
domainMap,
|
|
487
|
+
};
|
|
397
488
|
}
|
|
398
489
|
/**
|
|
399
490
|
* Build the SDK index. Prefers a live monorepo scan; falls back to the bundled
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional verified recipe accelerators. They are fixtures for the shared
|
|
3
|
+
* planner/scaffolder, not special-cased branches and not the taxonomy of
|
|
4
|
+
* supported applications.
|
|
5
|
+
*/
|
|
6
|
+
import type { PlanCompositionInput, CompositionPlan } from './composition.js';
|
|
7
|
+
import type { SdkIndex } from './types.js';
|
|
8
|
+
export interface RecipeDefinition {
|
|
9
|
+
id: string;
|
|
10
|
+
title: string;
|
|
11
|
+
boundary: string;
|
|
12
|
+
input: Omit<PlanCompositionInput, 'brief'>;
|
|
13
|
+
}
|
|
14
|
+
export declare const RECIPES: RecipeDefinition[];
|
|
15
|
+
export declare function planRecipe(recipe: RecipeDefinition, index: SdkIndex): CompositionPlan;
|
package/dist/recipes.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Optional verified recipe accelerators. They are fixtures for the shared
|
|
4
|
+
* planner/scaffolder, not special-cased branches and not the taxonomy of
|
|
5
|
+
* supported applications.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.RECIPES = void 0;
|
|
9
|
+
exports.planRecipe = planRecipe;
|
|
10
|
+
const composition_js_1 = require("./composition.js");
|
|
11
|
+
exports.RECIPES = [
|
|
12
|
+
{
|
|
13
|
+
id: 'digital-product-passport',
|
|
14
|
+
title: 'Digital Product Passport',
|
|
15
|
+
boundary: 'Passport assembles attested event evidence; generated summaries are not certified digital twins.',
|
|
16
|
+
input: {
|
|
17
|
+
runtime: 'node-esm',
|
|
18
|
+
requiredPackages: ['@totemsdk/edge-mqtt', '@totemsdk/core', '@totemsdk/proof', '@totemsdk/proofgraph'],
|
|
19
|
+
suggestedPackages: ['@totemsdk/manifest', '@totemsdk/identity', '@totemsdk/authority', '@totemsdk/intelligence', '@totemsdk/qvac', '@totemsdk/storage'],
|
|
20
|
+
requiredCapabilities: ['attested event evidence', 'manifest identity', 'proof graph', 'local AI summary'],
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: 'machinepay-edge',
|
|
25
|
+
title: 'MachinePay Edge Gateway',
|
|
26
|
+
boundary: 'The gateway executes only within a signed policy envelope; payment receipts do not certify the physical outcome.',
|
|
27
|
+
input: {
|
|
28
|
+
runtime: 'node-esm',
|
|
29
|
+
requiredPackages: ['@totemsdk/edge', '@totemsdk/edge-mqtt', '@totemsdk/omnia', '@totemsdk/agent-policy'],
|
|
30
|
+
suggestedPackages: ['@totemsdk/manifest', '@totemsdk/proofgraph', '@totemsdk/root-identity'],
|
|
31
|
+
requiredCapabilities: ['edge device runtime', 'mqtt transport', 'payment channel', 'agent policy'],
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
];
|
|
35
|
+
function planRecipe(recipe, index) {
|
|
36
|
+
return (0, composition_js_1.planComposition)({ ...recipe.input, brief: `${recipe.title}. ${recipe.boundary}` }, index);
|
|
37
|
+
}
|
package/dist/resources.js
CHANGED
|
@@ -54,7 +54,7 @@ const PAPERS = [
|
|
|
54
54
|
{
|
|
55
55
|
uri: 'totemsdk://papers/yellow',
|
|
56
56
|
name: 'Core Yellow Paper',
|
|
57
|
-
description: 'WOTS
|
|
57
|
+
description: 'WOTS signatures, TreeKey hierarchy, MMR proofs, byte-exact serialization, lease/watermark coordination, Rust/WASM engine',
|
|
58
58
|
file: 'TOTEM_CORE_YELLOW_PAPER.md',
|
|
59
59
|
},
|
|
60
60
|
{
|
|
@@ -108,7 +108,12 @@ function docMeta(file, fallbackName) {
|
|
|
108
108
|
description = t.replace(/[*_`]/g, '').trim();
|
|
109
109
|
break;
|
|
110
110
|
}
|
|
111
|
-
|
|
111
|
+
const clean = description || name || fallbackName;
|
|
112
|
+
// Cap at the first sentence / ~160 chars. Discovery must stay cheap: an
|
|
113
|
+
// unbounded first paragraph bloats every client's resource list.
|
|
114
|
+
const firstSentence = clean.split(/(?<=\.)\s/)[0];
|
|
115
|
+
const bounded = (firstSentence.length <= 160 ? firstSentence : clean.slice(0, 160)).trim();
|
|
116
|
+
return { name: name || fallbackName, description: bounded.length > 0 ? bounded : name || fallbackName };
|
|
112
117
|
}
|
|
113
118
|
/**
|
|
114
119
|
* RFCs and audits are discovered from disk, so a new `docs/rfc/RFC-*.md` or
|
|
@@ -125,7 +130,14 @@ function discoverDocs() {
|
|
|
125
130
|
continue;
|
|
126
131
|
const file = path.join('docs', 'rfc', f);
|
|
127
132
|
const meta = docMeta(file, `RFC-${m[1]}`);
|
|
128
|
-
|
|
133
|
+
// Amendments share an RFC number with the original; give them a distinct
|
|
134
|
+
// URI (`totemsdk://rfc/007/amendment-a`) so a reader never retrieves an
|
|
135
|
+
// amendment when they asked for the base specification.
|
|
136
|
+
const amend = m[2].match(/^AMENDMENT-([A-Z])/i);
|
|
137
|
+
const uri = amend
|
|
138
|
+
? `totemsdk://rfc/${m[1]}/amendment-${amend[1].toLowerCase()}`
|
|
139
|
+
: `totemsdk://rfc/${m[1]}`;
|
|
140
|
+
docs.push({ uri, ...meta, file });
|
|
129
141
|
}
|
|
130
142
|
}
|
|
131
143
|
const auditDir = path.join(REPO_ROOT, 'docs', 'audits');
|
|
@@ -140,6 +152,19 @@ function discoverDocs() {
|
|
|
140
152
|
docs.push({ uri: `totemsdk://audit/${slug}`, ...meta, file });
|
|
141
153
|
}
|
|
142
154
|
}
|
|
155
|
+
// Worked, runnable end-to-end scenarios (usually backed by a test under
|
|
156
|
+
// packages/sdk-tests). Discovered from disk like RFCs/audits.
|
|
157
|
+
const exampleDir = path.join(REPO_ROOT, 'docs', 'examples');
|
|
158
|
+
if (fs.existsSync(exampleDir)) {
|
|
159
|
+
for (const f of fs.readdirSync(exampleDir).sort()) {
|
|
160
|
+
if (!f.endsWith('.md'))
|
|
161
|
+
continue;
|
|
162
|
+
const slug = f.replace(/\.md$/, '');
|
|
163
|
+
const file = path.join('docs', 'examples', f);
|
|
164
|
+
const meta = docMeta(file, slug);
|
|
165
|
+
docs.push({ uri: `totemsdk://example/${slug}`, ...meta, file });
|
|
166
|
+
}
|
|
167
|
+
}
|
|
143
168
|
return docs;
|
|
144
169
|
}
|
|
145
170
|
const DOCS = discoverDocs();
|
|
@@ -202,7 +227,7 @@ function resourceMimeType(uri) {
|
|
|
202
227
|
const base = uri.split('#')[0];
|
|
203
228
|
if (base === 'totemsdk://conventions' || base === 'totemsdk://templates')
|
|
204
229
|
return 'text/markdown';
|
|
205
|
-
if (/^totemsdk:\/\/(papers|rfc|audit)\/[^/]
|
|
230
|
+
if (/^totemsdk:\/\/(papers|rfc|audit|example)\/[^/]+(\/[^/]+)?$/.test(base))
|
|
206
231
|
return 'text/markdown';
|
|
207
232
|
if (base === 'totemsdk://packages' || base === 'totemsdk://domain-map' || base === 'totemsdk://rfc')
|
|
208
233
|
return 'application/json';
|
|
@@ -253,6 +278,17 @@ const CONVENTIONS = `# Totem SDK Conventions
|
|
|
253
278
|
## Async
|
|
254
279
|
- Promise<T> everywhere, typed event emitters, no Observables
|
|
255
280
|
- Action lifecycle: propose -> reserve -> execute -> confirm/fail/unknown`;
|
|
281
|
+
function decodeResourceSegment(value) {
|
|
282
|
+
try {
|
|
283
|
+
const decoded = decodeURIComponent(value);
|
|
284
|
+
if (!decoded || decoded.includes('..') || decoded.includes('\\') || decoded.includes('\0'))
|
|
285
|
+
return null;
|
|
286
|
+
return decoded;
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
256
292
|
function handleResourceRead(uri, index) {
|
|
257
293
|
if (uri === 'totemsdk://connect/methods')
|
|
258
294
|
return JSON.stringify(connectMethods(), null, 2);
|
|
@@ -275,6 +311,16 @@ function handleResourceRead(uri, index) {
|
|
|
275
311
|
}
|
|
276
312
|
return content;
|
|
277
313
|
}
|
|
314
|
+
if (uri === 'totemsdk://index') {
|
|
315
|
+
return JSON.stringify({
|
|
316
|
+
schemaVersion: index.schemaVersion ?? null,
|
|
317
|
+
source: index.source ?? 'unknown',
|
|
318
|
+
sourceRevision: index.sourceRevision ?? null,
|
|
319
|
+
generatedAt: index.generatedAt,
|
|
320
|
+
packageCount: Object.keys(index.packages).length,
|
|
321
|
+
coverage: index.coverage ?? null,
|
|
322
|
+
}, null, 2);
|
|
323
|
+
}
|
|
278
324
|
if (uri === 'totemsdk://packages') {
|
|
279
325
|
return JSON.stringify(Object.values(index.packages).map(p => ({
|
|
280
326
|
name: p.name,
|
|
@@ -306,29 +352,61 @@ function handleResourceRead(uri, index) {
|
|
|
306
352
|
}
|
|
307
353
|
const pkgMatch = uri.match(/^totemsdk:\/\/packages\/([^/]+)$/);
|
|
308
354
|
if (pkgMatch) {
|
|
309
|
-
const
|
|
355
|
+
const name = decodeResourceSegment(pkgMatch[1]);
|
|
356
|
+
if (!name)
|
|
357
|
+
return null;
|
|
358
|
+
const pkg = index.packages[name];
|
|
310
359
|
return pkg ? JSON.stringify(pkg, null, 2) : null;
|
|
311
360
|
}
|
|
361
|
+
const pkgContractsMatch = uri.match(/^totemsdk:\/\/packages\/([^/]+)\/contracts$/);
|
|
362
|
+
if (pkgContractsMatch) {
|
|
363
|
+
const name = decodeResourceSegment(pkgContractsMatch[1]);
|
|
364
|
+
if (!name)
|
|
365
|
+
return null;
|
|
366
|
+
const pkg = index.packages[name];
|
|
367
|
+
if (!pkg)
|
|
368
|
+
return null;
|
|
369
|
+
return JSON.stringify({
|
|
370
|
+
contentKind: 'contract',
|
|
371
|
+
package: pkg.name,
|
|
372
|
+
version: pkg.version,
|
|
373
|
+
origin: index.source ?? 'unknown',
|
|
374
|
+
sourceRevision: index.sourceRevision ?? null,
|
|
375
|
+
symbols: pkg.symbols ?? {},
|
|
376
|
+
}, null, 2);
|
|
377
|
+
}
|
|
312
378
|
const pkgExportsMatch = uri.match(/^totemsdk:\/\/packages\/([^/]+)\/exports$/);
|
|
313
379
|
if (pkgExportsMatch) {
|
|
314
|
-
const
|
|
380
|
+
const name = decodeResourceSegment(pkgExportsMatch[1]);
|
|
381
|
+
if (!name)
|
|
382
|
+
return null;
|
|
383
|
+
const pkg = index.packages[name];
|
|
315
384
|
return pkg ? JSON.stringify(pkg.exports, null, 2) : null;
|
|
316
385
|
}
|
|
317
386
|
const pkgDepsMatch = uri.match(/^totemsdk:\/\/packages\/([^/]+)\/dependencies$/);
|
|
318
387
|
if (pkgDepsMatch) {
|
|
319
|
-
const
|
|
388
|
+
const name = decodeResourceSegment(pkgDepsMatch[1]);
|
|
389
|
+
if (!name)
|
|
390
|
+
return null;
|
|
391
|
+
const pkg = index.packages[name];
|
|
320
392
|
if (!pkg)
|
|
321
393
|
return null;
|
|
322
394
|
return JSON.stringify({ dependencies: pkg.dependencies, devDependencies: pkg.devDependencies }, null, 2);
|
|
323
395
|
}
|
|
324
396
|
const domainMatch = uri.match(/^totemsdk:\/\/packages\/by-domain\/(.+)$/);
|
|
325
397
|
if (domainMatch) {
|
|
326
|
-
const
|
|
398
|
+
const domain = decodeResourceSegment(domainMatch[1]);
|
|
399
|
+
if (!domain)
|
|
400
|
+
return null;
|
|
401
|
+
const pkgs = index.domainMap[domain];
|
|
327
402
|
return pkgs ? JSON.stringify(pkgs.map(n => index.packages[n]).filter(Boolean), null, 2) : null;
|
|
328
403
|
}
|
|
329
404
|
const symbolMatch = uri.match(/^totemsdk:\/\/symbol\/(.+)$/);
|
|
330
405
|
if (symbolMatch) {
|
|
331
|
-
const
|
|
406
|
+
const symbol = decodeResourceSegment(symbolMatch[1]);
|
|
407
|
+
if (!symbol)
|
|
408
|
+
return null;
|
|
409
|
+
const entries = index.symbolIndex[symbol];
|
|
332
410
|
return entries ? JSON.stringify(entries, null, 2) : null;
|
|
333
411
|
}
|
|
334
412
|
return null;
|
|
@@ -337,6 +415,7 @@ function listResources(index) {
|
|
|
337
415
|
const resources = [
|
|
338
416
|
{ uri: 'totemsdk://packages', name: 'All Packages', description: `List of all ${Object.keys(index.packages).length} SDK packages with metadata` },
|
|
339
417
|
{ uri: 'totemsdk://templates', name: 'All KISSVM Templates', description: `All ${(0, template_catalog_js_1.getAllTemplates)().length} KISSVM script templates with import paths and descriptions` },
|
|
418
|
+
{ uri: 'totemsdk://index', name: 'Index Provenance', description: 'Index schema, source revision, package count and coverage reconciliation' },
|
|
340
419
|
{ uri: 'totemsdk://conventions', name: 'Coding Conventions', description: 'Totem SDK coding conventions and patterns' },
|
|
341
420
|
{ uri: 'totemsdk://domain-map', name: 'Domain Map', description: 'Packages grouped by domain layer' },
|
|
342
421
|
...PAPERS.map(p => ({ uri: p.uri, name: p.name, description: p.description })),
|
|
@@ -366,11 +445,22 @@ function listResources(index) {
|
|
|
366
445
|
name: `Dependencies: ${name}`,
|
|
367
446
|
description: `Dependencies of ${name}`,
|
|
368
447
|
});
|
|
448
|
+
resources.push({
|
|
449
|
+
uri: `totemsdk://packages/${encodeURIComponent(name)}/contracts`,
|
|
450
|
+
name: `Contracts: ${name}`,
|
|
451
|
+
description: `Exact exported contract metadata for ${name} (contentKind: contract)`,
|
|
452
|
+
});
|
|
369
453
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
454
|
+
// Symbol lookups are served by the `search-symbol` tool (query-driven), which
|
|
455
|
+
// is far cheaper than enumerating ~3.7k symbols as resources. Only surface a
|
|
456
|
+
// small set of the most unambiguous symbols (exported by exactly one package)
|
|
457
|
+
// so `totemsdk://symbol/{name}` remains discoverable without bloating
|
|
458
|
+
// discovery to hundreds of entries.
|
|
459
|
+
const uniqueSymbols = Object.entries(index.symbolIndex)
|
|
460
|
+
.filter(([_, v]) => v.length === 1)
|
|
461
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
462
|
+
.slice(0, 50);
|
|
463
|
+
for (const [symbol, entries] of uniqueSymbols) {
|
|
374
464
|
resources.push({
|
|
375
465
|
uri: `totemsdk://symbol/${symbol}`,
|
|
376
466
|
name: `Symbol: ${symbol}`,
|