@decantr/mcp-server 1.0.0-beta.9 → 1.0.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.
@@ -0,0 +1,2063 @@
1
+ // src/index.ts
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import {
5
+ ListToolsRequestSchema,
6
+ CallToolRequestSchema
7
+ } from "@modelcontextprotocol/sdk/types.js";
8
+
9
+ // src/tools.ts
10
+ import { existsSync, readdirSync, readFileSync } from "fs";
11
+ import { readFile as readFile2 } from "fs/promises";
12
+ import { basename, join as join2, dirname as dirname2, isAbsolute, resolve } from "path";
13
+ import { validateEssence, evaluateGuard, isV3 as isV32 } from "@decantr/essence-spec";
14
+ import { isContentIntelligenceSource, resolvePatternPreset } from "@decantr/registry";
15
+
16
+ // src/helpers.ts
17
+ import { readFile, writeFile, mkdir } from "fs/promises";
18
+ import { join, dirname } from "path";
19
+ import { RegistryAPIClient } from "@decantr/registry";
20
+ import { isV3, migrateV2ToV3 } from "@decantr/essence-spec";
21
+ var MAX_INPUT_LENGTH = 1e3;
22
+ function validateStringArg(args, field) {
23
+ const val = args[field];
24
+ if (!val || typeof val !== "string") {
25
+ return `Required parameter "${field}" must be a non-empty string.`;
26
+ }
27
+ if (val.length > MAX_INPUT_LENGTH) {
28
+ return `Parameter "${field}" exceeds maximum length of ${MAX_INPUT_LENGTH} characters.`;
29
+ }
30
+ return null;
31
+ }
32
+ function fuzzyScore(query, text) {
33
+ const q = query.toLowerCase();
34
+ const t = text.toLowerCase();
35
+ if (t === q) return 100;
36
+ if (t.startsWith(q)) return 90;
37
+ if (t.includes(q)) return 80;
38
+ let qi = 0;
39
+ for (let ti = 0; ti < t.length && qi < q.length; ti++) {
40
+ if (t[ti] === q[qi]) qi++;
41
+ }
42
+ return qi === q.length ? 60 : 0;
43
+ }
44
+ var _apiClient = null;
45
+ var _publicApiClient = null;
46
+ function getAPIClient() {
47
+ if (!_apiClient) {
48
+ _apiClient = new RegistryAPIClient({
49
+ baseUrl: process.env.DECANTR_API_URL || void 0,
50
+ apiKey: process.env.DECANTR_API_KEY || void 0
51
+ });
52
+ }
53
+ return _apiClient;
54
+ }
55
+ function getPublicAPIClient() {
56
+ if (!_publicApiClient) {
57
+ _publicApiClient = new RegistryAPIClient({
58
+ baseUrl: process.env.DECANTR_API_URL || void 0
59
+ });
60
+ }
61
+ return _publicApiClient;
62
+ }
63
+ async function readEssenceFile(essencePath) {
64
+ const resolvedPath = essencePath || join(process.cwd(), "decantr.essence.json");
65
+ const raw = await readFile(resolvedPath, "utf-8");
66
+ const essence = JSON.parse(raw);
67
+ return { essence, raw, path: resolvedPath };
68
+ }
69
+ async function writeEssenceFile(essencePath, essence) {
70
+ const dir = dirname(essencePath);
71
+ await mkdir(dir, { recursive: true });
72
+ await writeFile(essencePath, JSON.stringify(essence, null, 2) + "\n", "utf-8");
73
+ }
74
+ async function mutateEssenceFile(essencePath, mutate) {
75
+ const { essence, path } = await readEssenceFile(essencePath);
76
+ const v3 = isV3(essence) ? structuredClone(essence) : migrateV2ToV3(essence);
77
+ const updated = mutate(v3);
78
+ await writeEssenceFile(path, updated);
79
+ return { essence: updated, path };
80
+ }
81
+ async function readDriftLog(projectRoot) {
82
+ const root = projectRoot || process.cwd();
83
+ const logPath = join(root, ".decantr", "drift-log.json");
84
+ try {
85
+ const raw = await readFile(logPath, "utf-8");
86
+ return JSON.parse(raw);
87
+ } catch {
88
+ return [];
89
+ }
90
+ }
91
+ async function writeDriftLog(entries, projectRoot) {
92
+ const root = projectRoot || process.cwd();
93
+ const logPath = join(root, ".decantr", "drift-log.json");
94
+ await mkdir(dirname(logPath), { recursive: true });
95
+ await writeFile(logPath, JSON.stringify(entries, null, 2) + "\n", "utf-8");
96
+ return logPath;
97
+ }
98
+
99
+ // src/tools.ts
100
+ async function getShowcaseBenchmarkPayload(view) {
101
+ const client = getPublicAPIClient();
102
+ if (view === "manifest") {
103
+ return client.getShowcaseManifest();
104
+ }
105
+ if (view === "verification") {
106
+ return client.getShowcaseShortlistVerification();
107
+ }
108
+ return client.getShowcaseShortlist();
109
+ }
110
+ async function getRegistryIntelligenceSummaryPayload(namespace) {
111
+ const client = getPublicAPIClient();
112
+ return client.getRegistryIntelligenceSummary(namespace ? { namespace } : void 0);
113
+ }
114
+ async function getHostedExecutionPackBundlePayload(args) {
115
+ const client = getPublicAPIClient();
116
+ const essence = (() => {
117
+ if (typeof args.essence === "object" && args.essence !== null && !Array.isArray(args.essence)) {
118
+ return args.essence;
119
+ }
120
+ return readEssenceFile(args.path);
121
+ })();
122
+ return client.compileExecutionPacks(
123
+ essence,
124
+ typeof args.namespace === "string" ? { namespace: args.namespace } : void 0
125
+ );
126
+ }
127
+ async function getHostedSelectedExecutionPackPayload(args) {
128
+ const client = getPublicAPIClient();
129
+ const essence = (() => {
130
+ if (typeof args.essence === "object" && args.essence !== null && !Array.isArray(args.essence)) {
131
+ return args.essence;
132
+ }
133
+ return readEssenceFile(args.path);
134
+ })();
135
+ return client.selectExecutionPack(
136
+ {
137
+ essence,
138
+ pack_type: args.pack_type,
139
+ ...typeof args.id === "string" ? { id: args.id } : {}
140
+ },
141
+ typeof args.namespace === "string" ? { namespace: args.namespace } : void 0
142
+ );
143
+ }
144
+ async function getHostedExecutionPackManifestPayload(args) {
145
+ const client = getPublicAPIClient();
146
+ const essence = (() => {
147
+ if (typeof args.essence === "object" && args.essence !== null && !Array.isArray(args.essence)) {
148
+ return args.essence;
149
+ }
150
+ return readEssenceFile(args.path);
151
+ })();
152
+ return client.getExecutionPackManifest(
153
+ essence,
154
+ typeof args.namespace === "string" ? { namespace: args.namespace } : void 0
155
+ );
156
+ }
157
+ async function getHostedFileCritiquePayload(args) {
158
+ const client = getPublicAPIClient();
159
+ const filePath = args.file_path;
160
+ const resolvedFilePath = isAbsolute(filePath) ? filePath : resolve(process.cwd(), filePath);
161
+ const code = await readFile2(resolvedFilePath, "utf-8");
162
+ const { essence } = await readEssenceFile(args.path);
163
+ const treatmentsPath = typeof args.treatments_path === "string" ? isAbsolute(args.treatments_path) ? args.treatments_path : resolve(process.cwd(), args.treatments_path) : join2(process.cwd(), "src", "styles", "treatments.css");
164
+ const treatmentsCss = existsSync(treatmentsPath) ? readFileSync(treatmentsPath, "utf-8") : void 0;
165
+ return client.critiqueFile(
166
+ {
167
+ essence,
168
+ filePath,
169
+ code,
170
+ treatmentsCss
171
+ },
172
+ typeof args.namespace === "string" ? { namespace: args.namespace } : void 0
173
+ );
174
+ }
175
+ function extractHostedAssetPaths(indexHtml) {
176
+ const assetPaths = /* @__PURE__ */ new Set();
177
+ for (const match of indexHtml.matchAll(/<(?:script|link)[^>]+(?:src|href)="([^"]+)"/g)) {
178
+ const assetPath = match[1];
179
+ const assetsIndex = assetPath.indexOf("/assets/");
180
+ if (assetsIndex === -1) continue;
181
+ assetPaths.add(assetPath.slice(assetsIndex));
182
+ }
183
+ return [...assetPaths];
184
+ }
185
+ async function captureHostedDistSnapshot(projectRoot, distPathArg) {
186
+ const distRoot = distPathArg ? isAbsolute(distPathArg) ? distPathArg : resolve(projectRoot, distPathArg) : join2(projectRoot, "dist");
187
+ const indexPath = join2(distRoot, "index.html");
188
+ if (!existsSync(indexPath)) {
189
+ return void 0;
190
+ }
191
+ const indexHtml = readFileSync(indexPath, "utf-8");
192
+ const assets = {};
193
+ for (const assetPath of extractHostedAssetPaths(indexHtml)) {
194
+ const assetFilePath = join2(distRoot, assetPath.replace(/^[/\\]+/, ""));
195
+ if (existsSync(assetFilePath)) {
196
+ assets[assetPath] = readFileSync(assetFilePath, "utf-8");
197
+ }
198
+ }
199
+ return {
200
+ indexHtml,
201
+ assets
202
+ };
203
+ }
204
+ function isHostedSourceSnapshotFile(path) {
205
+ if (/\.d\.ts$/i.test(path)) return false;
206
+ return /\.(?:[cm]?[jt]sx?)$/i.test(path);
207
+ }
208
+ async function captureHostedSourceSnapshot(projectRoot, sourcesPathArg) {
209
+ if (!sourcesPathArg) {
210
+ return void 0;
211
+ }
212
+ const sourcesRoot = isAbsolute(sourcesPathArg) ? sourcesPathArg : resolve(projectRoot, sourcesPathArg);
213
+ if (!existsSync(sourcesRoot)) {
214
+ return void 0;
215
+ }
216
+ const files = {};
217
+ const ignoredDirNames = /* @__PURE__ */ new Set(["node_modules", ".git", ".decantr", "dist", "build", "coverage"]);
218
+ const rootPrefix = basename(sourcesRoot);
219
+ const walk = (absoluteDir, relativeDir) => {
220
+ for (const entry of readdirSync(absoluteDir, { withFileTypes: true })) {
221
+ if (ignoredDirNames.has(entry.name)) continue;
222
+ const absolutePath = join2(absoluteDir, entry.name);
223
+ const relativePath = join2(relativeDir, entry.name).replace(/\\/g, "/");
224
+ if (entry.isDirectory()) {
225
+ walk(absolutePath, relativePath);
226
+ continue;
227
+ }
228
+ if (!entry.isFile()) continue;
229
+ if (!isHostedSourceSnapshotFile(relativePath)) continue;
230
+ files[relativePath] = readFileSync(absolutePath, "utf-8");
231
+ }
232
+ };
233
+ walk(sourcesRoot, rootPrefix);
234
+ return Object.keys(files).length > 0 ? { files } : void 0;
235
+ }
236
+ async function getHostedProjectAuditPayload(args) {
237
+ const client = getPublicAPIClient();
238
+ const { essence } = await readEssenceFile(args.path);
239
+ const dist = await captureHostedDistSnapshot(process.cwd(), args.dist_path);
240
+ const sources = await captureHostedSourceSnapshot(process.cwd(), args.sources_path);
241
+ return client.auditProject(
242
+ {
243
+ essence,
244
+ dist,
245
+ sources
246
+ },
247
+ typeof args.namespace === "string" ? { namespace: args.namespace } : void 0
248
+ );
249
+ }
250
+ async function loadHostedExecutionPackBundleFallback(args) {
251
+ try {
252
+ return {
253
+ bundle: await getHostedExecutionPackBundlePayload(args),
254
+ error: null
255
+ };
256
+ } catch (error) {
257
+ return {
258
+ bundle: null,
259
+ error: error.message
260
+ };
261
+ }
262
+ }
263
+ async function loadHostedExecutionPackManifestFallback(args) {
264
+ try {
265
+ return {
266
+ manifest: await getHostedExecutionPackManifestPayload(args),
267
+ error: null
268
+ };
269
+ } catch (error) {
270
+ return {
271
+ manifest: null,
272
+ error: error.message
273
+ };
274
+ }
275
+ }
276
+ async function loadHostedSelectedExecutionPackFallback(args) {
277
+ try {
278
+ return {
279
+ selected: await getHostedSelectedExecutionPackPayload(args),
280
+ error: null
281
+ };
282
+ } catch (error) {
283
+ return {
284
+ selected: null,
285
+ error: error.message
286
+ };
287
+ }
288
+ }
289
+ async function loadHostedFileCritiqueFallback(args) {
290
+ try {
291
+ return {
292
+ report: await getHostedFileCritiquePayload(args),
293
+ error: null
294
+ };
295
+ } catch (error) {
296
+ return {
297
+ report: null,
298
+ error: error.message
299
+ };
300
+ }
301
+ }
302
+ async function loadHostedProjectAuditFallback(args) {
303
+ try {
304
+ return {
305
+ report: await getHostedProjectAuditPayload(args),
306
+ error: null
307
+ };
308
+ } catch (error) {
309
+ return {
310
+ report: null,
311
+ error: error.message
312
+ };
313
+ }
314
+ }
315
+ function hasExecutionPackPayload(payload) {
316
+ return payload.markdown !== null || payload.json !== null;
317
+ }
318
+ function toHostedExecutionPackPayload(pack) {
319
+ return {
320
+ markdown: pack && typeof pack.renderedMarkdown === "string" ? pack.renderedMarkdown : null,
321
+ json: pack ?? null
322
+ };
323
+ }
324
+ function findManifestEntryForPack(manifest, packType, id) {
325
+ switch (packType) {
326
+ case "scaffold":
327
+ return manifest.scaffold;
328
+ case "review":
329
+ return manifest.review ?? null;
330
+ case "section":
331
+ return id ? manifest.sections.find((section) => section.id === id) ?? null : null;
332
+ case "page":
333
+ return id ? manifest.pages.find((page) => page.id === id) ?? null : null;
334
+ case "mutation":
335
+ return id ? (manifest.mutations ?? []).find((mutation) => mutation.id === id) ?? null : null;
336
+ default:
337
+ return null;
338
+ }
339
+ }
340
+ var ZONE_ORDER = ["public", "gateway", "primary", "auxiliary"];
341
+ function deriveZones(inputs) {
342
+ const zoneMap = /* @__PURE__ */ new Map();
343
+ for (const input of inputs) {
344
+ const existing = zoneMap.get(input.role);
345
+ if (existing) {
346
+ existing.archetypes.push(input.archetypeId);
347
+ existing.features.push(...input.features);
348
+ existing.descriptions.push(input.description);
349
+ } else {
350
+ zoneMap.set(input.role, {
351
+ role: input.role,
352
+ archetypes: [input.archetypeId],
353
+ shell: input.shell,
354
+ features: [...input.features],
355
+ descriptions: [input.description]
356
+ });
357
+ }
358
+ }
359
+ for (const zone of zoneMap.values()) {
360
+ zone.features = [...new Set(zone.features)];
361
+ }
362
+ return ZONE_ORDER.filter((role) => zoneMap.has(role)).map((role) => zoneMap.get(role));
363
+ }
364
+ var GATEWAY_TRIGGER_MAP = {
365
+ auth: "authentication",
366
+ login: "authentication",
367
+ mfa: "authentication",
368
+ payment: "payment",
369
+ subscription: "payment",
370
+ checkout: "payment",
371
+ onboarding: "onboarding",
372
+ "setup-wizard": "onboarding",
373
+ welcome: "onboarding",
374
+ invite: "invitation",
375
+ "access-code": "invitation"
376
+ };
377
+ function resolveGatewayTrigger(features) {
378
+ for (const feature of features) {
379
+ const trigger = GATEWAY_TRIGGER_MAP[feature];
380
+ if (trigger) return trigger;
381
+ }
382
+ return "authentication";
383
+ }
384
+ function deriveTransitions(zones) {
385
+ const transitions = [];
386
+ const roles = new Set(zones.map((z) => z.role));
387
+ const gateway = zones.find((z) => z.role === "gateway");
388
+ const gatewayTrigger = gateway ? resolveGatewayTrigger(gateway.features) : "authentication";
389
+ const hasApp = roles.has("primary") || roles.has("auxiliary");
390
+ const hasGateway = roles.has("gateway");
391
+ const hasPublic = roles.has("public");
392
+ if (hasPublic && hasGateway) {
393
+ transitions.push({ from: "public", to: "gateway", type: "conversion", trigger: gatewayTrigger });
394
+ }
395
+ if (hasPublic && hasApp && !hasGateway) {
396
+ transitions.push({ from: "public", to: "app", type: "conversion", trigger: "navigation" });
397
+ }
398
+ if (hasGateway && hasApp) {
399
+ transitions.push({ from: "gateway", to: "app", type: "gate-pass", trigger: gatewayTrigger });
400
+ transitions.push({ from: "app", to: "gateway", type: "gate-return", trigger: gatewayTrigger });
401
+ }
402
+ if (hasApp && hasPublic) {
403
+ transitions.push({ from: "app", to: "public", type: "navigation", trigger: "external" });
404
+ }
405
+ return transitions;
406
+ }
407
+ var READ_ONLY = {
408
+ readOnlyHint: true,
409
+ destructiveHint: false,
410
+ idempotentHint: true,
411
+ openWorldHint: false
412
+ };
413
+ var READ_ONLY_NETWORK = {
414
+ readOnlyHint: true,
415
+ destructiveHint: false,
416
+ idempotentHint: true,
417
+ openWorldHint: true
418
+ };
419
+ var WRITE_TOOL = {
420
+ readOnlyHint: false,
421
+ destructiveHint: false,
422
+ idempotentHint: false,
423
+ openWorldHint: false
424
+ };
425
+ var TOOLS = [
426
+ // 1. decantr_read_essence — local read
427
+ {
428
+ name: "decantr_read_essence",
429
+ title: "Read Essence",
430
+ description: "Read and return the current decantr.essence.json file from the working directory. For v3 files, optionally filter by layer (dna, blueprint, or full).",
431
+ inputSchema: {
432
+ type: "object",
433
+ properties: {
434
+ path: { type: "string", description: "Optional path to essence file. Defaults to ./decantr.essence.json." },
435
+ layer: { type: "string", enum: ["dna", "blueprint", "full"], description: "For v3 essences: return only the specified layer. Defaults to full." }
436
+ }
437
+ },
438
+ annotations: READ_ONLY
439
+ },
440
+ // 2. decantr_validate — local read
441
+ {
442
+ name: "decantr_validate",
443
+ title: "Validate Essence",
444
+ description: "Validate a decantr.essence.json file against the schema and guard rules. For v3, reports DNA vs Blueprint violations separately.",
445
+ inputSchema: {
446
+ type: "object",
447
+ properties: {
448
+ path: { type: "string", description: "Path to essence file. Defaults to ./decantr.essence.json." }
449
+ }
450
+ },
451
+ annotations: READ_ONLY
452
+ },
453
+ // 3. decantr_search_registry — network
454
+ {
455
+ name: "decantr_search_registry",
456
+ title: "Search Registry",
457
+ description: "Search the Decantr community content registry for patterns, archetypes, themes, and shells.",
458
+ inputSchema: {
459
+ type: "object",
460
+ properties: {
461
+ query: { type: "string", description: 'Search query (e.g. "kanban", "neon", "dashboard")' },
462
+ type: { type: "string", description: "Filter by type: pattern, archetype, theme, shell" },
463
+ sort: { type: "string", description: "Optional sort: recommended, recent, or name." },
464
+ recommended: { type: "boolean", description: "When true, only return recommended items." },
465
+ source: { type: "string", description: "Optional intelligence source filter: authored, benchmark, or hybrid." }
466
+ },
467
+ required: ["query"]
468
+ },
469
+ annotations: READ_ONLY_NETWORK
470
+ },
471
+ // 4. decantr_resolve_pattern — network
472
+ {
473
+ name: "decantr_resolve_pattern",
474
+ title: "Resolve Pattern",
475
+ description: "Get full pattern details including layout spec, components, presets, and code examples.",
476
+ inputSchema: {
477
+ type: "object",
478
+ properties: {
479
+ id: { type: "string", description: 'Pattern ID (e.g. "hero", "data-table", "kpi-grid")' },
480
+ preset: { type: "string", description: 'Optional preset name (e.g. "product", "content")' },
481
+ namespace: { type: "string", description: 'Namespace (default: "@official")' }
482
+ },
483
+ required: ["id"]
484
+ },
485
+ annotations: READ_ONLY_NETWORK
486
+ },
487
+ // 5. decantr_resolve_archetype — network
488
+ {
489
+ name: "decantr_resolve_archetype",
490
+ title: "Resolve Archetype",
491
+ description: "Get archetype details including default pages, layouts, features, and suggested theme.",
492
+ inputSchema: {
493
+ type: "object",
494
+ properties: {
495
+ id: { type: "string", description: 'Archetype ID (e.g. "saas-dashboard", "ecommerce")' },
496
+ namespace: { type: "string", description: 'Namespace (default: "@official")' }
497
+ },
498
+ required: ["id"]
499
+ },
500
+ annotations: READ_ONLY_NETWORK
501
+ },
502
+ // 6. decantr_resolve_blueprint — network
503
+ {
504
+ name: "decantr_resolve_blueprint",
505
+ title: "Resolve Blueprint",
506
+ description: "Get a blueprint (app composition) with its archetype list, suggested theme, personality traits, and full page structure.",
507
+ inputSchema: {
508
+ type: "object",
509
+ properties: {
510
+ id: { type: "string", description: 'Blueprint ID (e.g. "saas-dashboard", "ecommerce", "portfolio")' },
511
+ namespace: { type: "string", description: 'Namespace (default: "@official")' }
512
+ },
513
+ required: ["id"]
514
+ },
515
+ annotations: READ_ONLY_NETWORK
516
+ },
517
+ // 8. decantr_suggest_patterns — network
518
+ {
519
+ name: "decantr_suggest_patterns",
520
+ title: "Suggest Patterns",
521
+ description: "Given a page description, suggest appropriate patterns from the registry. Returns ranked pattern matches with layout specs and component lists.",
522
+ inputSchema: {
523
+ type: "object",
524
+ properties: {
525
+ description: { type: "string", description: 'Description of the page or section (e.g. "dashboard with metrics and charts", "settings form with toggles")' }
526
+ },
527
+ required: ["description"]
528
+ },
529
+ annotations: READ_ONLY_NETWORK
530
+ },
531
+ // 9. decantr_check_drift — local read
532
+ {
533
+ name: "decantr_check_drift",
534
+ title: "Check Drift",
535
+ description: "Check if code changes violate the design intent captured in the Essence spec. For v3, returns separate dna_violations and blueprint_drift with autoFixable flags.",
536
+ inputSchema: {
537
+ type: "object",
538
+ properties: {
539
+ path: { type: "string", description: "Path to essence file. Defaults to ./decantr.essence.json." },
540
+ page_id: { type: "string", description: 'Page ID being modified (e.g. "overview", "settings")' },
541
+ components_used: {
542
+ type: "array",
543
+ items: { type: "string" },
544
+ description: "List of component names used in the generated code. Checked against page layout patterns."
545
+ },
546
+ theme_used: { type: "string", description: "Theme id used in the generated code" }
547
+ }
548
+ },
549
+ annotations: READ_ONLY
550
+ },
551
+ // 10. decantr_create_essence — network (fetches archetype)
552
+ {
553
+ name: "decantr_create_essence",
554
+ title: "Create Essence",
555
+ description: "Generate a valid v3 Essence spec skeleton from a project description. Returns a structured essence.json template based on the closest matching archetype and blueprint.",
556
+ inputSchema: {
557
+ type: "object",
558
+ properties: {
559
+ description: { type: "string", description: 'Natural language project description (e.g. "SaaS dashboard with analytics, user management, and billing")' },
560
+ framework: { type: "string", description: 'Target framework (e.g. "react", "vue", "svelte"). Defaults to "react".' }
561
+ },
562
+ required: ["description"]
563
+ },
564
+ annotations: READ_ONLY_NETWORK
565
+ },
566
+ // 11. decantr_accept_drift — WRITE tool (NEW)
567
+ {
568
+ name: "decantr_accept_drift",
569
+ title: "Accept Drift",
570
+ description: "Resolve guard violations by accepting, scoping, rejecting, or deferring drift. For DNA violations, requires explicit confirmation. Updates the essence file or drift log.",
571
+ inputSchema: {
572
+ type: "object",
573
+ properties: {
574
+ violations: {
575
+ type: "array",
576
+ items: {
577
+ type: "object",
578
+ properties: {
579
+ rule: { type: "string" },
580
+ page_id: { type: "string" },
581
+ details: { type: "string" }
582
+ },
583
+ required: ["rule"]
584
+ },
585
+ description: "The violations to resolve."
586
+ },
587
+ resolution: {
588
+ type: "string",
589
+ enum: ["accept", "accept_scoped", "reject", "defer"],
590
+ description: "How to resolve: accept updates the essence, accept_scoped limits to a page, reject is a no-op, defer logs for later."
591
+ },
592
+ scope: { type: "string", description: "For accept_scoped: the page or section scope." },
593
+ path: { type: "string", description: "Path to essence file. Defaults to ./decantr.essence.json." },
594
+ confirm_dna: { type: "boolean", description: "Required to be true when accepting DNA-layer violations." }
595
+ },
596
+ required: ["violations", "resolution"]
597
+ },
598
+ annotations: WRITE_TOOL
599
+ },
600
+ // 12. decantr_update_essence — WRITE tool (NEW)
601
+ {
602
+ name: "decantr_update_essence",
603
+ title: "Update Essence",
604
+ description: "Mutate the essence file: add/remove/update pages, update DNA or blueprint fields, add/remove features. Operates on v3 format (auto-migrates v2).",
605
+ inputSchema: {
606
+ type: "object",
607
+ properties: {
608
+ operation: {
609
+ type: "string",
610
+ enum: ["add_page", "remove_page", "update_page_layout", "update_dna", "update_blueprint", "add_feature", "remove_feature"],
611
+ description: "The mutation operation to perform."
612
+ },
613
+ payload: {
614
+ type: "object",
615
+ description: "Operation-specific payload. See tool docs for each operation."
616
+ },
617
+ path: { type: "string", description: "Path to essence file. Defaults to ./decantr.essence.json." }
618
+ },
619
+ required: ["operation", "payload"]
620
+ },
621
+ annotations: WRITE_TOOL
622
+ },
623
+ // 13. decantr_get_scaffold_context — local read
624
+ {
625
+ name: "decantr_get_scaffold_context",
626
+ title: "Get Scaffold Context",
627
+ description: "Get the top-level scaffold context for the current project. Returns the scaffold task brief, scaffold overview, compiled scaffold execution pack, compiled review pack, and pack manifest when available. Falls back to hosted execution-pack compilation when local context artifacts are missing.",
628
+ inputSchema: {
629
+ type: "object",
630
+ properties: {
631
+ path: {
632
+ type: "string",
633
+ description: "Optional path to an essence file when using hosted fallback compilation. Defaults to ./decantr.essence.json."
634
+ },
635
+ namespace: {
636
+ type: "string",
637
+ description: 'Optional preferred public namespace for hosted fallback compilation. Defaults to "@official".'
638
+ }
639
+ }
640
+ },
641
+ annotations: READ_ONLY
642
+ },
643
+ // 14. decantr_get_section_context — local read
644
+ {
645
+ name: "decantr_get_section_context",
646
+ title: "Get Section Context",
647
+ description: "Get the self-contained context for a specific section of the project. Returns the richer section context file and, when available, the compiled section execution pack for a more compact contract-first view. Falls back to hosted execution-pack compilation when local pack artifacts are missing.",
648
+ inputSchema: {
649
+ type: "object",
650
+ properties: {
651
+ section_id: { type: "string", description: 'Section ID (archetype ID, e.g., "ai-chatbot", "auth-full", "settings-full")' },
652
+ path: {
653
+ type: "string",
654
+ description: "Optional path to an essence file when using hosted fallback compilation. Defaults to ./decantr.essence.json."
655
+ },
656
+ namespace: {
657
+ type: "string",
658
+ description: 'Optional preferred public namespace for hosted fallback compilation. Defaults to "@official".'
659
+ }
660
+ },
661
+ required: ["section_id"]
662
+ },
663
+ annotations: READ_ONLY
664
+ },
665
+ // 15. decantr_get_page_context — local read
666
+ {
667
+ name: "decantr_get_page_context",
668
+ title: "Get Page Context",
669
+ description: "Get the route-local context for a specific page. Returns the compiled page execution pack plus its parent section pack and section context when available. Falls back to hosted execution-pack compilation when local pack artifacts are missing.",
670
+ inputSchema: {
671
+ type: "object",
672
+ properties: {
673
+ page_id: { type: "string", description: 'Page ID (for example "overview", "settings", or "home").' },
674
+ path: {
675
+ type: "string",
676
+ description: "Optional path to an essence file when using hosted fallback compilation. Defaults to ./decantr.essence.json."
677
+ },
678
+ namespace: {
679
+ type: "string",
680
+ description: 'Optional preferred public namespace for hosted fallback compilation. Defaults to "@official".'
681
+ }
682
+ },
683
+ required: ["page_id"]
684
+ },
685
+ annotations: READ_ONLY
686
+ },
687
+ // 16. decantr_get_execution_pack — local read
688
+ {
689
+ name: "decantr_get_execution_pack",
690
+ title: "Get Execution Pack",
691
+ description: "Read compiled execution packs from .decantr/context. Returns the pack manifest by default, or a specific scaffold, review, mutation, section, or page pack in markdown, JSON, or both. Falls back to the hosted selected-pack surface for targeted reads when local pack artifacts are missing.",
692
+ inputSchema: {
693
+ type: "object",
694
+ properties: {
695
+ pack_type: {
696
+ type: "string",
697
+ enum: ["manifest", "scaffold", "review", "mutation", "section", "page"],
698
+ description: "Pack type to fetch. Defaults to manifest."
699
+ },
700
+ id: {
701
+ type: "string",
702
+ description: 'Required for section/page/mutation packs (for example "dashboard", "overview", or "modify").'
703
+ },
704
+ format: {
705
+ type: "string",
706
+ enum: ["json", "markdown", "both"],
707
+ description: "Return format for a specific pack. Defaults to both."
708
+ },
709
+ path: {
710
+ type: "string",
711
+ description: "Optional path to an essence file when using hosted fallback compilation. Defaults to ./decantr.essence.json."
712
+ },
713
+ namespace: {
714
+ type: "string",
715
+ description: 'Optional preferred public namespace for hosted fallback compilation. Defaults to "@official".'
716
+ }
717
+ }
718
+ },
719
+ annotations: READ_ONLY
720
+ },
721
+ // 17. decantr_get_showcase_benchmarks — network read
722
+ {
723
+ name: "decantr_get_showcase_benchmarks",
724
+ title: "Get Showcase Benchmarks",
725
+ description: "Read the audited Decantr showcase corpus metadata. Returns the active manifest, shortlisted benchmark set, or the schema-backed shortlist verification report.",
726
+ inputSchema: {
727
+ type: "object",
728
+ properties: {
729
+ view: {
730
+ type: "string",
731
+ enum: ["manifest", "shortlist", "verification"],
732
+ description: "Which showcase benchmark view to return. Defaults to shortlist."
733
+ }
734
+ }
735
+ },
736
+ annotations: READ_ONLY_NETWORK
737
+ },
738
+ // 18. decantr_get_registry_intelligence_summary — network read
739
+ {
740
+ name: "decantr_get_registry_intelligence_summary",
741
+ title: "Get Registry Intelligence Summary",
742
+ description: "Read the hosted schema-backed registry intelligence summary. Useful for checking overall intelligence/recommendation coverage without crawling every item.",
743
+ inputSchema: {
744
+ type: "object",
745
+ properties: {
746
+ namespace: {
747
+ type: "string",
748
+ description: 'Optional namespace to scope the summary to, for example "@official".'
749
+ }
750
+ }
751
+ },
752
+ annotations: READ_ONLY_NETWORK
753
+ },
754
+ // 19. decantr_compile_execution_packs — network read
755
+ {
756
+ name: "decantr_compile_execution_packs",
757
+ title: "Compile Execution Packs",
758
+ description: "Compile a hosted execution-pack bundle from an essence document using the public Decantr API. Reads the local essence file by default, or accepts an inline essence object.",
759
+ inputSchema: {
760
+ type: "object",
761
+ properties: {
762
+ path: {
763
+ type: "string",
764
+ description: "Optional path to an essence file. Defaults to ./decantr.essence.json when essence is not provided."
765
+ },
766
+ essence: {
767
+ type: "object",
768
+ description: "Optional inline essence document to compile instead of reading from disk."
769
+ },
770
+ namespace: {
771
+ type: "string",
772
+ description: 'Optional preferred public namespace for content resolution. Defaults to "@official".'
773
+ }
774
+ }
775
+ },
776
+ annotations: READ_ONLY_NETWORK
777
+ },
778
+ // 20. decantr_audit_project — local read with hosted fallback
779
+ {
780
+ name: "decantr_audit_project",
781
+ title: "Audit Project",
782
+ description: "Audit the current project against the essence contract, guard rules, and compiled execution packs. Falls back to the hosted verifier when local compiled pack artifacts are missing. Returns a schema-backed project audit report.",
783
+ inputSchema: {
784
+ type: "object",
785
+ properties: {
786
+ path: { type: "string", description: "Optional path to the essence file for hosted fallback. Defaults to ./decantr.essence.json." },
787
+ namespace: { type: "string", description: 'Optional preferred public namespace for hosted fallback. Defaults to "@official".' },
788
+ dist_path: { type: "string", description: "Optional path to a local dist directory to snapshot for hosted runtime verification. Defaults to ./dist." },
789
+ sources_path: { type: "string", description: "Optional path to a local source directory to snapshot for hosted source-level verification. For example `src` or `app`." }
790
+ }
791
+ },
792
+ annotations: READ_ONLY_NETWORK
793
+ },
794
+ // 21. decantr_critique — local read with hosted fallback
795
+ {
796
+ name: "decantr_critique",
797
+ title: "Design Critique",
798
+ description: "Critique a file against the compiled review contract and Decantr verification heuristics. Falls back to the hosted verifier when local review packs are missing. Returns a schema-backed file critique report with scores, findings, and focus areas.",
799
+ inputSchema: {
800
+ type: "object",
801
+ properties: {
802
+ file_path: { type: "string", description: "Path to the component file to critique" },
803
+ path: { type: "string", description: "Optional path to the essence file when using hosted fallback. Defaults to ./decantr.essence.json." },
804
+ namespace: { type: "string", description: 'Optional preferred public namespace for hosted fallback. Defaults to "@official".' },
805
+ treatments_path: { type: "string", description: "Optional path to treatments.css when using hosted fallback. Defaults to ./src/styles/treatments.css." }
806
+ },
807
+ required: ["file_path"]
808
+ },
809
+ annotations: READ_ONLY_NETWORK
810
+ }
811
+ ];
812
+ async function handleTool(name, args) {
813
+ const apiClient = getAPIClient();
814
+ switch (name) {
815
+ case "decantr_read_essence": {
816
+ const essencePath = args.path || join2(process.cwd(), "decantr.essence.json");
817
+ try {
818
+ const raw = await readFile2(essencePath, "utf-8");
819
+ const essence = JSON.parse(raw);
820
+ const layer = args.layer;
821
+ if (layer && isV32(essence)) {
822
+ if (layer === "dna") return essence.dna;
823
+ if (layer === "blueprint") return essence.blueprint;
824
+ }
825
+ return essence;
826
+ } catch (e) {
827
+ return { error: `Could not read essence file: ${e.message}` };
828
+ }
829
+ }
830
+ case "decantr_validate": {
831
+ const essencePath = args.path || join2(process.cwd(), "decantr.essence.json");
832
+ let essence;
833
+ try {
834
+ essence = JSON.parse(await readFile2(essencePath, "utf-8"));
835
+ } catch (e) {
836
+ return { valid: false, errors: [`Could not read: ${e.message}`], guardViolations: [] };
837
+ }
838
+ const result = validateEssence(essence);
839
+ let guardViolations = [];
840
+ if (result.valid && typeof essence === "object" && essence !== null) {
841
+ try {
842
+ guardViolations = evaluateGuard(essence, {});
843
+ } catch {
844
+ }
845
+ }
846
+ if (result.valid && typeof essence === "object" && essence !== null && isV32(essence)) {
847
+ const dnaViolations = guardViolations.filter((v) => v.layer === "dna");
848
+ const blueprintViolations = guardViolations.filter((v) => v.layer === "blueprint");
849
+ const otherViolations = guardViolations.filter((v) => !v.layer);
850
+ return {
851
+ ...result,
852
+ format: "v3",
853
+ dna_violations: dnaViolations,
854
+ blueprint_violations: blueprintViolations,
855
+ guardViolations: otherViolations
856
+ };
857
+ }
858
+ return { ...result, guardViolations };
859
+ }
860
+ case "decantr_search_registry": {
861
+ const err = validateStringArg(args, "query");
862
+ if (err) return { error: err };
863
+ if (args.source && (typeof args.source !== "string" || !isContentIntelligenceSource(args.source))) {
864
+ return { error: "Invalid source. Must be one of: authored, benchmark, hybrid." };
865
+ }
866
+ try {
867
+ const response = await apiClient.search({
868
+ q: args.query,
869
+ type: args.type,
870
+ sort: args.sort,
871
+ recommended: args.recommended === true,
872
+ intelligenceSource: args.source
873
+ });
874
+ return {
875
+ total: response.total,
876
+ results: response.results.map((r) => ({
877
+ type: r.type,
878
+ id: r.slug,
879
+ namespace: r.namespace,
880
+ name: r.name,
881
+ description: r.description,
882
+ install: `decantr get ${r.type} ${r.slug}`,
883
+ intelligence: r.intelligence ?? null
884
+ }))
885
+ };
886
+ } catch (e) {
887
+ return { error: `Search failed: ${e.message}` };
888
+ }
889
+ }
890
+ case "decantr_resolve_pattern": {
891
+ const err = validateStringArg(args, "id");
892
+ if (err) return { error: err };
893
+ const namespace = args.namespace || "@official";
894
+ try {
895
+ const pattern = await apiClient.getPattern(namespace, args.id);
896
+ const result = { found: true, ...pattern };
897
+ if (args.preset && typeof args.preset === "string") {
898
+ const preset = resolvePatternPreset(pattern, args.preset);
899
+ if (preset) result.resolvedPreset = preset;
900
+ }
901
+ return result;
902
+ } catch {
903
+ return { found: false, message: `Pattern "${args.id}" not found in ${namespace}.` };
904
+ }
905
+ }
906
+ case "decantr_resolve_archetype": {
907
+ const err = validateStringArg(args, "id");
908
+ if (err) return { error: err };
909
+ const namespace = args.namespace || "@official";
910
+ try {
911
+ const archetype = await apiClient.getArchetype(namespace, args.id);
912
+ return { found: true, ...archetype };
913
+ } catch {
914
+ return { found: false, message: `Archetype "${args.id}" not found in ${namespace}.` };
915
+ }
916
+ }
917
+ case "decantr_resolve_blueprint": {
918
+ const err = validateStringArg(args, "id");
919
+ if (err) return { error: err };
920
+ const namespace = args.namespace || "@official";
921
+ try {
922
+ const blueprint = await apiClient.getBlueprint(namespace, args.id);
923
+ let topology = null;
924
+ const composeEntries = blueprint.compose;
925
+ if (composeEntries && Array.isArray(composeEntries) && composeEntries.length > 0) {
926
+ const zoneInputs = [];
927
+ const archetypePromises = composeEntries.map(async (entry) => {
928
+ const arcId = typeof entry === "string" ? entry : entry.archetype;
929
+ try {
930
+ const archData = await apiClient.getArchetype(namespace, arcId);
931
+ const explicitRole = typeof entry === "string" ? void 0 : entry.role;
932
+ zoneInputs.push({
933
+ archetypeId: arcId,
934
+ role: explicitRole || archData.role || "auxiliary",
935
+ shell: archData.pages?.[0]?.shell || "sidebar-main",
936
+ features: archData.features || [],
937
+ description: archData.description || ""
938
+ });
939
+ } catch {
940
+ }
941
+ });
942
+ await Promise.all(archetypePromises);
943
+ if (zoneInputs.length > 0) {
944
+ const zones = deriveZones(zoneInputs);
945
+ const transitions = deriveTransitions(zones);
946
+ const primaryArchetype = zoneInputs.find((z) => z.role === "primary");
947
+ topology = {
948
+ zones: zones.map((z) => ({
949
+ role: z.role,
950
+ archetypes: z.archetypes,
951
+ shell: z.shell,
952
+ features: z.features,
953
+ purpose: z.descriptions.join(" ")
954
+ })),
955
+ transitions,
956
+ entryPoints: {
957
+ anonymous: "/",
958
+ authenticated: primaryArchetype ? `/${primaryArchetype.archetypeId}` : "/home"
959
+ }
960
+ };
961
+ }
962
+ }
963
+ return { found: true, ...blueprint, ...topology ? { topology } : {} };
964
+ } catch {
965
+ return { found: false, message: `Blueprint "${args.id}" not found in ${namespace}.` };
966
+ }
967
+ }
968
+ case "decantr_suggest_patterns": {
969
+ const err = validateStringArg(args, "description");
970
+ if (err) return { error: err };
971
+ const desc = args.description.toLowerCase();
972
+ try {
973
+ const patternsResponse = await apiClient.listContent("patterns", {
974
+ namespace: "@official",
975
+ limit: 100
976
+ });
977
+ const prelimScores = [];
978
+ for (const p of patternsResponse.items) {
979
+ const slug = p.slug || "";
980
+ const name2 = p.name || slug;
981
+ const description = p.description || "";
982
+ const searchable = [name2, description].join(" ").toLowerCase();
983
+ let score = 0;
984
+ const words = desc.split(/\s+/);
985
+ for (const word of words) {
986
+ if (word.length < 3) continue;
987
+ if (searchable.includes(word)) score += 10;
988
+ }
989
+ if (desc.includes("dashboard") && ["kpi-grid", "chart-grid", "data-table", "filter-bar"].includes(slug)) score += 20;
990
+ if (desc.includes("metric") && slug === "kpi-grid") score += 15;
991
+ if (desc.includes("chart") && slug === "chart-grid") score += 15;
992
+ if (desc.includes("table") && slug === "data-table") score += 15;
993
+ if (desc.includes("form") && slug === "form-sections") score += 15;
994
+ if (desc.includes("setting") && slug === "form-sections") score += 15;
995
+ if (desc.includes("landing") && ["hero", "cta-section", "card-grid"].includes(slug)) score += 20;
996
+ if (desc.includes("hero") && slug === "hero") score += 20;
997
+ if (desc.includes("ecommerce") && ["card-grid", "filter-bar", "detail-header"].includes(slug)) score += 15;
998
+ if (desc.includes("product") && slug === "card-grid") score += 15;
999
+ if (desc.includes("feed") && slug === "activity-feed") score += 15;
1000
+ if (desc.includes("filter") && slug === "filter-bar") score += 15;
1001
+ if (desc.includes("search") && slug === "filter-bar") score += 10;
1002
+ if (score > 0) {
1003
+ prelimScores.push({ slug, score, name: name2, description });
1004
+ }
1005
+ }
1006
+ prelimScores.sort((a, b) => b.score - a.score);
1007
+ const top10 = prelimScores.slice(0, 10);
1008
+ const suggestions = [];
1009
+ for (const candidate of top10) {
1010
+ let fullPattern = null;
1011
+ try {
1012
+ const fetched = await apiClient.getPattern("@official", candidate.slug);
1013
+ fullPattern = fetched;
1014
+ } catch {
1015
+ }
1016
+ let score = candidate.score;
1017
+ if (fullPattern) {
1018
+ const fullSearchable = [
1019
+ ...fullPattern.components || [],
1020
+ ...fullPattern.tags || []
1021
+ ].join(" ").toLowerCase();
1022
+ const words = desc.split(/\s+/);
1023
+ for (const word of words) {
1024
+ if (word.length < 3) continue;
1025
+ if (fullSearchable.includes(word)) score += 10;
1026
+ }
1027
+ }
1028
+ const preset = fullPattern?.presets ? Object.values(fullPattern.presets)[0] : null;
1029
+ suggestions.push({
1030
+ id: candidate.slug,
1031
+ score,
1032
+ name: fullPattern?.name || candidate.name,
1033
+ description: fullPattern?.description || candidate.description,
1034
+ components: fullPattern?.components || [],
1035
+ layout: preset?.layout ? preset.layout.layout : "grid"
1036
+ });
1037
+ }
1038
+ suggestions.sort((a, b) => b.score - a.score);
1039
+ return {
1040
+ query: args.description,
1041
+ suggestions: suggestions.slice(0, 5),
1042
+ total: prelimScores.length
1043
+ };
1044
+ } catch (e) {
1045
+ return { error: `Could not fetch patterns: ${e.message}` };
1046
+ }
1047
+ }
1048
+ case "decantr_check_drift": {
1049
+ const essencePath = args.path || join2(process.cwd(), "decantr.essence.json");
1050
+ let essence;
1051
+ try {
1052
+ essence = JSON.parse(await readFile2(essencePath, "utf-8"));
1053
+ } catch (e) {
1054
+ return { error: `Could not read essence: ${e.message}` };
1055
+ }
1056
+ const validation = validateEssence(essence);
1057
+ if (!validation.valid) {
1058
+ return { drifted: true, reason: "invalid_essence", errors: validation.errors };
1059
+ }
1060
+ const violations = [];
1061
+ if (args.theme_used && typeof args.theme_used === "string") {
1062
+ let expectedThemeId;
1063
+ if (isV32(essence)) {
1064
+ expectedThemeId = essence.dna.theme.id;
1065
+ } else {
1066
+ const expectedTheme = essence.theme;
1067
+ expectedThemeId = expectedTheme?.id ?? expectedTheme?.style;
1068
+ }
1069
+ if (expectedThemeId && args.theme_used !== expectedThemeId) {
1070
+ violations.push({
1071
+ rule: "theme-match",
1072
+ severity: "critical",
1073
+ message: `Theme drift: code uses "${args.theme_used}" but Essence specifies "${expectedThemeId}". Do not switch themes.`,
1074
+ ...isV32(essence) ? { layer: "dna", autoFixable: false } : {}
1075
+ });
1076
+ }
1077
+ }
1078
+ if (args.page_id && typeof args.page_id === "string") {
1079
+ let pages;
1080
+ if (isV32(essence)) {
1081
+ pages = essence.blueprint.pages;
1082
+ } else {
1083
+ pages = essence.structure || [];
1084
+ }
1085
+ if (!pages.find((p) => p.id === args.page_id)) {
1086
+ violations.push({
1087
+ rule: "page-exists",
1088
+ severity: "critical",
1089
+ message: `Page "${args.page_id}" not found in Essence structure. Add it to the Essence before generating code for it.`,
1090
+ ...isV32(essence) ? {
1091
+ layer: "blueprint",
1092
+ autoFixable: true,
1093
+ autoFix: { type: "add_page", patch: { id: args.page_id } }
1094
+ } : {}
1095
+ });
1096
+ }
1097
+ }
1098
+ if (args.components_used && Array.isArray(args.components_used) && args.page_id && typeof args.page_id === "string") {
1099
+ let pages;
1100
+ if (isV32(essence)) {
1101
+ pages = essence.blueprint.pages;
1102
+ } else {
1103
+ pages = essence.structure || [];
1104
+ }
1105
+ const page = pages.find((p) => p.id === args.page_id);
1106
+ if (page && page.layout) {
1107
+ const expectedPatterns = /* @__PURE__ */ new Set();
1108
+ for (const item of page.layout) {
1109
+ if (typeof item === "string") {
1110
+ expectedPatterns.add(item);
1111
+ } else if (typeof item === "object" && item !== null && "pattern" in item) {
1112
+ expectedPatterns.add(item.pattern);
1113
+ }
1114
+ }
1115
+ const componentsUsed = args.components_used;
1116
+ const unmatchedComponents = [];
1117
+ for (const comp of componentsUsed) {
1118
+ const compLower = comp.toLowerCase().replace(/[_\s]/g, "-");
1119
+ let matched = false;
1120
+ for (const pattern of expectedPatterns) {
1121
+ const patternLower = pattern.toLowerCase();
1122
+ if (compLower.includes(patternLower) || patternLower.includes(compLower) || fuzzyScore(compLower, patternLower) >= 60) {
1123
+ matched = true;
1124
+ break;
1125
+ }
1126
+ }
1127
+ if (!matched) {
1128
+ unmatchedComponents.push(comp);
1129
+ }
1130
+ }
1131
+ if (unmatchedComponents.length > 0) {
1132
+ violations.push({
1133
+ rule: "component-pattern-match",
1134
+ severity: "warning",
1135
+ message: `Components [${unmatchedComponents.join(", ")}] do not match any pattern in page "${args.page_id}" layout. Expected patterns: [${[...expectedPatterns].join(", ")}].`,
1136
+ ...isV32(essence) ? { layer: "blueprint", autoFixable: false } : {}
1137
+ });
1138
+ }
1139
+ }
1140
+ }
1141
+ try {
1142
+ const guardViolations = evaluateGuard(essence, {
1143
+ pageId: args.page_id
1144
+ });
1145
+ for (const gv of guardViolations) {
1146
+ violations.push({
1147
+ rule: gv.rule || "guard",
1148
+ severity: gv.severity || "warning",
1149
+ message: gv.message || "Guard violation",
1150
+ ...gv.layer ? { layer: gv.layer } : {},
1151
+ ...gv.autoFixable !== void 0 ? { autoFixable: gv.autoFixable } : {},
1152
+ ...gv.autoFix ? { autoFix: gv.autoFix } : {}
1153
+ });
1154
+ }
1155
+ } catch {
1156
+ }
1157
+ if (isV32(essence)) {
1158
+ const dnaViolations = violations.filter((v) => v.layer === "dna");
1159
+ const blueprintDrift = violations.filter((v) => v.layer === "blueprint");
1160
+ const other = violations.filter((v) => !v.layer);
1161
+ return {
1162
+ drifted: violations.length > 0,
1163
+ dna_violations: dnaViolations,
1164
+ blueprint_drift: blueprintDrift,
1165
+ other_violations: other,
1166
+ checkedAgainst: essencePath
1167
+ };
1168
+ }
1169
+ return {
1170
+ drifted: violations.length > 0,
1171
+ violations,
1172
+ checkedAgainst: essencePath
1173
+ };
1174
+ }
1175
+ case "decantr_create_essence": {
1176
+ const err = validateStringArg(args, "description");
1177
+ if (err) return { error: err };
1178
+ const desc = args.description.toLowerCase();
1179
+ const framework = args.framework || "react";
1180
+ const archetypeScores = [];
1181
+ const archetypeIds = [
1182
+ "saas-dashboard",
1183
+ "ecommerce",
1184
+ "portfolio",
1185
+ "content-site",
1186
+ "financial-dashboard",
1187
+ "cloud-platform",
1188
+ "gaming-platform",
1189
+ "ecommerce-admin"
1190
+ ];
1191
+ for (const id of archetypeIds) {
1192
+ let score = 0;
1193
+ if (desc.includes("dashboard") && id.includes("dashboard")) score += 20;
1194
+ if (desc.includes("saas") && id.includes("saas")) score += 20;
1195
+ if (desc.includes("ecommerce") && id.includes("ecommerce")) score += 20;
1196
+ if (desc.includes("shop") && id.includes("ecommerce")) score += 15;
1197
+ if (desc.includes("portfolio") && id.includes("portfolio")) score += 20;
1198
+ if (desc.includes("blog") && id.includes("content")) score += 15;
1199
+ if (desc.includes("content") && id.includes("content")) score += 15;
1200
+ if (desc.includes("finance") && id.includes("financial")) score += 20;
1201
+ if (desc.includes("cloud") && id.includes("cloud")) score += 15;
1202
+ if (desc.includes("game") && id.includes("gaming")) score += 15;
1203
+ if (desc.includes("admin") && id.includes("admin")) score += 15;
1204
+ if (desc.includes("analytics") && id.includes("dashboard")) score += 10;
1205
+ if (score > 0) archetypeScores.push({ id, score });
1206
+ }
1207
+ archetypeScores.sort((a, b) => b.score - a.score);
1208
+ const bestMatch = archetypeScores[0]?.id || "saas-dashboard";
1209
+ let pages;
1210
+ let features = [];
1211
+ try {
1212
+ const archetype = await apiClient.getArchetype("@official", bestMatch);
1213
+ pages = archetype.pages;
1214
+ features = archetype.features || [];
1215
+ } catch {
1216
+ }
1217
+ const rawPages = pages || [{ id: "home", shell: "full-bleed", default_layout: ["hero"] }];
1218
+ const defaultShell = rawPages[0]?.shell || "sidebar-main";
1219
+ const essence = {
1220
+ version: "3.0.0",
1221
+ dna: {
1222
+ theme: {
1223
+ id: "auradecantism",
1224
+ mode: "dark",
1225
+ shape: "rounded"
1226
+ },
1227
+ spacing: {
1228
+ base_unit: 4,
1229
+ scale: "linear",
1230
+ density: "comfortable",
1231
+ content_gap: "4"
1232
+ },
1233
+ typography: {
1234
+ scale: "modular",
1235
+ heading_weight: 600,
1236
+ body_weight: 400
1237
+ },
1238
+ color: {
1239
+ palette: "semantic",
1240
+ accent_count: 1,
1241
+ cvd_preference: "auto"
1242
+ },
1243
+ radius: {
1244
+ philosophy: "rounded",
1245
+ base: 8
1246
+ },
1247
+ elevation: {
1248
+ system: "layered",
1249
+ max_levels: 3
1250
+ },
1251
+ motion: {
1252
+ preference: "subtle",
1253
+ duration_scale: 1,
1254
+ reduce_motion: true
1255
+ },
1256
+ accessibility: {
1257
+ wcag_level: "AA",
1258
+ focus_visible: true,
1259
+ skip_nav: true
1260
+ },
1261
+ personality: ["professional"]
1262
+ },
1263
+ blueprint: {
1264
+ shell: defaultShell,
1265
+ pages: rawPages.map((p) => ({
1266
+ id: p.id,
1267
+ ...p.shell !== defaultShell ? { shell_override: p.shell } : {},
1268
+ layout: p.default_layout || []
1269
+ })),
1270
+ features
1271
+ },
1272
+ meta: {
1273
+ archetype: bestMatch,
1274
+ target: framework,
1275
+ platform: { type: "spa", routing: "hash" },
1276
+ guard: { mode: "strict", dna_enforcement: "error", blueprint_enforcement: "warn" }
1277
+ }
1278
+ };
1279
+ return {
1280
+ essence,
1281
+ archetype: bestMatch,
1282
+ format: "v3",
1283
+ instructions: `Save this as decantr.essence.json in your project root. Review the dna (design tokens), blueprint (pages/features), and meta (project config) sections and adjust to match your needs. The guard rules will validate your code against this spec.`,
1284
+ _generated: {
1285
+ matched_archetype: bestMatch,
1286
+ confidence: archetypeScores[0]?.score || 0,
1287
+ alternatives: archetypeScores.slice(1, 4).map((a) => a.id),
1288
+ description: args.description
1289
+ }
1290
+ };
1291
+ }
1292
+ case "decantr_accept_drift": {
1293
+ const violations = args.violations;
1294
+ const resolution = args.resolution;
1295
+ if (!violations || !Array.isArray(violations) || violations.length === 0) {
1296
+ return { error: 'Required parameter "violations" must be a non-empty array.' };
1297
+ }
1298
+ if (!resolution || !["accept", "accept_scoped", "reject", "defer"].includes(resolution)) {
1299
+ return { error: 'Required parameter "resolution" must be one of: accept, accept_scoped, reject, defer.' };
1300
+ }
1301
+ const hasDnaViolation = violations.some((v) => {
1302
+ const rule = v.rule;
1303
+ return ["theme", "style", "density", "theme-mode", "accessibility", "theme-match"].includes(rule);
1304
+ });
1305
+ if (hasDnaViolation && resolution !== "reject" && resolution !== "defer" && !args.confirm_dna) {
1306
+ return {
1307
+ error: "DNA-layer violations detected. Set confirm_dna: true to accept changes to design axioms (theme, density, accessibility, etc.).",
1308
+ requires_confirmation: true,
1309
+ dna_rules_affected: violations.filter(
1310
+ (v) => ["theme", "style", "density", "theme-mode", "accessibility", "theme-match"].includes(v.rule)
1311
+ ).map((v) => v.rule)
1312
+ };
1313
+ }
1314
+ if (resolution === "reject") {
1315
+ return {
1316
+ status: "rejected",
1317
+ message: "Violations rejected. No changes made. Revert the code to match the essence spec.",
1318
+ violations_count: violations.length
1319
+ };
1320
+ }
1321
+ if (resolution === "defer") {
1322
+ const projectRoot = args.path ? dirname2(args.path) : void 0;
1323
+ const existingLog = await readDriftLog(projectRoot);
1324
+ const newEntries = violations.map((v) => ({
1325
+ rule: v.rule,
1326
+ page_id: v.page_id,
1327
+ details: v.details,
1328
+ resolution: "deferred",
1329
+ scope: args.scope || void 0,
1330
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1331
+ }));
1332
+ const updatedLog = [...existingLog, ...newEntries];
1333
+ const logPath = await writeDriftLog(updatedLog, projectRoot);
1334
+ return {
1335
+ status: "deferred",
1336
+ message: `${violations.length} violation(s) deferred to drift log.`,
1337
+ log_path: logPath,
1338
+ total_deferred: updatedLog.length
1339
+ };
1340
+ }
1341
+ try {
1342
+ const { essence, path } = await mutateEssenceFile(args.path, (v3) => {
1343
+ for (const v of violations) {
1344
+ applyDriftAcceptance(v3, v, resolution, args.scope);
1345
+ }
1346
+ return v3;
1347
+ });
1348
+ return {
1349
+ status: resolution === "accept_scoped" ? "accepted_scoped" : "accepted",
1350
+ message: `${violations.length} violation(s) resolved. Essence updated.`,
1351
+ path,
1352
+ scope: resolution === "accept_scoped" ? args.scope || "unscoped" : void 0
1353
+ };
1354
+ } catch (e) {
1355
+ return { error: `Failed to update essence: ${e.message}` };
1356
+ }
1357
+ }
1358
+ case "decantr_update_essence": {
1359
+ const operation = args.operation;
1360
+ const payload = args.payload;
1361
+ if (!operation) {
1362
+ return { error: 'Required parameter "operation" is missing.' };
1363
+ }
1364
+ if (!payload || typeof payload !== "object") {
1365
+ return { error: 'Required parameter "payload" must be an object.' };
1366
+ }
1367
+ const validOps = ["add_page", "remove_page", "update_page_layout", "update_dna", "update_blueprint", "add_feature", "remove_feature"];
1368
+ if (!validOps.includes(operation)) {
1369
+ return { error: `Invalid operation "${operation}". Must be one of: ${validOps.join(", ")}` };
1370
+ }
1371
+ try {
1372
+ const { essence, path } = await mutateEssenceFile(args.path, (v3) => {
1373
+ return applyEssenceUpdate(v3, operation, payload);
1374
+ });
1375
+ return {
1376
+ status: "updated",
1377
+ operation,
1378
+ path,
1379
+ summary: describeUpdate(operation, payload)
1380
+ };
1381
+ } catch (e) {
1382
+ return { error: `Failed to update essence: ${e.message}` };
1383
+ }
1384
+ }
1385
+ case "decantr_get_scaffold_context": {
1386
+ const contextDir = join2(process.cwd(), ".decantr", "context");
1387
+ const manifestPath = join2(contextDir, "pack-manifest.json");
1388
+ const scaffoldContextPath = join2(contextDir, "scaffold.md");
1389
+ const taskContextPath = join2(contextDir, "task-scaffold.md");
1390
+ const packMarkdownPath = join2(contextDir, "scaffold-pack.md");
1391
+ const packJsonPath = join2(contextDir, "scaffold-pack.json");
1392
+ const hasAnyContext = existsSync(scaffoldContextPath) || existsSync(taskContextPath) || existsSync(packMarkdownPath) || existsSync(packJsonPath) || existsSync(manifestPath);
1393
+ if (!hasAnyContext) {
1394
+ const [hostedScaffold, hostedReview] = await Promise.all([
1395
+ loadHostedSelectedExecutionPackFallback({
1396
+ ...args,
1397
+ pack_type: "scaffold"
1398
+ }),
1399
+ loadHostedSelectedExecutionPackFallback({
1400
+ ...args,
1401
+ pack_type: "review"
1402
+ })
1403
+ ]);
1404
+ const scaffoldSelected = hostedScaffold.selected;
1405
+ const reviewSelected = hostedReview.selected;
1406
+ if (scaffoldSelected && reviewSelected) {
1407
+ const scaffoldPayload2 = toHostedExecutionPackPayload(scaffoldSelected.pack);
1408
+ const reviewPayload2 = toHostedExecutionPackPayload(reviewSelected.pack);
1409
+ return {
1410
+ source: "hosted_fallback",
1411
+ task_context: null,
1412
+ scaffold_context: scaffoldPayload2.markdown,
1413
+ execution_pack: scaffoldPayload2,
1414
+ review_pack: reviewPayload2,
1415
+ pack_manifest: scaffoldSelected.manifest,
1416
+ available_sections: scaffoldSelected.manifest.sections.map((section) => ({ id: section.id, page_ids: section.pageIds })),
1417
+ available_pages: scaffoldSelected.manifest.pages.map((page) => ({ id: page.id, section_id: page.sectionId })),
1418
+ available_mutations: (scaffoldSelected.manifest.mutations ?? []).map((mutation) => ({ id: mutation.id, mutation_type: mutation.mutationType })),
1419
+ note: "Using hosted selected execution packs because local scaffold context artifacts were not found; scaffold pack markdown is being reused as readable scaffold context."
1420
+ };
1421
+ }
1422
+ const hosted = await loadHostedExecutionPackBundleFallback(args);
1423
+ if (!hosted.bundle) {
1424
+ return {
1425
+ error: "Scaffold context not found. Run `decantr refresh` or `decantr registry compile-packs --write-context` to materialize scaffold context and execution packs.",
1426
+ hosted_fallback_error: hosted.error ?? hostedScaffold.error ?? hostedReview.error
1427
+ };
1428
+ }
1429
+ const scaffoldPayload = toHostedExecutionPackPayload(hosted.bundle.scaffold);
1430
+ const reviewPayload = toHostedExecutionPackPayload(hosted.bundle.review);
1431
+ return {
1432
+ source: "hosted_fallback",
1433
+ task_context: null,
1434
+ scaffold_context: scaffoldPayload.markdown,
1435
+ execution_pack: scaffoldPayload,
1436
+ review_pack: reviewPayload,
1437
+ pack_manifest: hosted.bundle.manifest,
1438
+ available_sections: hosted.bundle.manifest.sections.map((section) => ({ id: section.id, page_ids: section.pageIds })),
1439
+ available_pages: hosted.bundle.manifest.pages.map((page) => ({ id: page.id, section_id: page.sectionId })),
1440
+ available_mutations: (hosted.bundle.manifest.mutations ?? []).map((mutation) => ({ id: mutation.id, mutation_type: mutation.mutationType })),
1441
+ note: "Using hosted compiled execution packs because local scaffold context artifacts were not found; scaffold pack markdown is being reused as readable scaffold context."
1442
+ };
1443
+ }
1444
+ let manifest = null;
1445
+ if (existsSync(manifestPath)) {
1446
+ try {
1447
+ manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
1448
+ } catch (e) {
1449
+ return { error: `Failed to read pack manifest: ${e.message}` };
1450
+ }
1451
+ }
1452
+ return {
1453
+ source: "local",
1454
+ task_context: existsSync(taskContextPath) ? readFileSync(taskContextPath, "utf-8") : null,
1455
+ scaffold_context: existsSync(scaffoldContextPath) ? readFileSync(scaffoldContextPath, "utf-8") : existsSync(packMarkdownPath) ? readFileSync(packMarkdownPath, "utf-8") : null,
1456
+ execution_pack: {
1457
+ markdown: existsSync(packMarkdownPath) ? readFileSync(packMarkdownPath, "utf-8") : null,
1458
+ json: existsSync(packJsonPath) ? JSON.parse(readFileSync(packJsonPath, "utf-8")) : null
1459
+ },
1460
+ review_pack: {
1461
+ markdown: existsSync(join2(contextDir, "review-pack.md")) ? readFileSync(join2(contextDir, "review-pack.md"), "utf-8") : null,
1462
+ json: existsSync(join2(contextDir, "review-pack.json")) ? JSON.parse(readFileSync(join2(contextDir, "review-pack.json"), "utf-8")) : null
1463
+ },
1464
+ pack_manifest: manifest,
1465
+ available_sections: manifest?.sections.map((section) => ({ id: section.id, page_ids: section.pageIds })) ?? [],
1466
+ available_pages: manifest?.pages.map((page) => ({ id: page.id, section_id: page.sectionId })) ?? [],
1467
+ available_mutations: manifest?.mutations?.map((mutation) => ({ id: mutation.id, mutation_type: mutation.mutationType })) ?? []
1468
+ };
1469
+ }
1470
+ case "decantr_get_section_context": {
1471
+ const err = validateStringArg(args, "section_id");
1472
+ if (err) return { error: err };
1473
+ const sectionId = args.section_id;
1474
+ let essence;
1475
+ try {
1476
+ const result = await readEssenceFile();
1477
+ essence = result.essence;
1478
+ } catch {
1479
+ return { error: "No valid essence file found. Run decantr init first." };
1480
+ }
1481
+ if (!isV32(essence)) {
1482
+ return { error: "Section context requires a v3 essence file. Run decantr migrate first." };
1483
+ }
1484
+ const sections = essence.blueprint.sections || [];
1485
+ const section = sections.find((s) => s.id === sectionId);
1486
+ if (!section) {
1487
+ return {
1488
+ error: `Section "${sectionId}" not found.`,
1489
+ available_sections: sections.map((s) => ({ id: s.id, role: s.role, pages: s.pages.length }))
1490
+ };
1491
+ }
1492
+ const packBasePath = join2(process.cwd(), ".decantr", "context", `section-${sectionId}-pack`);
1493
+ const packMarkdownPath = `${packBasePath}.md`;
1494
+ const packJsonPath = `${packBasePath}.json`;
1495
+ const localExecutionPack = {
1496
+ markdown: existsSync(packMarkdownPath) ? readFileSync(packMarkdownPath, "utf-8") : null,
1497
+ json: existsSync(packJsonPath) ? JSON.parse(readFileSync(packJsonPath, "utf-8")) : null
1498
+ };
1499
+ let executionPack = localExecutionPack;
1500
+ let executionPackSource = hasExecutionPackPayload(localExecutionPack) ? "local" : null;
1501
+ let hostedFallbackError = null;
1502
+ if (!executionPackSource) {
1503
+ const hosted = await loadHostedSelectedExecutionPackFallback({
1504
+ ...args,
1505
+ pack_type: "section",
1506
+ id: sectionId
1507
+ });
1508
+ hostedFallbackError = hosted.error;
1509
+ if (hosted.selected) {
1510
+ executionPack = toHostedExecutionPackPayload(hosted.selected.pack);
1511
+ executionPackSource = "hosted_fallback";
1512
+ }
1513
+ }
1514
+ const contextPath = join2(process.cwd(), ".decantr", "context", `section-${sectionId}.md`);
1515
+ if (existsSync(contextPath)) {
1516
+ return {
1517
+ section_id: sectionId,
1518
+ role: section.role,
1519
+ shell: section.shell,
1520
+ features: section.features,
1521
+ pages: section.pages.map((p) => ({ id: p.id, route: p.route, layout: p.layout })),
1522
+ context: readFileSync(contextPath, "utf-8"),
1523
+ execution_pack_source: executionPackSource,
1524
+ execution_pack: executionPack
1525
+ };
1526
+ }
1527
+ const derivedContext = executionPack.markdown;
1528
+ return {
1529
+ section_id: sectionId,
1530
+ role: section.role,
1531
+ shell: section.shell,
1532
+ features: section.features,
1533
+ description: section.description,
1534
+ pages: section.pages.map((p) => ({ id: p.id, route: p.route, layout: p.layout })),
1535
+ context: derivedContext,
1536
+ execution_pack_source: executionPackSource,
1537
+ execution_pack: executionPack,
1538
+ note: executionPackSource === "hosted_fallback" ? "Section context file not found. Using hosted compiled execution pack fallback as the readable section context." : `Section context file not found. Run \`decantr refresh\` or \`decantr registry get-pack section ${sectionId} --write-context\` to generate it.`,
1539
+ hosted_fallback_error: executionPackSource ? void 0 : hostedFallbackError
1540
+ };
1541
+ }
1542
+ case "decantr_get_page_context": {
1543
+ const err = validateStringArg(args, "page_id");
1544
+ if (err) return { error: err };
1545
+ const pageId = args.page_id;
1546
+ const contextDir = join2(process.cwd(), ".decantr", "context");
1547
+ const manifestPath = join2(contextDir, "pack-manifest.json");
1548
+ let manifest = null;
1549
+ let manifestSource = null;
1550
+ let hostedPageSelection = null;
1551
+ let hostedSectionSelection = null;
1552
+ let hostedFallbackError = null;
1553
+ const loadHostedPageSelection = async () => {
1554
+ if (hostedPageSelection) {
1555
+ return hostedPageSelection;
1556
+ }
1557
+ const hosted = await loadHostedSelectedExecutionPackFallback({
1558
+ ...args,
1559
+ pack_type: "page",
1560
+ id: pageId
1561
+ });
1562
+ hostedFallbackError = hosted.error;
1563
+ hostedPageSelection = hosted.selected;
1564
+ return hostedPageSelection;
1565
+ };
1566
+ const loadHostedSectionSelection = async (sectionId) => {
1567
+ if (hostedSectionSelection && hostedSectionSelection.selector.id === sectionId) {
1568
+ return hostedSectionSelection;
1569
+ }
1570
+ const hosted = await loadHostedSelectedExecutionPackFallback({
1571
+ ...args,
1572
+ pack_type: "section",
1573
+ id: sectionId
1574
+ });
1575
+ hostedFallbackError = hosted.error;
1576
+ hostedSectionSelection = hosted.selected;
1577
+ return hostedSectionSelection;
1578
+ };
1579
+ if (existsSync(manifestPath)) {
1580
+ try {
1581
+ manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
1582
+ manifestSource = "local";
1583
+ } catch (e) {
1584
+ return { error: `Failed to read pack manifest: ${e.message}` };
1585
+ }
1586
+ }
1587
+ if (!manifest) {
1588
+ const hosted = await loadHostedPageSelection();
1589
+ if (!hosted) {
1590
+ return {
1591
+ error: "Execution pack manifest not found. Run `decantr refresh` or `decantr registry get-pack manifest --write-context` to generate compiled packs.",
1592
+ hosted_fallback_error: hostedFallbackError
1593
+ };
1594
+ }
1595
+ manifest = hosted.manifest;
1596
+ manifestSource = "hosted_fallback";
1597
+ }
1598
+ let pageEntry = manifest.pages.find((page) => page.id === pageId) ?? null;
1599
+ if (!pageEntry) {
1600
+ if (manifestSource === "local") {
1601
+ const hosted = await loadHostedPageSelection();
1602
+ if (hosted) {
1603
+ manifest = hosted.manifest;
1604
+ manifestSource = "hosted_fallback";
1605
+ pageEntry = manifest.pages.find((page) => page.id === pageId) ?? null;
1606
+ }
1607
+ }
1608
+ if (!pageEntry) {
1609
+ return {
1610
+ error: `Page "${pageId}" not found in execution pack manifest.`,
1611
+ available_pages: manifest.pages.map((page) => ({ id: page.id, section_id: page.sectionId })),
1612
+ hosted_fallback_error: manifestSource === "hosted_fallback" ? void 0 : hostedFallbackError
1613
+ };
1614
+ }
1615
+ }
1616
+ let resolvedPageEntry = pageEntry;
1617
+ let sectionEntry = resolvedPageEntry.sectionId ? manifest.sections.find((section) => section.id === resolvedPageEntry.sectionId) ?? null : null;
1618
+ const pageMarkdownPath = join2(contextDir, resolvedPageEntry.markdown);
1619
+ const pageJsonPath = join2(contextDir, resolvedPageEntry.json);
1620
+ const sectionMarkdownPath = sectionEntry ? join2(contextDir, sectionEntry.markdown) : null;
1621
+ const sectionJsonPath = sectionEntry ? join2(contextDir, sectionEntry.json) : null;
1622
+ const sectionContextPath = resolvedPageEntry.sectionId ? join2(contextDir, `section-${resolvedPageEntry.sectionId}.md`) : null;
1623
+ const localPagePack = {
1624
+ markdown: existsSync(pageMarkdownPath) ? readFileSync(pageMarkdownPath, "utf-8") : null,
1625
+ json: existsSync(pageJsonPath) ? JSON.parse(readFileSync(pageJsonPath, "utf-8")) : null
1626
+ };
1627
+ let executionPack = localPagePack;
1628
+ let executionPackSource = hasExecutionPackPayload(localPagePack) ? "local" : null;
1629
+ if (!executionPackSource) {
1630
+ const hosted = await loadHostedPageSelection();
1631
+ if (hosted) {
1632
+ manifest = hosted.manifest;
1633
+ manifestSource = "hosted_fallback";
1634
+ resolvedPageEntry = manifest.pages.find((page) => page.id === pageId) ?? resolvedPageEntry;
1635
+ sectionEntry = resolvedPageEntry.sectionId ? manifest.sections.find((section) => section.id === resolvedPageEntry.sectionId) ?? sectionEntry : null;
1636
+ executionPack = toHostedExecutionPackPayload(hosted.pack);
1637
+ executionPackSource = "hosted_fallback";
1638
+ }
1639
+ }
1640
+ const localSectionPack = sectionEntry ? {
1641
+ markdown: sectionMarkdownPath && existsSync(sectionMarkdownPath) ? readFileSync(sectionMarkdownPath, "utf-8") : null,
1642
+ json: sectionJsonPath && existsSync(sectionJsonPath) ? JSON.parse(readFileSync(sectionJsonPath, "utf-8")) : null
1643
+ } : null;
1644
+ let sectionExecutionPack = localSectionPack;
1645
+ let sectionExecutionPackSource = localSectionPack && hasExecutionPackPayload(localSectionPack) ? "local" : null;
1646
+ if (sectionEntry && !sectionExecutionPackSource) {
1647
+ const hosted = await loadHostedSectionSelection(sectionEntry.id);
1648
+ if (hosted) {
1649
+ sectionExecutionPack = toHostedExecutionPackPayload(hosted.pack);
1650
+ sectionExecutionPackSource = "hosted_fallback";
1651
+ }
1652
+ }
1653
+ return {
1654
+ page_id: pageId,
1655
+ page_context: executionPack.markdown,
1656
+ section_id: resolvedPageEntry.sectionId,
1657
+ section_role: resolvedPageEntry.sectionRole,
1658
+ manifest_source: manifestSource,
1659
+ execution_pack_source: executionPackSource,
1660
+ section_execution_pack_source: sectionExecutionPackSource,
1661
+ execution_pack: executionPack,
1662
+ section_execution_pack: sectionExecutionPack,
1663
+ section_context: sectionContextPath && existsSync(sectionContextPath) ? readFileSync(sectionContextPath, "utf-8") : sectionExecutionPack?.markdown ?? null,
1664
+ manifest: {
1665
+ page: resolvedPageEntry,
1666
+ section: sectionEntry
1667
+ },
1668
+ note: manifestSource === "hosted_fallback" ? "Using hosted compiled execution-pack data because local page pack artifacts were missing or incomplete." : void 0,
1669
+ hosted_fallback_error: hostedFallbackError ?? void 0
1670
+ };
1671
+ }
1672
+ case "decantr_get_execution_pack": {
1673
+ const contextDir = join2(process.cwd(), ".decantr", "context");
1674
+ const manifestPath = join2(contextDir, "pack-manifest.json");
1675
+ let manifest = null;
1676
+ let manifestSource = null;
1677
+ let hostedBundle = null;
1678
+ let hostedSelectedPack = null;
1679
+ let hostedFallbackError = null;
1680
+ const packType = args.pack_type ?? "manifest";
1681
+ if (existsSync(manifestPath)) {
1682
+ try {
1683
+ manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
1684
+ manifestSource = "local";
1685
+ } catch (e) {
1686
+ return { error: `Failed to read pack manifest: ${e.message}` };
1687
+ }
1688
+ }
1689
+ if (!manifest && packType === "manifest") {
1690
+ const hostedManifest = await loadHostedExecutionPackManifestFallback(args);
1691
+ hostedFallbackError = hostedManifest.error;
1692
+ if (hostedManifest.manifest) {
1693
+ manifest = hostedManifest.manifest;
1694
+ manifestSource = "hosted_fallback";
1695
+ } else {
1696
+ const hosted = await loadHostedExecutionPackBundleFallback(args);
1697
+ hostedBundle = hosted.bundle;
1698
+ hostedFallbackError = hosted.error;
1699
+ if (!hosted.bundle) {
1700
+ return {
1701
+ error: "Execution pack manifest not found. Run `decantr refresh` or `decantr registry get-pack manifest --write-context` to generate compiled packs.",
1702
+ hosted_fallback_error: hosted.error
1703
+ };
1704
+ }
1705
+ manifest = hosted.bundle.manifest;
1706
+ manifestSource = "hosted_fallback";
1707
+ }
1708
+ }
1709
+ if (packType === "manifest") {
1710
+ return {
1711
+ ...manifest,
1712
+ source: manifestSource
1713
+ };
1714
+ }
1715
+ if (!manifest) {
1716
+ const hosted = await loadHostedSelectedExecutionPackFallback(args);
1717
+ hostedSelectedPack = hosted.selected;
1718
+ hostedFallbackError = hosted.error;
1719
+ if (!hosted.selected) {
1720
+ return {
1721
+ error: "Execution pack manifest not found. Run `decantr refresh` or `decantr registry get-pack manifest --write-context` to generate compiled packs.",
1722
+ hosted_fallback_error: hosted.error
1723
+ };
1724
+ }
1725
+ manifest = hosted.selected.manifest;
1726
+ manifestSource = "hosted_fallback";
1727
+ }
1728
+ const format = args.format ?? "both";
1729
+ let entry = null;
1730
+ let availableIds = [];
1731
+ if (packType === "scaffold") {
1732
+ entry = manifest.scaffold;
1733
+ } else if (packType === "review") {
1734
+ entry = manifest.review ?? null;
1735
+ } else if (packType === "mutation") {
1736
+ availableIds = (manifest.mutations ?? []).map((mutation) => mutation.id);
1737
+ const idErr = validateStringArg(args, "id");
1738
+ if (idErr) return { error: idErr, available_ids: availableIds };
1739
+ entry = (manifest.mutations ?? []).find((mutation) => mutation.id === args.id) ?? null;
1740
+ } else if (packType === "section") {
1741
+ availableIds = manifest.sections.map((section) => section.id);
1742
+ const idErr = validateStringArg(args, "id");
1743
+ if (idErr) return { error: idErr, available_ids: availableIds };
1744
+ entry = manifest.sections.find((section) => section.id === args.id) ?? null;
1745
+ } else if (packType === "page") {
1746
+ availableIds = manifest.pages.map((page) => page.id);
1747
+ const idErr = validateStringArg(args, "id");
1748
+ if (idErr) return { error: idErr, available_ids: availableIds };
1749
+ entry = manifest.pages.find((page) => page.id === args.id) ?? null;
1750
+ } else {
1751
+ return { error: `Unsupported pack type: ${packType}` };
1752
+ }
1753
+ if (!entry) {
1754
+ if (manifestSource === "local") {
1755
+ const hosted = await loadHostedSelectedExecutionPackFallback(args);
1756
+ hostedSelectedPack = hosted.selected;
1757
+ hostedFallbackError = hosted.error;
1758
+ if (hosted.selected) {
1759
+ manifest = hosted.selected.manifest;
1760
+ manifestSource = "hosted_fallback";
1761
+ entry = findManifestEntryForPack(
1762
+ manifest,
1763
+ packType,
1764
+ args.id
1765
+ );
1766
+ }
1767
+ }
1768
+ if (!entry) {
1769
+ return {
1770
+ error: `Execution pack not found for type "${packType}"${args.id ? ` and id "${args.id}"` : ""}.`,
1771
+ available_ids: availableIds,
1772
+ hosted_fallback_error: hostedFallbackError ?? void 0
1773
+ };
1774
+ }
1775
+ }
1776
+ const result = {
1777
+ pack_type: packType,
1778
+ id: entry.id,
1779
+ manifest: entry,
1780
+ source: manifestSource
1781
+ };
1782
+ const localPayload = {
1783
+ markdown: null,
1784
+ json: null
1785
+ };
1786
+ if (manifestSource === "local") {
1787
+ if (format === "markdown" || format === "both") {
1788
+ const markdownPath = join2(contextDir, entry.markdown);
1789
+ if (existsSync(markdownPath)) {
1790
+ localPayload.markdown = readFileSync(markdownPath, "utf-8");
1791
+ }
1792
+ }
1793
+ if (format === "json" || format === "both") {
1794
+ const jsonPath = join2(contextDir, entry.json);
1795
+ if (existsSync(jsonPath)) {
1796
+ localPayload.json = JSON.parse(readFileSync(jsonPath, "utf-8"));
1797
+ }
1798
+ }
1799
+ }
1800
+ if (hasExecutionPackPayload(localPayload)) {
1801
+ if (format === "markdown" || format === "both") {
1802
+ result.markdown = localPayload.markdown;
1803
+ }
1804
+ if (format === "json" || format === "both") {
1805
+ result.json = localPayload.json;
1806
+ }
1807
+ return result;
1808
+ }
1809
+ if (!hostedSelectedPack) {
1810
+ const hosted = await loadHostedSelectedExecutionPackFallback(args);
1811
+ hostedSelectedPack = hosted.selected;
1812
+ hostedFallbackError = hosted.error;
1813
+ }
1814
+ if (!hostedSelectedPack) {
1815
+ return {
1816
+ ...result,
1817
+ hosted_fallback_error: hostedFallbackError ?? void 0
1818
+ };
1819
+ }
1820
+ manifest = hostedSelectedPack.manifest;
1821
+ manifestSource = "hosted_fallback";
1822
+ const hostedPayload = toHostedExecutionPackPayload(hostedSelectedPack.pack);
1823
+ if (format === "markdown" || format === "both") {
1824
+ result.markdown = hostedPayload.markdown;
1825
+ }
1826
+ if (format === "json" || format === "both") {
1827
+ result.json = hostedPayload.json;
1828
+ }
1829
+ return result;
1830
+ }
1831
+ case "decantr_get_showcase_benchmarks": {
1832
+ const view = args.view ?? "shortlist";
1833
+ if (!["manifest", "shortlist", "verification"].includes(view)) {
1834
+ return { error: `Unsupported showcase benchmark view: ${view}` };
1835
+ }
1836
+ return getShowcaseBenchmarkPayload(view);
1837
+ }
1838
+ case "decantr_get_registry_intelligence_summary": {
1839
+ if (args.namespace != null && typeof args.namespace !== "string") {
1840
+ return { error: "Invalid namespace. Must be a string when provided." };
1841
+ }
1842
+ return getRegistryIntelligenceSummaryPayload(args.namespace);
1843
+ }
1844
+ case "decantr_compile_execution_packs": {
1845
+ if (args.path != null && typeof args.path !== "string") {
1846
+ return { error: "Invalid path. Must be a string when provided." };
1847
+ }
1848
+ if (args.namespace != null && typeof args.namespace !== "string") {
1849
+ return { error: "Invalid namespace. Must be a string when provided." };
1850
+ }
1851
+ if (args.essence != null && (typeof args.essence !== "object" || Array.isArray(args.essence))) {
1852
+ return { error: "Invalid essence. Must be an object when provided." };
1853
+ }
1854
+ return getHostedExecutionPackBundlePayload(args);
1855
+ }
1856
+ case "decantr_critique": {
1857
+ const err = validateStringArg(args, "file_path");
1858
+ if (err) return { error: err };
1859
+ if (args.path != null && typeof args.path !== "string") {
1860
+ return { error: "Invalid path. Must be a string when provided." };
1861
+ }
1862
+ if (args.namespace != null && typeof args.namespace !== "string") {
1863
+ return { error: "Invalid namespace. Must be a string when provided." };
1864
+ }
1865
+ if (args.treatments_path != null && typeof args.treatments_path !== "string") {
1866
+ return { error: "Invalid treatments_path. Must be a string when provided." };
1867
+ }
1868
+ const { critiqueFile } = await import("./critique-VEROHUF4.js");
1869
+ const localReviewPackPath = join2(process.cwd(), ".decantr", "context", "review-pack.json");
1870
+ if (existsSync(localReviewPackPath)) {
1871
+ return critiqueFile(args.file_path, process.cwd());
1872
+ }
1873
+ const hosted = await loadHostedFileCritiqueFallback(args);
1874
+ if (hosted.report) {
1875
+ return hosted.report;
1876
+ }
1877
+ return critiqueFile(args.file_path, process.cwd());
1878
+ }
1879
+ case "decantr_audit_project": {
1880
+ if (args.path != null && typeof args.path !== "string") {
1881
+ return { error: "Invalid path. Must be a string when provided." };
1882
+ }
1883
+ if (args.namespace != null && typeof args.namespace !== "string") {
1884
+ return { error: "Invalid namespace. Must be a string when provided." };
1885
+ }
1886
+ if (args.dist_path != null && typeof args.dist_path !== "string") {
1887
+ return { error: "Invalid dist_path. Must be a string when provided." };
1888
+ }
1889
+ if (args.sources_path != null && typeof args.sources_path !== "string") {
1890
+ return { error: "Invalid sources_path. Must be a string when provided." };
1891
+ }
1892
+ const { auditProject } = await import("@decantr/verifier");
1893
+ const projectRoot = process.cwd();
1894
+ const hasReviewPack = existsSync(join2(projectRoot, ".decantr", "context", "review-pack.json"));
1895
+ const hasPackManifest = existsSync(join2(projectRoot, ".decantr", "context", "pack-manifest.json"));
1896
+ if (hasReviewPack && hasPackManifest) {
1897
+ return auditProject(projectRoot);
1898
+ }
1899
+ const hosted = await loadHostedProjectAuditFallback(args);
1900
+ if (hosted.report) {
1901
+ return hosted.report;
1902
+ }
1903
+ return auditProject(projectRoot);
1904
+ }
1905
+ default:
1906
+ return { error: `Unknown tool: ${name}` };
1907
+ }
1908
+ }
1909
+ function applyDriftAcceptance(essence, violation, resolution, scope) {
1910
+ switch (violation.rule) {
1911
+ case "theme-match":
1912
+ case "theme":
1913
+ case "style": {
1914
+ if (violation.details) {
1915
+ essence.dna.theme.id = violation.details;
1916
+ }
1917
+ break;
1918
+ }
1919
+ case "page-exists":
1920
+ case "structure": {
1921
+ if (violation.page_id) {
1922
+ const existing = essence.blueprint.pages.find((p) => p.id === violation.page_id);
1923
+ if (!existing) {
1924
+ essence.blueprint.pages.push({
1925
+ id: violation.page_id,
1926
+ layout: []
1927
+ });
1928
+ }
1929
+ }
1930
+ break;
1931
+ }
1932
+ case "layout": {
1933
+ break;
1934
+ }
1935
+ case "density": {
1936
+ break;
1937
+ }
1938
+ default:
1939
+ break;
1940
+ }
1941
+ }
1942
+ function applyEssenceUpdate(essence, operation, payload) {
1943
+ switch (operation) {
1944
+ case "add_page": {
1945
+ const id = payload.id;
1946
+ if (!id) throw new Error('Payload must include "id" for add_page.');
1947
+ const existing = essence.blueprint.pages.find((p) => p.id === id);
1948
+ if (existing) throw new Error(`Page "${id}" already exists.`);
1949
+ essence.blueprint.pages.push({
1950
+ id,
1951
+ layout: payload.layout || [],
1952
+ ...payload.shell_override ? { shell_override: payload.shell_override } : {},
1953
+ ...payload.surface ? { surface: payload.surface } : {}
1954
+ });
1955
+ break;
1956
+ }
1957
+ case "remove_page": {
1958
+ const id = payload.id;
1959
+ if (!id) throw new Error('Payload must include "id" for remove_page.');
1960
+ const idx = essence.blueprint.pages.findIndex((p) => p.id === id);
1961
+ if (idx === -1) throw new Error(`Page "${id}" not found.`);
1962
+ essence.blueprint.pages.splice(idx, 1);
1963
+ break;
1964
+ }
1965
+ case "update_page_layout": {
1966
+ const id = payload.id;
1967
+ const layout = payload.layout;
1968
+ if (!id) throw new Error('Payload must include "id" for update_page_layout.');
1969
+ if (!layout || !Array.isArray(layout)) throw new Error('Payload must include "layout" array for update_page_layout.');
1970
+ const page = essence.blueprint.pages.find((p) => p.id === id);
1971
+ if (!page) throw new Error(`Page "${id}" not found.`);
1972
+ page.layout = layout;
1973
+ break;
1974
+ }
1975
+ case "update_dna": {
1976
+ for (const [key, value] of Object.entries(payload)) {
1977
+ if (key in essence.dna && typeof value === "object" && value !== null && !Array.isArray(value)) {
1978
+ essence.dna[key] = {
1979
+ ...essence.dna[key],
1980
+ ...value
1981
+ };
1982
+ } else {
1983
+ essence.dna[key] = value;
1984
+ }
1985
+ }
1986
+ break;
1987
+ }
1988
+ case "update_blueprint": {
1989
+ for (const [key, value] of Object.entries(payload)) {
1990
+ if (key === "pages") continue;
1991
+ essence.blueprint[key] = value;
1992
+ }
1993
+ break;
1994
+ }
1995
+ case "add_feature": {
1996
+ const feature = payload.feature;
1997
+ if (!feature) throw new Error('Payload must include "feature" for add_feature.');
1998
+ if (!essence.blueprint.features.includes(feature)) {
1999
+ essence.blueprint.features.push(feature);
2000
+ }
2001
+ break;
2002
+ }
2003
+ case "remove_feature": {
2004
+ const feature = payload.feature;
2005
+ if (!feature) throw new Error('Payload must include "feature" for remove_feature.');
2006
+ const idx = essence.blueprint.features.indexOf(feature);
2007
+ if (idx === -1) throw new Error(`Feature "${feature}" not found.`);
2008
+ essence.blueprint.features.splice(idx, 1);
2009
+ break;
2010
+ }
2011
+ }
2012
+ return essence;
2013
+ }
2014
+ function describeUpdate(operation, payload) {
2015
+ switch (operation) {
2016
+ case "add_page":
2017
+ return `Added page "${payload.id}".`;
2018
+ case "remove_page":
2019
+ return `Removed page "${payload.id}".`;
2020
+ case "update_page_layout":
2021
+ return `Updated layout for page "${payload.id}".`;
2022
+ case "update_dna":
2023
+ return `Updated DNA: ${Object.keys(payload).join(", ")}.`;
2024
+ case "update_blueprint":
2025
+ return `Updated blueprint: ${Object.keys(payload).join(", ")}.`;
2026
+ case "add_feature":
2027
+ return `Added feature "${payload.feature}".`;
2028
+ case "remove_feature":
2029
+ return `Removed feature "${payload.feature}".`;
2030
+ default:
2031
+ return `Performed ${operation}.`;
2032
+ }
2033
+ }
2034
+
2035
+ // src/index.ts
2036
+ var VERSION = "0.2.0";
2037
+ var server = new Server(
2038
+ { name: "decantr", version: VERSION },
2039
+ { capabilities: { tools: {} } }
2040
+ );
2041
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
2042
+ return { tools: TOOLS };
2043
+ });
2044
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2045
+ const { name, arguments: args } = request.params;
2046
+ try {
2047
+ const result = await handleTool(name, args ?? {});
2048
+ const response = {
2049
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
2050
+ };
2051
+ if (result && typeof result === "object" && "error" in result) {
2052
+ response.isError = true;
2053
+ }
2054
+ return response;
2055
+ } catch (err) {
2056
+ return {
2057
+ content: [{ type: "text", text: JSON.stringify({ error: err.message }) }],
2058
+ isError: true
2059
+ };
2060
+ }
2061
+ });
2062
+ var transport = new StdioServerTransport();
2063
+ await server.connect(transport);