@decantr/mcp-server 1.0.6 → 2.1.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/README.md +9 -2
- package/dist/bin.js +1 -1
- package/dist/{chunk-A4ZCCVQR.js → chunk-FEXPLJKB.js} +602 -79
- package/dist/index.js +1 -1
- package/package.json +7 -4
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@ Design intelligence for AI-generated UI. Make Claude, Cursor, and Windsurf gener
|
|
|
13
13
|

|
|
14
14
|
|
|
15
15
|
- **Structured design context** -- gives your AI assistant patterns, layouts, and component specs instead of letting it guess
|
|
16
|
+
- **Evidence-backed repair loops** -- gives AI agents Project Health, Evidence Bundles, workspace health, and scoped repair prompts without uploading source
|
|
16
17
|
- **Drift detection** -- catches when generated code deviates from your design intent
|
|
17
18
|
- **Zero config** -- run with `npx`, no API keys or accounts required
|
|
18
19
|
|
|
@@ -138,13 +139,17 @@ The server exposes Decantr registry, context, benchmark, and verification tools.
|
|
|
138
139
|
| `decantr_compile_execution_packs` | Compile a hosted execution-pack bundle from a local or inline essence document | `{ "path": "./decantr.essence.json", "namespace": "@official" }` |
|
|
139
140
|
| `decantr_audit_project` | Run the schema-backed Decantr project audit against essence and compiled packs, with hosted fallback when local pack artifacts are missing | `{ "namespace": "@official" }` |
|
|
140
141
|
| `decantr_critique` | Critique a file against the compiled review contract, with hosted fallback when local review packs are missing | `{ "file_path": "./src/pages/Overview.tsx", "namespace": "@official" }` |
|
|
142
|
+
| `decantr_get_evidence_bundle` | Generate the local privacy-redacted Evidence Bundle for a project | `{ "project_path": "apps/web" }` |
|
|
143
|
+
| `decantr_workspace_health` | Discover Decantr projects and return aggregate workspace health | `{ "workspace_root": ".", "max_projects": 100 }` |
|
|
144
|
+
| `decantr_get_repair_prompt` | Return the scoped repair prompt for a health finding | `{ "finding_id": "assertion-contract-context-pack-manifest" }` |
|
|
145
|
+
| `decantr_run_health_loop` | Run health, evidence, and next repair prompt in one local agent loop | `{ "project_path": "apps/web" }` |
|
|
141
146
|
| `decantr_get_showcase_benchmarks` | Read the audited showcase corpus manifest, shortlist, or verification report | `{ "view": "verification" }` |
|
|
142
147
|
|
|
143
148
|
For the broader product surface and support policy, see the root Decantr docs and package support matrix.
|
|
144
149
|
|
|
145
150
|
## Compatibility
|
|
146
151
|
|
|
147
|
-
`@decantr/mcp-server` is stable in the `
|
|
152
|
+
`@decantr/mcp-server` is stable in the `2.x` line for the documented MCP tool surface.
|
|
148
153
|
|
|
149
154
|
- new tools may be added in compatible releases
|
|
150
155
|
- existing documented tool names and envelopes should not break without a major version
|
|
@@ -169,8 +174,10 @@ The AI assistant calls these tools behind the scenes:
|
|
|
169
174
|
7. `decantr_check_drift` -- validates the generated code against the Essence spec before presenting it
|
|
170
175
|
8. `decantr_critique` -- critiques a specific file, falling back to the hosted verifier when the local review pack is missing
|
|
171
176
|
9. `decantr_audit_project` -- runs the stronger project-level audit once the implementation is in place
|
|
177
|
+
10. `decantr_get_evidence_bundle` -- returns the local evidence bundle for the AI repair loop
|
|
178
|
+
11. `decantr_get_repair_prompt` -- gives the assistant exact finding evidence, constraints to preserve, and commands to rerun
|
|
172
179
|
|
|
173
|
-
The AI now generates code with the right layout structure, correct components, and consistent styling
|
|
180
|
+
The AI now generates code with the right layout structure, correct components, and consistent styling, then gets a scoped evidence-backed repair loop instead of a generic guess.
|
|
174
181
|
|
|
175
182
|
## License
|
|
176
183
|
|
package/dist/bin.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "./chunk-
|
|
2
|
+
import "./chunk-FEXPLJKB.js";
|
|
@@ -7,14 +7,14 @@ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprot
|
|
|
7
7
|
import { existsSync, readdirSync, readFileSync } from "fs";
|
|
8
8
|
import { readFile as readFile2 } from "fs/promises";
|
|
9
9
|
import { basename as basename2, dirname as dirname2, join as join2, relative as relative2 } from "path";
|
|
10
|
-
import { evaluateGuard,
|
|
10
|
+
import { evaluateGuard, isV4 as isV42, validateEssence } from "@decantr/essence-spec";
|
|
11
11
|
import { isContentIntelligenceSource, resolvePatternPreset } from "@decantr/registry";
|
|
12
12
|
|
|
13
13
|
// src/helpers.ts
|
|
14
14
|
import { realpathSync } from "fs";
|
|
15
15
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
16
16
|
import { basename, dirname, isAbsolute, join, relative, resolve } from "path";
|
|
17
|
-
import {
|
|
17
|
+
import { isV4 } from "@decantr/essence-spec";
|
|
18
18
|
import { RegistryAPIClient } from "@decantr/registry";
|
|
19
19
|
var MAX_INPUT_LENGTH = 1e3;
|
|
20
20
|
function validateStringArg(args, field) {
|
|
@@ -95,8 +95,13 @@ async function writeEssenceFile(essencePath, essence) {
|
|
|
95
95
|
}
|
|
96
96
|
async function mutateEssenceFile(essencePath, mutate) {
|
|
97
97
|
const { essence, path } = await readEssenceFile(essencePath);
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
if (!isV4(essence)) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
"Active Decantr V2 workflows require Essence v4.0.0. Run `decantr migrate --to v4` for older essence files."
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
const v4 = structuredClone(essence);
|
|
104
|
+
const updated = mutate(v4);
|
|
100
105
|
await writeEssenceFile(path, updated);
|
|
101
106
|
return { essence: updated, path };
|
|
102
107
|
}
|
|
@@ -464,12 +469,332 @@ var WRITE_TOOL = {
|
|
|
464
469
|
idempotentHint: false,
|
|
465
470
|
openWorldHint: false
|
|
466
471
|
};
|
|
472
|
+
var MCP_PROJECT_HEALTH_SCHEMA_URL = "https://decantr.ai/schemas/project-health-report.v1.json";
|
|
473
|
+
var MCP_WORKSPACE_HEALTH_SCHEMA_URL = "https://decantr.ai/schemas/workspace-health-report.v1.json";
|
|
474
|
+
var MCP_WORKSPACE_IGNORES = /* @__PURE__ */ new Set([
|
|
475
|
+
".git",
|
|
476
|
+
".next",
|
|
477
|
+
".turbo",
|
|
478
|
+
".vercel",
|
|
479
|
+
"coverage",
|
|
480
|
+
"dist",
|
|
481
|
+
"node_modules",
|
|
482
|
+
"playwright-report"
|
|
483
|
+
]);
|
|
484
|
+
function mcpSlug(value) {
|
|
485
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
|
|
486
|
+
}
|
|
487
|
+
function mcpStatusFromCounts(counts) {
|
|
488
|
+
if (counts.errorCount > 0) return "error";
|
|
489
|
+
if (counts.warnCount > 0) return "warning";
|
|
490
|
+
return "healthy";
|
|
491
|
+
}
|
|
492
|
+
function mcpScoreFromCounts(counts) {
|
|
493
|
+
return Math.max(0, Math.min(100, 100 - counts.errorCount * 15 - counts.warnCount * 5 - counts.infoCount));
|
|
494
|
+
}
|
|
495
|
+
function mcpCommandsForFinding(source) {
|
|
496
|
+
switch (source) {
|
|
497
|
+
case "assertion":
|
|
498
|
+
return ["decantr refresh", "decantr health --evidence"];
|
|
499
|
+
case "brownfield":
|
|
500
|
+
return ["decantr analyze", "decantr init --existing --merge-proposal", "decantr health"];
|
|
501
|
+
case "browser":
|
|
502
|
+
return ["decantr health --browser", "decantr health --evidence"];
|
|
503
|
+
case "check":
|
|
504
|
+
return ["decantr check", "decantr health"];
|
|
505
|
+
case "design-token":
|
|
506
|
+
return ["decantr export --to figma-tokens", "decantr health --evidence"];
|
|
507
|
+
case "interaction":
|
|
508
|
+
return ["decantr check --strict", "decantr health"];
|
|
509
|
+
case "pack":
|
|
510
|
+
return ["decantr refresh", "decantr registry get-pack review --write-context", "decantr health"];
|
|
511
|
+
case "runtime":
|
|
512
|
+
return ["npm run build", "decantr health"];
|
|
513
|
+
default:
|
|
514
|
+
return ["decantr audit", "decantr health"];
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
function mcpSourceFromFinding(finding) {
|
|
518
|
+
const category = finding.category.toLowerCase();
|
|
519
|
+
const id = finding.id.toLowerCase();
|
|
520
|
+
const rule = finding.rule?.toLowerCase() ?? "";
|
|
521
|
+
if (category.includes("runtime") || category.includes("document") || category.includes("performance")) {
|
|
522
|
+
return "runtime";
|
|
523
|
+
}
|
|
524
|
+
if (category.includes("pack") || category.includes("review contract")) {
|
|
525
|
+
return "pack";
|
|
526
|
+
}
|
|
527
|
+
if (category.includes("interaction") || id.includes("interaction") || rule.includes("interaction")) {
|
|
528
|
+
return "interaction";
|
|
529
|
+
}
|
|
530
|
+
return "audit";
|
|
531
|
+
}
|
|
532
|
+
function mcpBuildRepairPrompt(input) {
|
|
533
|
+
return [
|
|
534
|
+
"You are fixing one Decantr Project Health finding in this local workspace.",
|
|
535
|
+
"",
|
|
536
|
+
"Read `DECANTR.md`, `decantr.essence.json`, and `.decantr/context/scaffold-pack.md` if they exist. For route or page work, read the matching page/section packs before editing.",
|
|
537
|
+
"",
|
|
538
|
+
`Finding: ${input.id}`,
|
|
539
|
+
`Source: ${input.source}`,
|
|
540
|
+
`Severity: ${input.severity}`,
|
|
541
|
+
`Category: ${input.category}`,
|
|
542
|
+
`Message: ${input.message}`,
|
|
543
|
+
input.evidence.length > 0 ? `Evidence:
|
|
544
|
+
${input.evidence.map((entry) => `- ${entry}`).join("\n")}` : null,
|
|
545
|
+
input.suggestedFix ? `Suggested fix: ${input.suggestedFix}` : null,
|
|
546
|
+
"",
|
|
547
|
+
"Make the smallest coherent code or contract change that resolves this finding. Preserve the existing framework, routing, styling system, and Decantr workflow mode unless the finding explicitly requires a contract update.",
|
|
548
|
+
"Do not rewrite unrelated routes, replace the styling system, remove existing product behavior, or regenerate Decantr artifacts unless the finding is about stale or missing generated context.",
|
|
549
|
+
"",
|
|
550
|
+
`After the fix, run:
|
|
551
|
+
${input.commands.map((command) => `- ${command}`).join("\n")}`
|
|
552
|
+
].filter((line) => Boolean(line)).join("\n");
|
|
553
|
+
}
|
|
554
|
+
function mcpHealthFinding(input) {
|
|
555
|
+
const id = `${input.source}-${mcpSlug(input.baseId || input.rule || `${input.category}-${input.message}`)}`;
|
|
556
|
+
const commands = mcpCommandsForFinding(input.source);
|
|
557
|
+
return {
|
|
558
|
+
id,
|
|
559
|
+
source: input.source,
|
|
560
|
+
category: input.category,
|
|
561
|
+
severity: input.severity,
|
|
562
|
+
message: input.message,
|
|
563
|
+
evidence: input.evidence ?? [],
|
|
564
|
+
target: input.target,
|
|
565
|
+
file: input.file,
|
|
566
|
+
rule: input.rule,
|
|
567
|
+
suggestedFix: input.suggestedFix,
|
|
568
|
+
remediation: {
|
|
569
|
+
summary: input.suggestedFix || `Resolve ${input.category.toLowerCase()} finding.`,
|
|
570
|
+
commands,
|
|
571
|
+
prompt: mcpBuildRepairPrompt({
|
|
572
|
+
id,
|
|
573
|
+
source: input.source,
|
|
574
|
+
category: input.category,
|
|
575
|
+
severity: input.severity,
|
|
576
|
+
message: input.message,
|
|
577
|
+
evidence: input.evidence ?? [],
|
|
578
|
+
suggestedFix: input.suggestedFix,
|
|
579
|
+
commands
|
|
580
|
+
})
|
|
581
|
+
}
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
function mcpCollectDeclaredRoutes(essence) {
|
|
585
|
+
if (!essence || !isV42(essence)) return [];
|
|
586
|
+
return Object.keys(essence.blueprint.routes ?? {}).sort();
|
|
587
|
+
}
|
|
588
|
+
function mcpReportFromAudit(projectRoot, audit, assertions) {
|
|
589
|
+
const findings = [];
|
|
590
|
+
const seen = /* @__PURE__ */ new Set();
|
|
591
|
+
const pushUnique = (finding) => {
|
|
592
|
+
const key = `${finding.rule ?? finding.id}|${finding.message}`;
|
|
593
|
+
if (seen.has(key)) return;
|
|
594
|
+
seen.add(key);
|
|
595
|
+
findings.push(finding);
|
|
596
|
+
};
|
|
597
|
+
for (const finding of audit.findings) {
|
|
598
|
+
pushUnique(
|
|
599
|
+
mcpHealthFinding({
|
|
600
|
+
source: mcpSourceFromFinding(finding),
|
|
601
|
+
category: finding.category,
|
|
602
|
+
severity: finding.severity,
|
|
603
|
+
message: finding.message,
|
|
604
|
+
evidence: finding.evidence,
|
|
605
|
+
target: finding.target,
|
|
606
|
+
file: finding.file,
|
|
607
|
+
rule: finding.rule,
|
|
608
|
+
suggestedFix: finding.suggestedFix,
|
|
609
|
+
baseId: finding.id
|
|
610
|
+
})
|
|
611
|
+
);
|
|
612
|
+
}
|
|
613
|
+
for (const assertion of assertions) {
|
|
614
|
+
if (assertion.status !== "failed") continue;
|
|
615
|
+
pushUnique(
|
|
616
|
+
mcpHealthFinding({
|
|
617
|
+
source: "assertion",
|
|
618
|
+
category: `Contract ${assertion.category}`,
|
|
619
|
+
severity: assertion.severity,
|
|
620
|
+
message: assertion.message,
|
|
621
|
+
evidence: assertion.evidence,
|
|
622
|
+
target: assertion.target,
|
|
623
|
+
rule: assertion.rule,
|
|
624
|
+
suggestedFix: assertion.suggestedFix,
|
|
625
|
+
baseId: assertion.id
|
|
626
|
+
})
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
if (!audit.valid && findings.every((finding) => finding.severity !== "error")) {
|
|
630
|
+
pushUnique(
|
|
631
|
+
mcpHealthFinding({
|
|
632
|
+
source: "audit",
|
|
633
|
+
category: "Project Contract",
|
|
634
|
+
severity: "error",
|
|
635
|
+
message: "Project audit is not valid.",
|
|
636
|
+
evidence: ["The verifier returned valid=false."],
|
|
637
|
+
rule: "project-audit-invalid",
|
|
638
|
+
suggestedFix: "Resolve blocking audit findings and rerun `decantr health`."
|
|
639
|
+
})
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
const counts = {
|
|
643
|
+
errorCount: findings.filter((finding) => finding.severity === "error").length,
|
|
644
|
+
warnCount: findings.filter((finding) => finding.severity === "warn").length,
|
|
645
|
+
infoCount: findings.filter((finding) => finding.severity === "info").length
|
|
646
|
+
};
|
|
647
|
+
const manifest = audit.packManifest;
|
|
648
|
+
return {
|
|
649
|
+
$schema: MCP_PROJECT_HEALTH_SCHEMA_URL,
|
|
650
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
651
|
+
projectRoot,
|
|
652
|
+
status: mcpStatusFromCounts(counts),
|
|
653
|
+
score: mcpScoreFromCounts(counts),
|
|
654
|
+
summary: {
|
|
655
|
+
...counts,
|
|
656
|
+
findingCount: findings.length,
|
|
657
|
+
workflowMode: null,
|
|
658
|
+
adoptionMode: null,
|
|
659
|
+
essenceVersion: audit.summary.essenceVersion,
|
|
660
|
+
pageCount: audit.summary.pageCount,
|
|
661
|
+
runtimeAuditChecked: audit.summary.runtimeAuditChecked,
|
|
662
|
+
runtimePassed: audit.summary.runtimePassed,
|
|
663
|
+
packManifestPresent: audit.summary.packManifestPresent,
|
|
664
|
+
reviewPackPresent: audit.summary.reviewPackPresent
|
|
665
|
+
},
|
|
666
|
+
routes: {
|
|
667
|
+
declared: mcpCollectDeclaredRoutes(audit.essence),
|
|
668
|
+
runtimeChecked: audit.runtimeAudit.routeHintsChecked,
|
|
669
|
+
runtimeMatched: audit.runtimeAudit.routeHintsMatched,
|
|
670
|
+
runtimeCoverageOk: audit.summary.runtimeAuditChecked ? audit.runtimeAudit.routeHintsCoverageOk : null,
|
|
671
|
+
issues: findings.filter(
|
|
672
|
+
(finding) => finding.category.toLowerCase().includes("route") || finding.rule?.toLowerCase().includes("route") || finding.id.toLowerCase().includes("route")
|
|
673
|
+
).map((finding) => finding.message)
|
|
674
|
+
},
|
|
675
|
+
packs: {
|
|
676
|
+
manifestPresent: Boolean(manifest),
|
|
677
|
+
reviewPackPresent: Boolean(manifest?.review ?? audit.reviewPack),
|
|
678
|
+
scaffoldPackPresent: Boolean(manifest?.scaffold),
|
|
679
|
+
sectionPackCount: manifest?.sections.length ?? 0,
|
|
680
|
+
pagePackCount: manifest?.pages.length ?? 0,
|
|
681
|
+
mutationPackCount: manifest?.mutations?.length ?? 0,
|
|
682
|
+
generatedAt: typeof manifest?.generatedAt === "string" ? manifest.generatedAt : null
|
|
683
|
+
},
|
|
684
|
+
ci: {
|
|
685
|
+
recommendedCommand: "decantr health --ci --fail-on error",
|
|
686
|
+
failOn: "error"
|
|
687
|
+
},
|
|
688
|
+
findings
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
function resolveMcpProjectRoot(value) {
|
|
692
|
+
if (value == null) return process.cwd();
|
|
693
|
+
if (typeof value !== "string") {
|
|
694
|
+
throw new Error("project_path must be a string when provided.");
|
|
695
|
+
}
|
|
696
|
+
return resolveWorkspacePath(value);
|
|
697
|
+
}
|
|
698
|
+
async function getMcpHealthState(projectRoot) {
|
|
699
|
+
const { auditProject, createContractAssertions, createEvidenceBundle } = await import("@decantr/verifier");
|
|
700
|
+
const audit = await auditProject(projectRoot);
|
|
701
|
+
const assertions = createContractAssertions(projectRoot, audit);
|
|
702
|
+
const report = mcpReportFromAudit(projectRoot, audit, assertions);
|
|
703
|
+
const evidence = createEvidenceBundle({
|
|
704
|
+
projectRoot,
|
|
705
|
+
audit,
|
|
706
|
+
assertions,
|
|
707
|
+
report,
|
|
708
|
+
workspaceConfigPath: existsSync(join2(projectRoot, ".decantr", "workspace.json")) ? join2(projectRoot, ".decantr", "workspace.json") : null
|
|
709
|
+
});
|
|
710
|
+
return { audit, assertions, report, evidence };
|
|
711
|
+
}
|
|
712
|
+
function discoverMcpWorkspaceProjects(root, maxProjects = 500) {
|
|
713
|
+
const projects = [];
|
|
714
|
+
function walk(dir, depth) {
|
|
715
|
+
if (projects.length >= maxProjects || depth > 6) return;
|
|
716
|
+
if (existsSync(join2(dir, "decantr.essence.json"))) {
|
|
717
|
+
const path = relative2(root, dir).replace(/\\/g, "/") || ".";
|
|
718
|
+
projects.push({
|
|
719
|
+
id: path.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "project",
|
|
720
|
+
path,
|
|
721
|
+
absolutePath: dir
|
|
722
|
+
});
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
726
|
+
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
727
|
+
if (MCP_WORKSPACE_IGNORES.has(entry.name)) continue;
|
|
728
|
+
walk(join2(dir, entry.name), depth + 1);
|
|
729
|
+
if (projects.length >= maxProjects) return;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
walk(root, 0);
|
|
733
|
+
return projects.sort((a, b) => a.path.localeCompare(b.path));
|
|
734
|
+
}
|
|
735
|
+
async function getMcpWorkspaceHealth(args) {
|
|
736
|
+
const root = args.workspace_root == null ? process.cwd() : resolveMcpProjectRoot(args.workspace_root);
|
|
737
|
+
const maxProjects = typeof args.max_projects === "number" && Number.isFinite(args.max_projects) ? Math.max(1, Math.floor(args.max_projects)) : 500;
|
|
738
|
+
const discovered = discoverMcpWorkspaceProjects(root, maxProjects);
|
|
739
|
+
const projects = [];
|
|
740
|
+
for (const project of discovered) {
|
|
741
|
+
const startedAt = Date.now();
|
|
742
|
+
try {
|
|
743
|
+
const state = await getMcpHealthState(project.absolutePath);
|
|
744
|
+
projects.push({
|
|
745
|
+
id: project.id,
|
|
746
|
+
path: project.path,
|
|
747
|
+
status: state.report.status,
|
|
748
|
+
score: state.report.score,
|
|
749
|
+
errorCount: state.report.summary.errorCount,
|
|
750
|
+
warnCount: state.report.summary.warnCount,
|
|
751
|
+
infoCount: state.report.summary.infoCount,
|
|
752
|
+
findingCount: state.report.summary.findingCount,
|
|
753
|
+
durationMs: Date.now() - startedAt,
|
|
754
|
+
changed: false,
|
|
755
|
+
source: "auto",
|
|
756
|
+
error: null
|
|
757
|
+
});
|
|
758
|
+
} catch (error) {
|
|
759
|
+
projects.push({
|
|
760
|
+
id: project.id,
|
|
761
|
+
path: project.path,
|
|
762
|
+
status: "failed",
|
|
763
|
+
score: 0,
|
|
764
|
+
errorCount: 1,
|
|
765
|
+
warnCount: 0,
|
|
766
|
+
infoCount: 0,
|
|
767
|
+
findingCount: 1,
|
|
768
|
+
durationMs: Date.now() - startedAt,
|
|
769
|
+
changed: false,
|
|
770
|
+
source: "auto",
|
|
771
|
+
error: error.message
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
return {
|
|
776
|
+
$schema: MCP_WORKSPACE_HEALTH_SCHEMA_URL,
|
|
777
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
778
|
+
workspaceRoot: "<workspace>",
|
|
779
|
+
changedOnly: false,
|
|
780
|
+
since: null,
|
|
781
|
+
summary: {
|
|
782
|
+
projectCount: discovered.length,
|
|
783
|
+
checkedCount: projects.length,
|
|
784
|
+
healthyCount: projects.filter((project) => project.status === "healthy").length,
|
|
785
|
+
warningCount: projects.filter((project) => project.status === "warning").length,
|
|
786
|
+
errorCount: projects.filter((project) => project.status === "error").length,
|
|
787
|
+
failedCount: projects.filter((project) => project.status === "failed").length
|
|
788
|
+
},
|
|
789
|
+
projects
|
|
790
|
+
};
|
|
791
|
+
}
|
|
467
792
|
var TOOLS = [
|
|
468
793
|
// 1. decantr_read_essence — local read
|
|
469
794
|
{
|
|
470
795
|
name: "decantr_read_essence",
|
|
471
796
|
title: "Read Essence",
|
|
472
|
-
description: "Read and return the current decantr.essence.json file from the working directory.
|
|
797
|
+
description: "Read and return the current Essence v4 decantr.essence.json file from the working directory. Optionally filter by layer (dna, blueprint, or full).",
|
|
473
798
|
inputSchema: {
|
|
474
799
|
type: "object",
|
|
475
800
|
properties: {
|
|
@@ -480,7 +805,7 @@ var TOOLS = [
|
|
|
480
805
|
layer: {
|
|
481
806
|
type: "string",
|
|
482
807
|
enum: ["dna", "blueprint", "full"],
|
|
483
|
-
description: "For
|
|
808
|
+
description: "For Essence v4 files: return only the specified layer. Defaults to full."
|
|
484
809
|
}
|
|
485
810
|
}
|
|
486
811
|
},
|
|
@@ -490,7 +815,7 @@ var TOOLS = [
|
|
|
490
815
|
{
|
|
491
816
|
name: "decantr_validate",
|
|
492
817
|
title: "Validate Essence",
|
|
493
|
-
description: "Validate
|
|
818
|
+
description: "Validate an Essence v4 decantr.essence.json file against the schema and guard rules, reporting DNA vs Blueprint violations separately.",
|
|
494
819
|
inputSchema: {
|
|
495
820
|
type: "object",
|
|
496
821
|
properties: {
|
|
@@ -593,7 +918,7 @@ var TOOLS = [
|
|
|
593
918
|
{
|
|
594
919
|
name: "decantr_check_drift",
|
|
595
920
|
title: "Check Drift",
|
|
596
|
-
description: "Check if code changes violate the design intent captured in the Essence spec.
|
|
921
|
+
description: "Check if code changes violate the design intent captured in the Essence v4 spec. Returns separate dna_violations and blueprint_drift with autoFixable flags.",
|
|
597
922
|
inputSchema: {
|
|
598
923
|
type: "object",
|
|
599
924
|
properties: {
|
|
@@ -619,7 +944,7 @@ var TOOLS = [
|
|
|
619
944
|
{
|
|
620
945
|
name: "decantr_create_essence",
|
|
621
946
|
title: "Create Essence",
|
|
622
|
-
description: "Generate a valid
|
|
947
|
+
description: "Generate a valid Essence v4 skeleton from a project description. Returns a sectioned decantr.essence.json template based on the closest matching archetype and blueprint.",
|
|
623
948
|
inputSchema: {
|
|
624
949
|
type: "object",
|
|
625
950
|
properties: {
|
|
@@ -680,7 +1005,7 @@ var TOOLS = [
|
|
|
680
1005
|
{
|
|
681
1006
|
name: "decantr_update_essence",
|
|
682
1007
|
title: "Update Essence",
|
|
683
|
-
description: "Mutate
|
|
1008
|
+
description: "Mutate an Essence v4 file: add/remove/update pages, update DNA or blueprint fields, add/remove features. Older projects must run `decantr migrate --to v4` first.",
|
|
684
1009
|
inputSchema: {
|
|
685
1010
|
type: "object",
|
|
686
1011
|
properties: {
|
|
@@ -935,6 +1260,82 @@ var TOOLS = [
|
|
|
935
1260
|
required: ["file_path"]
|
|
936
1261
|
},
|
|
937
1262
|
annotations: READ_ONLY_NETWORK
|
|
1263
|
+
},
|
|
1264
|
+
// 22. decantr_get_evidence_bundle — local reliability artifact
|
|
1265
|
+
{
|
|
1266
|
+
name: "decantr_get_evidence_bundle",
|
|
1267
|
+
title: "Get Evidence Bundle",
|
|
1268
|
+
description: "Generate a local Evidence Bundle for the current Decantr project. The bundle redacts source, prompts, secrets, absolute paths, repo names, and screenshots by default.",
|
|
1269
|
+
inputSchema: {
|
|
1270
|
+
type: "object",
|
|
1271
|
+
properties: {
|
|
1272
|
+
project_path: {
|
|
1273
|
+
type: "string",
|
|
1274
|
+
description: "Optional relative project path inside the active workspace. Defaults to the current working directory."
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
},
|
|
1278
|
+
annotations: READ_ONLY
|
|
1279
|
+
},
|
|
1280
|
+
// 23. decantr_workspace_health — local workspace reliability scan
|
|
1281
|
+
{
|
|
1282
|
+
name: "decantr_workspace_health",
|
|
1283
|
+
title: "Workspace Health",
|
|
1284
|
+
description: "Discover Decantr projects in the active workspace and return deterministic aggregate health for monorepos with many Decantr apps.",
|
|
1285
|
+
inputSchema: {
|
|
1286
|
+
type: "object",
|
|
1287
|
+
properties: {
|
|
1288
|
+
workspace_root: {
|
|
1289
|
+
type: "string",
|
|
1290
|
+
description: "Optional relative workspace root inside the active workspace. Defaults to the current working directory."
|
|
1291
|
+
},
|
|
1292
|
+
max_projects: {
|
|
1293
|
+
type: "number",
|
|
1294
|
+
description: "Optional cap on discovered projects. Defaults to 500."
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
},
|
|
1298
|
+
annotations: READ_ONLY
|
|
1299
|
+
},
|
|
1300
|
+
// 24. decantr_get_repair_prompt — local AI repair loop
|
|
1301
|
+
{
|
|
1302
|
+
name: "decantr_get_repair_prompt",
|
|
1303
|
+
title: "Get Repair Prompt",
|
|
1304
|
+
description: "Return an AI-ready repair prompt for a Project Health finding, including exact finding evidence, preserved constraints, do-not-change guidance, and rerun commands.",
|
|
1305
|
+
inputSchema: {
|
|
1306
|
+
type: "object",
|
|
1307
|
+
properties: {
|
|
1308
|
+
project_path: {
|
|
1309
|
+
type: "string",
|
|
1310
|
+
description: "Optional relative project path inside the active workspace. Defaults to the current working directory."
|
|
1311
|
+
},
|
|
1312
|
+
finding_id: {
|
|
1313
|
+
type: "string",
|
|
1314
|
+
description: "Optional finding id. Defaults to the first error or warning, then the first finding."
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
},
|
|
1318
|
+
annotations: READ_ONLY
|
|
1319
|
+
},
|
|
1320
|
+
// 25. decantr_run_health_loop — local evidence + repair loop
|
|
1321
|
+
{
|
|
1322
|
+
name: "decantr_run_health_loop",
|
|
1323
|
+
title: "Run Health Loop",
|
|
1324
|
+
description: "Run Project Health, produce evidence, and return the next repair prompt for AI agents without uploading project source.",
|
|
1325
|
+
inputSchema: {
|
|
1326
|
+
type: "object",
|
|
1327
|
+
properties: {
|
|
1328
|
+
project_path: {
|
|
1329
|
+
type: "string",
|
|
1330
|
+
description: "Optional relative project path inside the active workspace. Defaults to the current working directory."
|
|
1331
|
+
},
|
|
1332
|
+
finding_id: {
|
|
1333
|
+
type: "string",
|
|
1334
|
+
description: "Optional finding id to target. Defaults to the first error or warning, then the first finding."
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
},
|
|
1338
|
+
annotations: READ_ONLY
|
|
938
1339
|
}
|
|
939
1340
|
];
|
|
940
1341
|
async function handleTool(name, args) {
|
|
@@ -946,7 +1347,12 @@ async function handleTool(name, args) {
|
|
|
946
1347
|
const raw = await readFile2(essencePath, "utf-8");
|
|
947
1348
|
const essence = JSON.parse(raw);
|
|
948
1349
|
const layer = args.layer;
|
|
949
|
-
if (
|
|
1350
|
+
if (!isV42(essence)) {
|
|
1351
|
+
return {
|
|
1352
|
+
error: "Active Decantr V2 workflows require Essence v4.0.0. Run `decantr migrate --to v4` for older essence files."
|
|
1353
|
+
};
|
|
1354
|
+
}
|
|
1355
|
+
if (layer) {
|
|
950
1356
|
if (layer === "dna") return essence.dna;
|
|
951
1357
|
if (layer === "blueprint") return essence.blueprint;
|
|
952
1358
|
}
|
|
@@ -975,13 +1381,13 @@ async function handleTool(name, args) {
|
|
|
975
1381
|
} catch {
|
|
976
1382
|
}
|
|
977
1383
|
}
|
|
978
|
-
if (result.valid && typeof essence === "object" && essence !== null &&
|
|
1384
|
+
if (result.valid && typeof essence === "object" && essence !== null && isV42(essence)) {
|
|
979
1385
|
const dnaViolations = guardViolations.filter((v) => v.layer === "dna");
|
|
980
1386
|
const blueprintViolations = guardViolations.filter((v) => v.layer === "blueprint");
|
|
981
1387
|
const otherViolations = guardViolations.filter((v) => !v.layer);
|
|
982
1388
|
return {
|
|
983
1389
|
...result,
|
|
984
|
-
format: "
|
|
1390
|
+
format: "v4",
|
|
985
1391
|
dna_violations: dnaViolations,
|
|
986
1392
|
blueprint_violations: blueprintViolations,
|
|
987
1393
|
guardViolations: otherViolations
|
|
@@ -1189,51 +1595,43 @@ async function handleTool(name, args) {
|
|
|
1189
1595
|
if (!validation.valid) {
|
|
1190
1596
|
return { drifted: true, reason: "invalid_essence", errors: validation.errors };
|
|
1191
1597
|
}
|
|
1598
|
+
if (!isV42(essence)) {
|
|
1599
|
+
return {
|
|
1600
|
+
drifted: true,
|
|
1601
|
+
reason: "legacy_essence",
|
|
1602
|
+
errors: [
|
|
1603
|
+
"Active Decantr V2 workflows require Essence v4.0.0. Run `decantr migrate --to v4` for older essence files."
|
|
1604
|
+
]
|
|
1605
|
+
};
|
|
1606
|
+
}
|
|
1192
1607
|
const violations = [];
|
|
1193
1608
|
if (args.theme_used && typeof args.theme_used === "string") {
|
|
1194
|
-
|
|
1195
|
-
if (isV32(essence)) {
|
|
1196
|
-
expectedThemeId = essence.dna.theme.id;
|
|
1197
|
-
} else {
|
|
1198
|
-
const expectedTheme = essence.theme;
|
|
1199
|
-
expectedThemeId = expectedTheme?.id ?? expectedTheme?.style;
|
|
1200
|
-
}
|
|
1609
|
+
const expectedThemeId = essence.dna.theme.id;
|
|
1201
1610
|
if (expectedThemeId && args.theme_used !== expectedThemeId) {
|
|
1202
1611
|
violations.push({
|
|
1203
1612
|
rule: "theme-match",
|
|
1204
1613
|
severity: "critical",
|
|
1205
1614
|
message: `Theme drift: code uses "${args.theme_used}" but Essence specifies "${expectedThemeId}". Do not switch themes.`,
|
|
1206
|
-
|
|
1615
|
+
layer: "dna",
|
|
1616
|
+
autoFixable: false
|
|
1207
1617
|
});
|
|
1208
1618
|
}
|
|
1209
1619
|
}
|
|
1210
1620
|
if (args.page_id && typeof args.page_id === "string") {
|
|
1211
|
-
|
|
1212
|
-
if (isV32(essence)) {
|
|
1213
|
-
pages = essence.blueprint.pages;
|
|
1214
|
-
} else {
|
|
1215
|
-
pages = essence.structure || [];
|
|
1216
|
-
}
|
|
1621
|
+
const pages = listEssencePages(essence);
|
|
1217
1622
|
if (!pages.find((p) => p.id === args.page_id)) {
|
|
1218
1623
|
violations.push({
|
|
1219
1624
|
rule: "page-exists",
|
|
1220
1625
|
severity: "critical",
|
|
1221
1626
|
message: `Page "${args.page_id}" not found in Essence structure. Add it to the Essence before generating code for it.`,
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
autoFix: { type: "add_page", patch: { id: args.page_id } }
|
|
1226
|
-
} : {}
|
|
1627
|
+
layer: "blueprint",
|
|
1628
|
+
autoFixable: true,
|
|
1629
|
+
autoFix: { type: "add_page", patch: { id: args.page_id } }
|
|
1227
1630
|
});
|
|
1228
1631
|
}
|
|
1229
1632
|
}
|
|
1230
1633
|
if (args.components_used && Array.isArray(args.components_used) && args.page_id && typeof args.page_id === "string") {
|
|
1231
|
-
|
|
1232
|
-
if (isV32(essence)) {
|
|
1233
|
-
pages = essence.blueprint.pages;
|
|
1234
|
-
} else {
|
|
1235
|
-
pages = essence.structure || [];
|
|
1236
|
-
}
|
|
1634
|
+
const pages = listEssencePages(essence);
|
|
1237
1635
|
const page = pages.find((p) => p.id === args.page_id);
|
|
1238
1636
|
if (page && page.layout) {
|
|
1239
1637
|
const expectedPatterns = /* @__PURE__ */ new Set();
|
|
@@ -1265,7 +1663,8 @@ async function handleTool(name, args) {
|
|
|
1265
1663
|
rule: "component-pattern-match",
|
|
1266
1664
|
severity: "warning",
|
|
1267
1665
|
message: `Components [${unmatchedComponents.join(", ")}] do not match any pattern in page "${args.page_id}" layout. Expected patterns: [${[...expectedPatterns].join(", ")}].`,
|
|
1268
|
-
|
|
1666
|
+
layer: "blueprint",
|
|
1667
|
+
autoFixable: false
|
|
1269
1668
|
});
|
|
1270
1669
|
}
|
|
1271
1670
|
}
|
|
@@ -1286,21 +1685,11 @@ async function handleTool(name, args) {
|
|
|
1286
1685
|
}
|
|
1287
1686
|
} catch {
|
|
1288
1687
|
}
|
|
1289
|
-
if (isV32(essence)) {
|
|
1290
|
-
const dnaViolations = violations.filter((v) => v.layer === "dna");
|
|
1291
|
-
const blueprintDrift = violations.filter((v) => v.layer === "blueprint");
|
|
1292
|
-
const other = violations.filter((v) => !v.layer);
|
|
1293
|
-
return {
|
|
1294
|
-
drifted: violations.length > 0,
|
|
1295
|
-
dna_violations: dnaViolations,
|
|
1296
|
-
blueprint_drift: blueprintDrift,
|
|
1297
|
-
other_violations: other,
|
|
1298
|
-
checkedAgainst: essencePath
|
|
1299
|
-
};
|
|
1300
|
-
}
|
|
1301
1688
|
return {
|
|
1302
1689
|
drifted: violations.length > 0,
|
|
1303
|
-
violations,
|
|
1690
|
+
dna_violations: violations.filter((v) => v.layer === "dna"),
|
|
1691
|
+
blueprint_drift: violations.filter((v) => v.layer === "blueprint"),
|
|
1692
|
+
other_violations: violations.filter((v) => !v.layer),
|
|
1304
1693
|
checkedAgainst: essencePath
|
|
1305
1694
|
};
|
|
1306
1695
|
}
|
|
@@ -1348,8 +1737,17 @@ async function handleTool(name, args) {
|
|
|
1348
1737
|
}
|
|
1349
1738
|
const rawPages = pages || [{ id: "home", shell: "full-bleed", default_layout: ["hero"] }];
|
|
1350
1739
|
const defaultShell = rawPages[0]?.shell || "sidebar-main";
|
|
1740
|
+
const sectionPages = rawPages.map((p, index) => ({
|
|
1741
|
+
id: p.id,
|
|
1742
|
+
route: p.id === "home" || index === 0 ? "/" : `/${p.id}`,
|
|
1743
|
+
...p.shell !== defaultShell ? { shell_override: p.shell } : {},
|
|
1744
|
+
layout: p.default_layout || []
|
|
1745
|
+
}));
|
|
1746
|
+
const routes = Object.fromEntries(
|
|
1747
|
+
sectionPages.map((page) => [page.route, { section: bestMatch, page: page.id }])
|
|
1748
|
+
);
|
|
1351
1749
|
const essence = {
|
|
1352
|
-
version: "
|
|
1750
|
+
version: "4.0.0",
|
|
1353
1751
|
dna: {
|
|
1354
1752
|
theme: {
|
|
1355
1753
|
id: "auradecantism",
|
|
@@ -1394,12 +1792,18 @@ async function handleTool(name, args) {
|
|
|
1394
1792
|
},
|
|
1395
1793
|
blueprint: {
|
|
1396
1794
|
shell: defaultShell,
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1795
|
+
sections: [
|
|
1796
|
+
{
|
|
1797
|
+
id: bestMatch,
|
|
1798
|
+
role: "primary",
|
|
1799
|
+
shell: defaultShell,
|
|
1800
|
+
features,
|
|
1801
|
+
description: `${bestMatch} primary section`,
|
|
1802
|
+
pages: sectionPages
|
|
1803
|
+
}
|
|
1804
|
+
],
|
|
1805
|
+
features,
|
|
1806
|
+
routes
|
|
1403
1807
|
},
|
|
1404
1808
|
meta: {
|
|
1405
1809
|
archetype: bestMatch,
|
|
@@ -1411,7 +1815,7 @@ async function handleTool(name, args) {
|
|
|
1411
1815
|
return {
|
|
1412
1816
|
essence,
|
|
1413
1817
|
archetype: bestMatch,
|
|
1414
|
-
format: "
|
|
1818
|
+
format: "v4",
|
|
1415
1819
|
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.`,
|
|
1416
1820
|
_generated: {
|
|
1417
1821
|
matched_archetype: bestMatch,
|
|
@@ -1477,11 +1881,11 @@ async function handleTool(name, args) {
|
|
|
1477
1881
|
};
|
|
1478
1882
|
}
|
|
1479
1883
|
try {
|
|
1480
|
-
const { essence, path } = await mutateEssenceFile(args.path, (
|
|
1884
|
+
const { essence, path } = await mutateEssenceFile(args.path, (v4) => {
|
|
1481
1885
|
for (const v of violations) {
|
|
1482
|
-
applyDriftAcceptance(
|
|
1886
|
+
applyDriftAcceptance(v4, v, resolution, args.scope);
|
|
1483
1887
|
}
|
|
1484
|
-
return
|
|
1888
|
+
return v4;
|
|
1485
1889
|
});
|
|
1486
1890
|
return {
|
|
1487
1891
|
status: resolution === "accept_scoped" ? "accepted_scoped" : "accepted",
|
|
@@ -1517,8 +1921,8 @@ async function handleTool(name, args) {
|
|
|
1517
1921
|
};
|
|
1518
1922
|
}
|
|
1519
1923
|
try {
|
|
1520
|
-
const { essence, path } = await mutateEssenceFile(args.path, (
|
|
1521
|
-
return applyEssenceUpdate(
|
|
1924
|
+
const { essence, path } = await mutateEssenceFile(args.path, (v4) => {
|
|
1925
|
+
return applyEssenceUpdate(v4, operation, payload);
|
|
1522
1926
|
});
|
|
1523
1927
|
return {
|
|
1524
1928
|
status: "updated",
|
|
@@ -1647,8 +2051,10 @@ async function handleTool(name, args) {
|
|
|
1647
2051
|
} catch {
|
|
1648
2052
|
return { error: "No valid essence file found. Run decantr init first." };
|
|
1649
2053
|
}
|
|
1650
|
-
if (!
|
|
1651
|
-
return {
|
|
2054
|
+
if (!isV42(essence)) {
|
|
2055
|
+
return {
|
|
2056
|
+
error: "Section context requires Essence v4.0.0. Run `decantr migrate --to v4` first."
|
|
2057
|
+
};
|
|
1652
2058
|
}
|
|
1653
2059
|
const sections = essence.blueprint.sections || [];
|
|
1654
2060
|
const section = sections.find((s) => s.id === sectionId);
|
|
@@ -2092,10 +2498,122 @@ async function handleTool(name, args) {
|
|
|
2092
2498
|
}
|
|
2093
2499
|
return auditProject(projectRoot);
|
|
2094
2500
|
}
|
|
2501
|
+
case "decantr_get_evidence_bundle": {
|
|
2502
|
+
try {
|
|
2503
|
+
const projectRoot = resolveMcpProjectRoot(args.project_path);
|
|
2504
|
+
const state = await getMcpHealthState(projectRoot);
|
|
2505
|
+
return state.evidence;
|
|
2506
|
+
} catch (error) {
|
|
2507
|
+
return { error: error.message };
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
case "decantr_workspace_health": {
|
|
2511
|
+
if (args.workspace_root != null && typeof args.workspace_root !== "string") {
|
|
2512
|
+
return { error: "Invalid workspace_root. Must be a string when provided." };
|
|
2513
|
+
}
|
|
2514
|
+
if (args.max_projects != null && (typeof args.max_projects !== "number" || !Number.isFinite(args.max_projects))) {
|
|
2515
|
+
return { error: "Invalid max_projects. Must be a finite number when provided." };
|
|
2516
|
+
}
|
|
2517
|
+
try {
|
|
2518
|
+
return await getMcpWorkspaceHealth(args);
|
|
2519
|
+
} catch (error) {
|
|
2520
|
+
return { error: error.message };
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
case "decantr_get_repair_prompt": {
|
|
2524
|
+
if (args.finding_id != null && typeof args.finding_id !== "string") {
|
|
2525
|
+
return { error: "Invalid finding_id. Must be a string when provided." };
|
|
2526
|
+
}
|
|
2527
|
+
try {
|
|
2528
|
+
const projectRoot = resolveMcpProjectRoot(args.project_path);
|
|
2529
|
+
const state = await getMcpHealthState(projectRoot);
|
|
2530
|
+
const finding = (typeof args.finding_id === "string" ? state.report.findings.find((entry) => entry.id === args.finding_id) : void 0) ?? state.report.findings.find((entry) => entry.severity === "error") ?? state.report.findings.find((entry) => entry.severity === "warn") ?? state.report.findings[0] ?? null;
|
|
2531
|
+
if (!finding) {
|
|
2532
|
+
return {
|
|
2533
|
+
project: state.evidence.project,
|
|
2534
|
+
health: state.evidence.health,
|
|
2535
|
+
prompt: null,
|
|
2536
|
+
message: "No Project Health findings require repair.",
|
|
2537
|
+
commands: ["decantr health --evidence"]
|
|
2538
|
+
};
|
|
2539
|
+
}
|
|
2540
|
+
return {
|
|
2541
|
+
project: state.evidence.project,
|
|
2542
|
+
health: state.evidence.health,
|
|
2543
|
+
finding: {
|
|
2544
|
+
id: finding.id,
|
|
2545
|
+
source: finding.source,
|
|
2546
|
+
severity: finding.severity,
|
|
2547
|
+
category: finding.category,
|
|
2548
|
+
message: finding.message
|
|
2549
|
+
},
|
|
2550
|
+
prompt: finding.remediation.prompt,
|
|
2551
|
+
commands: finding.remediation.commands
|
|
2552
|
+
};
|
|
2553
|
+
} catch (error) {
|
|
2554
|
+
return { error: error.message };
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
case "decantr_run_health_loop": {
|
|
2558
|
+
if (args.finding_id != null && typeof args.finding_id !== "string") {
|
|
2559
|
+
return { error: "Invalid finding_id. Must be a string when provided." };
|
|
2560
|
+
}
|
|
2561
|
+
try {
|
|
2562
|
+
const projectRoot = resolveMcpProjectRoot(args.project_path);
|
|
2563
|
+
const state = await getMcpHealthState(projectRoot);
|
|
2564
|
+
const finding = (typeof args.finding_id === "string" ? state.report.findings.find((entry) => entry.id === args.finding_id) : void 0) ?? state.report.findings.find((entry) => entry.severity === "error") ?? state.report.findings.find((entry) => entry.severity === "warn") ?? state.report.findings[0] ?? null;
|
|
2565
|
+
return {
|
|
2566
|
+
project: state.evidence.project,
|
|
2567
|
+
health: state.evidence.health,
|
|
2568
|
+
report: state.report,
|
|
2569
|
+
evidence: state.evidence,
|
|
2570
|
+
repair: finding === null ? {
|
|
2571
|
+
finding: null,
|
|
2572
|
+
prompt: null,
|
|
2573
|
+
commands: ["decantr health --evidence"],
|
|
2574
|
+
message: "No Project Health findings require repair."
|
|
2575
|
+
} : {
|
|
2576
|
+
finding: {
|
|
2577
|
+
id: finding.id,
|
|
2578
|
+
source: finding.source,
|
|
2579
|
+
severity: finding.severity,
|
|
2580
|
+
category: finding.category,
|
|
2581
|
+
message: finding.message
|
|
2582
|
+
},
|
|
2583
|
+
prompt: finding.remediation.prompt,
|
|
2584
|
+
commands: finding.remediation.commands
|
|
2585
|
+
}
|
|
2586
|
+
};
|
|
2587
|
+
} catch (error) {
|
|
2588
|
+
return { error: error.message };
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
2095
2591
|
default:
|
|
2096
2592
|
return { error: `Unknown tool: ${name}` };
|
|
2097
2593
|
}
|
|
2098
2594
|
}
|
|
2595
|
+
function listEssencePages(essence) {
|
|
2596
|
+
return essence.blueprint.sections.flatMap(
|
|
2597
|
+
(section) => section.pages.map((page) => ({ ...page, sectionId: section.id }))
|
|
2598
|
+
);
|
|
2599
|
+
}
|
|
2600
|
+
function getMutablePage(essence, id, sectionId) {
|
|
2601
|
+
for (const section of essence.blueprint.sections) {
|
|
2602
|
+
if (sectionId && section.id !== sectionId) continue;
|
|
2603
|
+
const index = section.pages.findIndex((page) => page.id === id);
|
|
2604
|
+
if (index !== -1) {
|
|
2605
|
+
return { page: section.pages[index], section, index };
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2608
|
+
return null;
|
|
2609
|
+
}
|
|
2610
|
+
function getDefaultSection(essence) {
|
|
2611
|
+
const section = essence.blueprint.sections.find((s) => s.role === "primary") ?? essence.blueprint.sections[0];
|
|
2612
|
+
if (!section) {
|
|
2613
|
+
throw new Error("Essence v4 requires at least one blueprint section.");
|
|
2614
|
+
}
|
|
2615
|
+
return section;
|
|
2616
|
+
}
|
|
2099
2617
|
function applyDriftAcceptance(essence, violation, resolution, scope) {
|
|
2100
2618
|
switch (violation.rule) {
|
|
2101
2619
|
case "theme-match":
|
|
@@ -2109,9 +2627,10 @@ function applyDriftAcceptance(essence, violation, resolution, scope) {
|
|
|
2109
2627
|
case "page-exists":
|
|
2110
2628
|
case "structure": {
|
|
2111
2629
|
if (violation.page_id) {
|
|
2112
|
-
const
|
|
2630
|
+
const section = getDefaultSection(essence);
|
|
2631
|
+
const existing = getMutablePage(essence, violation.page_id);
|
|
2113
2632
|
if (!existing) {
|
|
2114
|
-
|
|
2633
|
+
section.pages.push({
|
|
2115
2634
|
id: violation.page_id,
|
|
2116
2635
|
layout: []
|
|
2117
2636
|
});
|
|
@@ -2134,11 +2653,15 @@ function applyEssenceUpdate(essence, operation, payload) {
|
|
|
2134
2653
|
case "add_page": {
|
|
2135
2654
|
const id = payload.id;
|
|
2136
2655
|
if (!id) throw new Error('Payload must include "id" for add_page.');
|
|
2137
|
-
const
|
|
2656
|
+
const sectionId = payload.section_id;
|
|
2657
|
+
const section = sectionId ? essence.blueprint.sections.find((candidate) => candidate.id === sectionId) : getDefaultSection(essence);
|
|
2658
|
+
if (!section) throw new Error(`Section "${sectionId}" not found.`);
|
|
2659
|
+
const existing = getMutablePage(essence, id, section.id);
|
|
2138
2660
|
if (existing) throw new Error(`Page "${id}" already exists.`);
|
|
2139
|
-
|
|
2661
|
+
section.pages.push({
|
|
2140
2662
|
id,
|
|
2141
2663
|
layout: payload.layout || [],
|
|
2664
|
+
...payload.route ? { route: payload.route } : {},
|
|
2142
2665
|
...payload.shell_override ? { shell_override: payload.shell_override } : {},
|
|
2143
2666
|
...payload.surface ? { surface: payload.surface } : {}
|
|
2144
2667
|
});
|
|
@@ -2147,9 +2670,9 @@ function applyEssenceUpdate(essence, operation, payload) {
|
|
|
2147
2670
|
case "remove_page": {
|
|
2148
2671
|
const id = payload.id;
|
|
2149
2672
|
if (!id) throw new Error('Payload must include "id" for remove_page.');
|
|
2150
|
-
const
|
|
2151
|
-
if (
|
|
2152
|
-
|
|
2673
|
+
const match = getMutablePage(essence, id, payload.section_id);
|
|
2674
|
+
if (!match) throw new Error(`Page "${id}" not found.`);
|
|
2675
|
+
match.section.pages.splice(match.index, 1);
|
|
2153
2676
|
break;
|
|
2154
2677
|
}
|
|
2155
2678
|
case "update_page_layout": {
|
|
@@ -2158,9 +2681,9 @@ function applyEssenceUpdate(essence, operation, payload) {
|
|
|
2158
2681
|
if (!id) throw new Error('Payload must include "id" for update_page_layout.');
|
|
2159
2682
|
if (!layout || !Array.isArray(layout))
|
|
2160
2683
|
throw new Error('Payload must include "layout" array for update_page_layout.');
|
|
2161
|
-
const
|
|
2162
|
-
if (!
|
|
2163
|
-
page.layout = layout;
|
|
2684
|
+
const match = getMutablePage(essence, id, payload.section_id);
|
|
2685
|
+
if (!match) throw new Error(`Page "${id}" not found.`);
|
|
2686
|
+
match.page.layout = layout;
|
|
2164
2687
|
break;
|
|
2165
2688
|
}
|
|
2166
2689
|
case "update_dna": {
|
|
@@ -2178,7 +2701,7 @@ function applyEssenceUpdate(essence, operation, payload) {
|
|
|
2178
2701
|
}
|
|
2179
2702
|
case "update_blueprint": {
|
|
2180
2703
|
for (const [key, value] of Object.entries(payload)) {
|
|
2181
|
-
if (key === "pages") continue;
|
|
2704
|
+
if (key === "pages" || key === "sections") continue;
|
|
2182
2705
|
essence.blueprint[key] = value;
|
|
2183
2706
|
}
|
|
2184
2707
|
break;
|
|
@@ -2224,7 +2747,7 @@ function describeUpdate(operation, payload) {
|
|
|
2224
2747
|
}
|
|
2225
2748
|
|
|
2226
2749
|
// src/index.ts
|
|
2227
|
-
var VERSION = "
|
|
2750
|
+
var VERSION = "2.0.0";
|
|
2228
2751
|
var server = new Server({ name: "decantr", version: VERSION }, { capabilities: { tools: {} } });
|
|
2229
2752
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
2230
2753
|
return { tools: TOOLS };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import "./chunk-
|
|
1
|
+
import "./chunk-FEXPLJKB.js";
|
package/package.json
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decantr/mcp-server",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"mcpName": "io.github.decantr-ai/mcp-server",
|
|
5
5
|
"description": "MCP server for Decantr — exposes design intelligence, packs, and verification to AI coding assistants",
|
|
6
6
|
"keywords": [
|
|
7
|
+
"decantr",
|
|
8
|
+
"decantr-ai",
|
|
7
9
|
"mcp",
|
|
8
10
|
"mcp-server",
|
|
9
11
|
"model-context-protocol",
|
|
@@ -15,6 +17,7 @@
|
|
|
15
17
|
"windsurf",
|
|
16
18
|
"design-system",
|
|
17
19
|
"design-intelligence",
|
|
20
|
+
"project-health",
|
|
18
21
|
"ui-generation",
|
|
19
22
|
"drift-detection"
|
|
20
23
|
],
|
|
@@ -46,9 +49,9 @@
|
|
|
46
49
|
},
|
|
47
50
|
"dependencies": {
|
|
48
51
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
49
|
-
"@decantr/essence-spec": "
|
|
50
|
-
"@decantr/
|
|
51
|
-
"@decantr/
|
|
52
|
+
"@decantr/essence-spec": "2.0.1",
|
|
53
|
+
"@decantr/verifier": "2.1.0",
|
|
54
|
+
"@decantr/registry": "2.0.0"
|
|
52
55
|
},
|
|
53
56
|
"scripts": {
|
|
54
57
|
"build": "tsup",
|