@decantr/mcp-server 2.0.0 → 2.2.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 +17 -8
- package/dist/bin.js +1 -1
- package/dist/{chunk-MUNBODQK.js → chunk-SVLMT45O.js} +732 -68
- package/dist/index.js +1 -1
- package/package.json +7 -4
package/README.md
CHANGED
|
@@ -12,7 +12,8 @@ Design intelligence for AI-generated UI. Make Claude, Cursor, and Windsurf gener
|
|
|
12
12
|
|
|
13
13
|

|
|
14
14
|
|
|
15
|
-
- **Structured design context** -- gives your AI assistant patterns, layouts,
|
|
15
|
+
- **Structured design context** -- gives your AI assistant patterns, layouts, component specs, and Brownfield task-time context 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
|
|
|
@@ -132,12 +133,17 @@ The server exposes Decantr registry, context, benchmark, and verification tools.
|
|
|
132
133
|
| `decantr_resolve_pattern` | Get full pattern details: layout spec, components, presets, code examples | `{ "id": "data-table", "preset": "product" }` |
|
|
133
134
|
| `decantr_resolve_archetype` | Get archetype details: default pages, layouts, features, suggested theme | `{ "id": "saas-dashboard" }` |
|
|
134
135
|
| `decantr_resolve_blueprint` | Get a full app composition with page structure and personality traits | `{ "id": "ecommerce" }` |
|
|
135
|
-
| `decantr_suggest_patterns` | Given a page description, get ranked pattern suggestions | `{ "description": "
|
|
136
|
+
| `decantr_suggest_patterns` | Given a page description plus optional route/source excerpt, get ranked pattern suggestions | `{ "description": "recipe feed with avatars and infinite scroll", "route": "/feed" }` |
|
|
136
137
|
| `decantr_check_drift` | Check if generated code violates the design intent in the Essence spec | `{ "page_id": "overview", "components_used": ["Card", "LineChart"], "theme_used": "auradecantism" }` |
|
|
137
138
|
| `decantr_get_execution_pack` | Read compiled scaffold, section, page, review, or mutation execution packs, with hosted fallback when local context is missing | `{ "pack_type": "page", "id": "overview", "format": "json" }` |
|
|
139
|
+
| `decantr_prepare_task_context` | Resolve compact route/task context before editing a Brownfield or Essence route | `{ "route": "/feed", "task": "improve recipe card loading" }` |
|
|
138
140
|
| `decantr_compile_execution_packs` | Compile a hosted execution-pack bundle from a local or inline essence document | `{ "path": "./decantr.essence.json", "namespace": "@official" }` |
|
|
139
141
|
| `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
142
|
| `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" }` |
|
|
143
|
+
| `decantr_get_evidence_bundle` | Generate the local privacy-redacted Evidence Bundle for a project | `{ "project_path": "apps/web" }` |
|
|
144
|
+
| `decantr_workspace_health` | Discover Decantr projects and return aggregate workspace health | `{ "workspace_root": ".", "max_projects": 100 }` |
|
|
145
|
+
| `decantr_get_repair_prompt` | Return the scoped repair prompt for a health finding | `{ "finding_id": "assertion-contract-context-pack-manifest" }` |
|
|
146
|
+
| `decantr_run_health_loop` | Run health, evidence, and next repair prompt in one local agent loop | `{ "project_path": "apps/web" }` |
|
|
141
147
|
| `decantr_get_showcase_benchmarks` | Read the audited showcase corpus manifest, shortlist, or verification report | `{ "view": "verification" }` |
|
|
142
148
|
|
|
143
149
|
For the broader product surface and support policy, see the root Decantr docs and package support matrix.
|
|
@@ -165,12 +171,15 @@ The AI assistant calls these tools behind the scenes:
|
|
|
165
171
|
3. `decantr_suggest_patterns` -- recommends `kpi-grid`, `chart-grid`, `data-table`, and `form-sections` for the described pages
|
|
166
172
|
4. `decantr_resolve_pattern` -- fetches layout specs and component lists for each pattern
|
|
167
173
|
5. `decantr_get_execution_pack` -- loads the compiled scaffold/page/review packs as the task contract, falling back to hosted compilation when local pack artifacts are missing
|
|
168
|
-
6. `
|
|
169
|
-
7. `
|
|
170
|
-
8. `
|
|
171
|
-
9. `
|
|
172
|
-
|
|
173
|
-
|
|
174
|
+
6. `decantr_prepare_task_context` -- resolves route-local Brownfield context, visual evidence, and theme inventory before editing an existing app
|
|
175
|
+
7. `decantr_compile_execution_packs` -- compiles the hosted pack bundle when the task needs a fresh remote contract from the essence document
|
|
176
|
+
8. `decantr_check_drift` -- validates the generated code against the Essence spec before presenting it
|
|
177
|
+
9. `decantr_critique` -- critiques a specific file, falling back to the hosted verifier when the local review pack is missing
|
|
178
|
+
10. `decantr_audit_project` -- runs the stronger project-level audit once the implementation is in place
|
|
179
|
+
11. `decantr_get_evidence_bundle` -- returns the local evidence bundle for the AI repair loop
|
|
180
|
+
12. `decantr_get_repair_prompt` -- gives the assistant exact finding evidence, constraints to preserve, and commands to rerun
|
|
181
|
+
|
|
182
|
+
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
183
|
|
|
175
184
|
## License
|
|
176
185
|
|
package/dist/bin.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "./chunk-
|
|
2
|
+
import "./chunk-SVLMT45O.js";
|
|
@@ -8,7 +8,12 @@ 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
10
|
import { evaluateGuard, isV4 as isV42, validateEssence } from "@decantr/essence-spec";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
isContentIntelligenceSource,
|
|
13
|
+
patternToDiscoveryCandidate,
|
|
14
|
+
rankPatternCandidates,
|
|
15
|
+
resolvePatternPreset
|
|
16
|
+
} from "@decantr/registry";
|
|
12
17
|
|
|
13
18
|
// src/helpers.ts
|
|
14
19
|
import { realpathSync } from "fs";
|
|
@@ -124,6 +129,47 @@ async function writeDriftLog(entries, projectRoot) {
|
|
|
124
129
|
}
|
|
125
130
|
|
|
126
131
|
// src/tools.ts
|
|
132
|
+
function readJsonIfExists(path) {
|
|
133
|
+
if (!existsSync(path)) return null;
|
|
134
|
+
try {
|
|
135
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
136
|
+
} catch {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function extractPatternIdsFromLayoutItem(item, ids) {
|
|
141
|
+
if (typeof item === "string") {
|
|
142
|
+
ids.add(item);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (!item || typeof item !== "object") return;
|
|
146
|
+
const record = item;
|
|
147
|
+
if (typeof record.pattern === "string") ids.add(record.pattern);
|
|
148
|
+
if (Array.isArray(record.cols)) {
|
|
149
|
+
for (const col of record.cols) extractPatternIdsFromLayoutItem(col, ids);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function extractPagePatternIds(page) {
|
|
153
|
+
if (!page) return [];
|
|
154
|
+
const ids = /* @__PURE__ */ new Set();
|
|
155
|
+
for (const item of page.layout ?? []) extractPatternIdsFromLayoutItem(item, ids);
|
|
156
|
+
return [...ids].sort();
|
|
157
|
+
}
|
|
158
|
+
function summarizePackJson(pack) {
|
|
159
|
+
if (!pack || typeof pack !== "object") {
|
|
160
|
+
return { directives: [], patterns: [], visualTarget: null, sharedComponents: [] };
|
|
161
|
+
}
|
|
162
|
+
const record = pack;
|
|
163
|
+
const data = record.data && typeof record.data === "object" ? record.data : record;
|
|
164
|
+
const patterns = Array.isArray(data.patterns) ? data.patterns : [];
|
|
165
|
+
const directives = Array.isArray(data.directives) ? data.directives : [];
|
|
166
|
+
const sharedComponents = Array.isArray(data.sharedComponents) ? data.sharedComponents : Array.isArray(data.shared_components) ? data.shared_components : [];
|
|
167
|
+
const visualTarget = typeof data.visualTarget === "string" ? data.visualTarget : typeof data.visual_target === "string" ? data.visual_target : null;
|
|
168
|
+
return { directives, patterns, visualTarget, sharedComponents };
|
|
169
|
+
}
|
|
170
|
+
function routeSlug(route) {
|
|
171
|
+
return route.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "root";
|
|
172
|
+
}
|
|
127
173
|
async function getShowcaseBenchmarkPayload(view) {
|
|
128
174
|
const client = getPublicAPIClient();
|
|
129
175
|
if (view === "manifest") {
|
|
@@ -469,6 +515,333 @@ var WRITE_TOOL = {
|
|
|
469
515
|
idempotentHint: false,
|
|
470
516
|
openWorldHint: false
|
|
471
517
|
};
|
|
518
|
+
var MCP_PROJECT_HEALTH_SCHEMA_URL = "https://decantr.ai/schemas/project-health-report.v1.json";
|
|
519
|
+
var MCP_WORKSPACE_HEALTH_SCHEMA_URL = "https://decantr.ai/schemas/workspace-health-report.v1.json";
|
|
520
|
+
var MCP_WORKSPACE_IGNORES = /* @__PURE__ */ new Set([
|
|
521
|
+
".git",
|
|
522
|
+
".next",
|
|
523
|
+
".turbo",
|
|
524
|
+
".vercel",
|
|
525
|
+
"coverage",
|
|
526
|
+
"dist",
|
|
527
|
+
"node_modules",
|
|
528
|
+
"playwright-report"
|
|
529
|
+
]);
|
|
530
|
+
function mcpSlug(value) {
|
|
531
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
|
|
532
|
+
}
|
|
533
|
+
function mcpStatusFromCounts(counts) {
|
|
534
|
+
if (counts.errorCount > 0) return "error";
|
|
535
|
+
if (counts.warnCount > 0) return "warning";
|
|
536
|
+
return "healthy";
|
|
537
|
+
}
|
|
538
|
+
function mcpScoreFromCounts(counts) {
|
|
539
|
+
return Math.max(
|
|
540
|
+
0,
|
|
541
|
+
Math.min(100, 100 - counts.errorCount * 15 - counts.warnCount * 5 - counts.infoCount)
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
function mcpCommandsForFinding(source) {
|
|
545
|
+
switch (source) {
|
|
546
|
+
case "assertion":
|
|
547
|
+
return ["decantr refresh", "decantr health --evidence"];
|
|
548
|
+
case "brownfield":
|
|
549
|
+
return ["decantr analyze", "decantr init --existing --merge-proposal", "decantr health"];
|
|
550
|
+
case "browser":
|
|
551
|
+
return ["decantr health --browser", "decantr health --evidence"];
|
|
552
|
+
case "check":
|
|
553
|
+
return ["decantr check", "decantr health"];
|
|
554
|
+
case "design-token":
|
|
555
|
+
return ["decantr export --to figma-tokens", "decantr health --evidence"];
|
|
556
|
+
case "interaction":
|
|
557
|
+
return ["decantr check --strict", "decantr health"];
|
|
558
|
+
case "pack":
|
|
559
|
+
return [
|
|
560
|
+
"decantr refresh",
|
|
561
|
+
"decantr registry get-pack review --write-context",
|
|
562
|
+
"decantr health"
|
|
563
|
+
];
|
|
564
|
+
case "runtime":
|
|
565
|
+
return ["npm run build", "decantr health"];
|
|
566
|
+
default:
|
|
567
|
+
return ["decantr audit", "decantr health"];
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
function mcpSourceFromFinding(finding) {
|
|
571
|
+
const category = finding.category.toLowerCase();
|
|
572
|
+
const id = finding.id.toLowerCase();
|
|
573
|
+
const rule = finding.rule?.toLowerCase() ?? "";
|
|
574
|
+
if (category.includes("runtime") || category.includes("document") || category.includes("performance")) {
|
|
575
|
+
return "runtime";
|
|
576
|
+
}
|
|
577
|
+
if (category.includes("pack") || category.includes("review contract")) {
|
|
578
|
+
return "pack";
|
|
579
|
+
}
|
|
580
|
+
if (category.includes("interaction") || id.includes("interaction") || rule.includes("interaction")) {
|
|
581
|
+
return "interaction";
|
|
582
|
+
}
|
|
583
|
+
return "audit";
|
|
584
|
+
}
|
|
585
|
+
function mcpBuildRepairPrompt(input) {
|
|
586
|
+
return [
|
|
587
|
+
"You are fixing one Decantr Project Health finding in this local workspace.",
|
|
588
|
+
"",
|
|
589
|
+
"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.",
|
|
590
|
+
"",
|
|
591
|
+
`Finding: ${input.id}`,
|
|
592
|
+
`Source: ${input.source}`,
|
|
593
|
+
`Severity: ${input.severity}`,
|
|
594
|
+
`Category: ${input.category}`,
|
|
595
|
+
`Message: ${input.message}`,
|
|
596
|
+
input.evidence.length > 0 ? `Evidence:
|
|
597
|
+
${input.evidence.map((entry) => `- ${entry}`).join("\n")}` : null,
|
|
598
|
+
input.suggestedFix ? `Suggested fix: ${input.suggestedFix}` : null,
|
|
599
|
+
"",
|
|
600
|
+
"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.",
|
|
601
|
+
"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.",
|
|
602
|
+
"",
|
|
603
|
+
`After the fix, run:
|
|
604
|
+
${input.commands.map((command) => `- ${command}`).join("\n")}`
|
|
605
|
+
].filter((line) => Boolean(line)).join("\n");
|
|
606
|
+
}
|
|
607
|
+
function mcpHealthFinding(input) {
|
|
608
|
+
const id = `${input.source}-${mcpSlug(input.baseId || input.rule || `${input.category}-${input.message}`)}`;
|
|
609
|
+
const commands = mcpCommandsForFinding(input.source);
|
|
610
|
+
return {
|
|
611
|
+
id,
|
|
612
|
+
source: input.source,
|
|
613
|
+
category: input.category,
|
|
614
|
+
severity: input.severity,
|
|
615
|
+
message: input.message,
|
|
616
|
+
evidence: input.evidence ?? [],
|
|
617
|
+
target: input.target,
|
|
618
|
+
file: input.file,
|
|
619
|
+
rule: input.rule,
|
|
620
|
+
suggestedFix: input.suggestedFix,
|
|
621
|
+
remediation: {
|
|
622
|
+
summary: input.suggestedFix || `Resolve ${input.category.toLowerCase()} finding.`,
|
|
623
|
+
commands,
|
|
624
|
+
prompt: mcpBuildRepairPrompt({
|
|
625
|
+
id,
|
|
626
|
+
source: input.source,
|
|
627
|
+
category: input.category,
|
|
628
|
+
severity: input.severity,
|
|
629
|
+
message: input.message,
|
|
630
|
+
evidence: input.evidence ?? [],
|
|
631
|
+
suggestedFix: input.suggestedFix,
|
|
632
|
+
commands
|
|
633
|
+
})
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
function mcpCollectDeclaredRoutes(essence) {
|
|
638
|
+
if (!essence || !isV42(essence)) return [];
|
|
639
|
+
return Object.keys(essence.blueprint.routes ?? {}).sort();
|
|
640
|
+
}
|
|
641
|
+
function mcpReportFromAudit(projectRoot, audit, assertions) {
|
|
642
|
+
const findings = [];
|
|
643
|
+
const seen = /* @__PURE__ */ new Set();
|
|
644
|
+
const pushUnique = (finding) => {
|
|
645
|
+
const key = `${finding.rule ?? finding.id}|${finding.message}`;
|
|
646
|
+
if (seen.has(key)) return;
|
|
647
|
+
seen.add(key);
|
|
648
|
+
findings.push(finding);
|
|
649
|
+
};
|
|
650
|
+
for (const finding of audit.findings) {
|
|
651
|
+
pushUnique(
|
|
652
|
+
mcpHealthFinding({
|
|
653
|
+
source: mcpSourceFromFinding(finding),
|
|
654
|
+
category: finding.category,
|
|
655
|
+
severity: finding.severity,
|
|
656
|
+
message: finding.message,
|
|
657
|
+
evidence: finding.evidence,
|
|
658
|
+
target: finding.target,
|
|
659
|
+
file: finding.file,
|
|
660
|
+
rule: finding.rule,
|
|
661
|
+
suggestedFix: finding.suggestedFix,
|
|
662
|
+
baseId: finding.id
|
|
663
|
+
})
|
|
664
|
+
);
|
|
665
|
+
}
|
|
666
|
+
for (const assertion of assertions) {
|
|
667
|
+
if (assertion.status !== "failed") continue;
|
|
668
|
+
pushUnique(
|
|
669
|
+
mcpHealthFinding({
|
|
670
|
+
source: "assertion",
|
|
671
|
+
category: `Contract ${assertion.category}`,
|
|
672
|
+
severity: assertion.severity,
|
|
673
|
+
message: assertion.message,
|
|
674
|
+
evidence: assertion.evidence,
|
|
675
|
+
target: assertion.target,
|
|
676
|
+
rule: assertion.rule,
|
|
677
|
+
suggestedFix: assertion.suggestedFix,
|
|
678
|
+
baseId: assertion.id
|
|
679
|
+
})
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
if (!audit.valid && findings.every((finding) => finding.severity !== "error")) {
|
|
683
|
+
pushUnique(
|
|
684
|
+
mcpHealthFinding({
|
|
685
|
+
source: "audit",
|
|
686
|
+
category: "Project Contract",
|
|
687
|
+
severity: "error",
|
|
688
|
+
message: "Project audit is not valid.",
|
|
689
|
+
evidence: ["The verifier returned valid=false."],
|
|
690
|
+
rule: "project-audit-invalid",
|
|
691
|
+
suggestedFix: "Resolve blocking audit findings and rerun `decantr health`."
|
|
692
|
+
})
|
|
693
|
+
);
|
|
694
|
+
}
|
|
695
|
+
const counts = {
|
|
696
|
+
errorCount: findings.filter((finding) => finding.severity === "error").length,
|
|
697
|
+
warnCount: findings.filter((finding) => finding.severity === "warn").length,
|
|
698
|
+
infoCount: findings.filter((finding) => finding.severity === "info").length
|
|
699
|
+
};
|
|
700
|
+
const manifest = audit.packManifest;
|
|
701
|
+
return {
|
|
702
|
+
$schema: MCP_PROJECT_HEALTH_SCHEMA_URL,
|
|
703
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
704
|
+
projectRoot,
|
|
705
|
+
status: mcpStatusFromCounts(counts),
|
|
706
|
+
score: mcpScoreFromCounts(counts),
|
|
707
|
+
summary: {
|
|
708
|
+
...counts,
|
|
709
|
+
findingCount: findings.length,
|
|
710
|
+
workflowMode: null,
|
|
711
|
+
adoptionMode: null,
|
|
712
|
+
essenceVersion: audit.summary.essenceVersion,
|
|
713
|
+
pageCount: audit.summary.pageCount,
|
|
714
|
+
runtimeAuditChecked: audit.summary.runtimeAuditChecked,
|
|
715
|
+
runtimePassed: audit.summary.runtimePassed,
|
|
716
|
+
packManifestPresent: audit.summary.packManifestPresent,
|
|
717
|
+
reviewPackPresent: audit.summary.reviewPackPresent
|
|
718
|
+
},
|
|
719
|
+
routes: {
|
|
720
|
+
declared: mcpCollectDeclaredRoutes(audit.essence),
|
|
721
|
+
runtimeChecked: audit.runtimeAudit.routeHintsChecked,
|
|
722
|
+
runtimeMatched: audit.runtimeAudit.routeHintsMatched,
|
|
723
|
+
runtimeCoverageOk: audit.summary.runtimeAuditChecked ? audit.runtimeAudit.routeHintsCoverageOk : null,
|
|
724
|
+
issues: findings.filter(
|
|
725
|
+
(finding) => finding.category.toLowerCase().includes("route") || finding.rule?.toLowerCase().includes("route") || finding.id.toLowerCase().includes("route")
|
|
726
|
+
).map((finding) => finding.message)
|
|
727
|
+
},
|
|
728
|
+
packs: {
|
|
729
|
+
manifestPresent: Boolean(manifest),
|
|
730
|
+
reviewPackPresent: Boolean(manifest?.review ?? audit.reviewPack),
|
|
731
|
+
scaffoldPackPresent: Boolean(manifest?.scaffold),
|
|
732
|
+
sectionPackCount: manifest?.sections.length ?? 0,
|
|
733
|
+
pagePackCount: manifest?.pages.length ?? 0,
|
|
734
|
+
mutationPackCount: manifest?.mutations?.length ?? 0,
|
|
735
|
+
generatedAt: typeof manifest?.generatedAt === "string" ? manifest.generatedAt : null
|
|
736
|
+
},
|
|
737
|
+
ci: {
|
|
738
|
+
recommendedCommand: "decantr health --ci --fail-on error",
|
|
739
|
+
failOn: "error"
|
|
740
|
+
},
|
|
741
|
+
findings
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
function resolveMcpProjectRoot(value) {
|
|
745
|
+
if (value == null) return process.cwd();
|
|
746
|
+
if (typeof value !== "string") {
|
|
747
|
+
throw new Error("project_path must be a string when provided.");
|
|
748
|
+
}
|
|
749
|
+
return resolveWorkspacePath(value);
|
|
750
|
+
}
|
|
751
|
+
async function getMcpHealthState(projectRoot) {
|
|
752
|
+
const { auditProject, createContractAssertions, createEvidenceBundle } = await import("@decantr/verifier");
|
|
753
|
+
const audit = await auditProject(projectRoot);
|
|
754
|
+
const assertions = createContractAssertions(projectRoot, audit);
|
|
755
|
+
const report = mcpReportFromAudit(projectRoot, audit, assertions);
|
|
756
|
+
const evidence = createEvidenceBundle({
|
|
757
|
+
projectRoot,
|
|
758
|
+
audit,
|
|
759
|
+
assertions,
|
|
760
|
+
report,
|
|
761
|
+
workspaceConfigPath: existsSync(join2(projectRoot, ".decantr", "workspace.json")) ? join2(projectRoot, ".decantr", "workspace.json") : null
|
|
762
|
+
});
|
|
763
|
+
return { audit, assertions, report, evidence };
|
|
764
|
+
}
|
|
765
|
+
function discoverMcpWorkspaceProjects(root, maxProjects = 500) {
|
|
766
|
+
const projects = [];
|
|
767
|
+
function walk(dir, depth) {
|
|
768
|
+
if (projects.length >= maxProjects || depth > 6) return;
|
|
769
|
+
if (existsSync(join2(dir, "decantr.essence.json"))) {
|
|
770
|
+
const path = relative2(root, dir).replace(/\\/g, "/") || ".";
|
|
771
|
+
projects.push({
|
|
772
|
+
id: path.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "project",
|
|
773
|
+
path,
|
|
774
|
+
absolutePath: dir
|
|
775
|
+
});
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
779
|
+
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
780
|
+
if (MCP_WORKSPACE_IGNORES.has(entry.name)) continue;
|
|
781
|
+
walk(join2(dir, entry.name), depth + 1);
|
|
782
|
+
if (projects.length >= maxProjects) return;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
walk(root, 0);
|
|
786
|
+
return projects.sort((a, b) => a.path.localeCompare(b.path));
|
|
787
|
+
}
|
|
788
|
+
async function getMcpWorkspaceHealth(args) {
|
|
789
|
+
const root = args.workspace_root == null ? process.cwd() : resolveMcpProjectRoot(args.workspace_root);
|
|
790
|
+
const maxProjects = typeof args.max_projects === "number" && Number.isFinite(args.max_projects) ? Math.max(1, Math.floor(args.max_projects)) : 500;
|
|
791
|
+
const discovered = discoverMcpWorkspaceProjects(root, maxProjects);
|
|
792
|
+
const projects = [];
|
|
793
|
+
for (const project of discovered) {
|
|
794
|
+
const startedAt = Date.now();
|
|
795
|
+
try {
|
|
796
|
+
const state = await getMcpHealthState(project.absolutePath);
|
|
797
|
+
projects.push({
|
|
798
|
+
id: project.id,
|
|
799
|
+
path: project.path,
|
|
800
|
+
status: state.report.status,
|
|
801
|
+
score: state.report.score,
|
|
802
|
+
errorCount: state.report.summary.errorCount,
|
|
803
|
+
warnCount: state.report.summary.warnCount,
|
|
804
|
+
infoCount: state.report.summary.infoCount,
|
|
805
|
+
findingCount: state.report.summary.findingCount,
|
|
806
|
+
durationMs: Date.now() - startedAt,
|
|
807
|
+
changed: false,
|
|
808
|
+
source: "auto",
|
|
809
|
+
error: null
|
|
810
|
+
});
|
|
811
|
+
} catch (error) {
|
|
812
|
+
projects.push({
|
|
813
|
+
id: project.id,
|
|
814
|
+
path: project.path,
|
|
815
|
+
status: "failed",
|
|
816
|
+
score: 0,
|
|
817
|
+
errorCount: 1,
|
|
818
|
+
warnCount: 0,
|
|
819
|
+
infoCount: 0,
|
|
820
|
+
findingCount: 1,
|
|
821
|
+
durationMs: Date.now() - startedAt,
|
|
822
|
+
changed: false,
|
|
823
|
+
source: "auto",
|
|
824
|
+
error: error.message
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
return {
|
|
829
|
+
$schema: MCP_WORKSPACE_HEALTH_SCHEMA_URL,
|
|
830
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
831
|
+
workspaceRoot: "<workspace>",
|
|
832
|
+
changedOnly: false,
|
|
833
|
+
since: null,
|
|
834
|
+
summary: {
|
|
835
|
+
projectCount: discovered.length,
|
|
836
|
+
checkedCount: projects.length,
|
|
837
|
+
healthyCount: projects.filter((project) => project.status === "healthy").length,
|
|
838
|
+
warningCount: projects.filter((project) => project.status === "warning").length,
|
|
839
|
+
errorCount: projects.filter((project) => project.status === "error").length,
|
|
840
|
+
failedCount: projects.filter((project) => project.status === "failed").length
|
|
841
|
+
},
|
|
842
|
+
projects
|
|
843
|
+
};
|
|
844
|
+
}
|
|
472
845
|
var TOOLS = [
|
|
473
846
|
// 1. decantr_read_essence — local read
|
|
474
847
|
{
|
|
@@ -588,6 +961,14 @@ var TOOLS = [
|
|
|
588
961
|
description: {
|
|
589
962
|
type: "string",
|
|
590
963
|
description: 'Description of the page or section (e.g. "dashboard with metrics and charts", "settings form with toggles")'
|
|
964
|
+
},
|
|
965
|
+
route: {
|
|
966
|
+
type: "string",
|
|
967
|
+
description: 'Optional route context, for example "/feed" or "/settings".'
|
|
968
|
+
},
|
|
969
|
+
source_code: {
|
|
970
|
+
type: "string",
|
|
971
|
+
description: "Optional local source excerpt to rank against actual code evidence."
|
|
591
972
|
}
|
|
592
973
|
},
|
|
593
974
|
required: ["description"]
|
|
@@ -785,7 +1166,31 @@ var TOOLS = [
|
|
|
785
1166
|
},
|
|
786
1167
|
annotations: READ_ONLY
|
|
787
1168
|
},
|
|
788
|
-
// 16.
|
|
1169
|
+
// 16. decantr_prepare_task_context — local read
|
|
1170
|
+
{
|
|
1171
|
+
name: "decantr_prepare_task_context",
|
|
1172
|
+
title: "Prepare Task Context",
|
|
1173
|
+
description: "Resolve compact Brownfield/Essence task-time context for a route or page before editing. Returns route, section, page pack, directives, patterns, shared components, visual target, health evidence, and local screenshot references when available.",
|
|
1174
|
+
inputSchema: {
|
|
1175
|
+
type: "object",
|
|
1176
|
+
properties: {
|
|
1177
|
+
route: {
|
|
1178
|
+
type: "string",
|
|
1179
|
+
description: 'Route being edited, for example "/feed". Preferred when known.'
|
|
1180
|
+
},
|
|
1181
|
+
page_id: {
|
|
1182
|
+
type: "string",
|
|
1183
|
+
description: "Page ID when route is unknown."
|
|
1184
|
+
},
|
|
1185
|
+
task: {
|
|
1186
|
+
type: "string",
|
|
1187
|
+
description: "Short task description used to rank relevant patterns and context."
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
},
|
|
1191
|
+
annotations: READ_ONLY
|
|
1192
|
+
},
|
|
1193
|
+
// 17. decantr_get_execution_pack — local read
|
|
789
1194
|
{
|
|
790
1195
|
name: "decantr_get_execution_pack",
|
|
791
1196
|
title: "Get Execution Pack",
|
|
@@ -940,6 +1345,82 @@ var TOOLS = [
|
|
|
940
1345
|
required: ["file_path"]
|
|
941
1346
|
},
|
|
942
1347
|
annotations: READ_ONLY_NETWORK
|
|
1348
|
+
},
|
|
1349
|
+
// 22. decantr_get_evidence_bundle — local reliability artifact
|
|
1350
|
+
{
|
|
1351
|
+
name: "decantr_get_evidence_bundle",
|
|
1352
|
+
title: "Get Evidence Bundle",
|
|
1353
|
+
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.",
|
|
1354
|
+
inputSchema: {
|
|
1355
|
+
type: "object",
|
|
1356
|
+
properties: {
|
|
1357
|
+
project_path: {
|
|
1358
|
+
type: "string",
|
|
1359
|
+
description: "Optional relative project path inside the active workspace. Defaults to the current working directory."
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
},
|
|
1363
|
+
annotations: READ_ONLY
|
|
1364
|
+
},
|
|
1365
|
+
// 23. decantr_workspace_health — local workspace reliability scan
|
|
1366
|
+
{
|
|
1367
|
+
name: "decantr_workspace_health",
|
|
1368
|
+
title: "Workspace Health",
|
|
1369
|
+
description: "Discover Decantr projects in the active workspace and return deterministic aggregate health for monorepos with many Decantr apps.",
|
|
1370
|
+
inputSchema: {
|
|
1371
|
+
type: "object",
|
|
1372
|
+
properties: {
|
|
1373
|
+
workspace_root: {
|
|
1374
|
+
type: "string",
|
|
1375
|
+
description: "Optional relative workspace root inside the active workspace. Defaults to the current working directory."
|
|
1376
|
+
},
|
|
1377
|
+
max_projects: {
|
|
1378
|
+
type: "number",
|
|
1379
|
+
description: "Optional cap on discovered projects. Defaults to 500."
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
},
|
|
1383
|
+
annotations: READ_ONLY
|
|
1384
|
+
},
|
|
1385
|
+
// 24. decantr_get_repair_prompt — local AI repair loop
|
|
1386
|
+
{
|
|
1387
|
+
name: "decantr_get_repair_prompt",
|
|
1388
|
+
title: "Get Repair Prompt",
|
|
1389
|
+
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.",
|
|
1390
|
+
inputSchema: {
|
|
1391
|
+
type: "object",
|
|
1392
|
+
properties: {
|
|
1393
|
+
project_path: {
|
|
1394
|
+
type: "string",
|
|
1395
|
+
description: "Optional relative project path inside the active workspace. Defaults to the current working directory."
|
|
1396
|
+
},
|
|
1397
|
+
finding_id: {
|
|
1398
|
+
type: "string",
|
|
1399
|
+
description: "Optional finding id. Defaults to the first error or warning, then the first finding."
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
},
|
|
1403
|
+
annotations: READ_ONLY
|
|
1404
|
+
},
|
|
1405
|
+
// 25. decantr_run_health_loop — local evidence + repair loop
|
|
1406
|
+
{
|
|
1407
|
+
name: "decantr_run_health_loop",
|
|
1408
|
+
title: "Run Health Loop",
|
|
1409
|
+
description: "Run Project Health, produce evidence, and return the next repair prompt for AI agents without uploading project source.",
|
|
1410
|
+
inputSchema: {
|
|
1411
|
+
type: "object",
|
|
1412
|
+
properties: {
|
|
1413
|
+
project_path: {
|
|
1414
|
+
type: "string",
|
|
1415
|
+
description: "Optional relative project path inside the active workspace. Defaults to the current working directory."
|
|
1416
|
+
},
|
|
1417
|
+
finding_id: {
|
|
1418
|
+
type: "string",
|
|
1419
|
+
description: "Optional finding id to target. Defaults to the first error or warning, then the first finding."
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
},
|
|
1423
|
+
annotations: READ_ONLY
|
|
943
1424
|
}
|
|
944
1425
|
];
|
|
945
1426
|
async function handleTool(name, args) {
|
|
@@ -1110,78 +1591,59 @@ async function handleTool(name, args) {
|
|
|
1110
1591
|
case "decantr_suggest_patterns": {
|
|
1111
1592
|
const err = validateStringArg(args, "description");
|
|
1112
1593
|
if (err) return { error: err };
|
|
1113
|
-
const desc = args.description
|
|
1594
|
+
const desc = args.description;
|
|
1595
|
+
const route = typeof args.route === "string" ? args.route : void 0;
|
|
1596
|
+
const sourceCode = typeof args.source_code === "string" ? args.source_code : void 0;
|
|
1114
1597
|
try {
|
|
1115
1598
|
const patternsResponse = await apiClient.listContent("patterns", {
|
|
1116
1599
|
namespace: "@official",
|
|
1117
|
-
limit:
|
|
1600
|
+
limit: 250
|
|
1118
1601
|
});
|
|
1119
|
-
const
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
if (desc.includes("landing") && ["hero", "cta-section", "card-grid"].includes(slug))
|
|
1139
|
-
score += 20;
|
|
1140
|
-
if (desc.includes("hero") && slug === "hero") score += 20;
|
|
1141
|
-
if (desc.includes("ecommerce") && ["card-grid", "filter-bar", "detail-header"].includes(slug))
|
|
1142
|
-
score += 15;
|
|
1143
|
-
if (desc.includes("product") && slug === "card-grid") score += 15;
|
|
1144
|
-
if (desc.includes("feed") && slug === "activity-feed") score += 15;
|
|
1145
|
-
if (desc.includes("filter") && slug === "filter-bar") score += 15;
|
|
1146
|
-
if (desc.includes("search") && slug === "filter-bar") score += 10;
|
|
1147
|
-
if (score > 0) {
|
|
1148
|
-
prelimScores.push({ slug, score, name: name2, description });
|
|
1149
|
-
}
|
|
1150
|
-
}
|
|
1151
|
-
prelimScores.sort((a, b) => b.score - a.score);
|
|
1152
|
-
const top10 = prelimScores.slice(0, 10);
|
|
1153
|
-
const suggestions = [];
|
|
1154
|
-
for (const candidate of top10) {
|
|
1155
|
-
let fullPattern = null;
|
|
1156
|
-
try {
|
|
1157
|
-
const fetched = await apiClient.getPattern("@official", candidate.slug);
|
|
1158
|
-
fullPattern = fetched;
|
|
1159
|
-
} catch {
|
|
1160
|
-
}
|
|
1161
|
-
let score = candidate.score;
|
|
1162
|
-
if (fullPattern) {
|
|
1163
|
-
const fullSearchable = [...fullPattern.components || [], ...fullPattern.tags || []].join(" ").toLowerCase();
|
|
1164
|
-
const words = desc.split(/\s+/);
|
|
1165
|
-
for (const word of words) {
|
|
1166
|
-
if (word.length < 3) continue;
|
|
1167
|
-
if (fullSearchable.includes(word)) score += 10;
|
|
1602
|
+
const preliminary = rankPatternCandidates(
|
|
1603
|
+
{ query: desc, route, code: sourceCode, limit: 12 },
|
|
1604
|
+
patternsResponse.items.map(
|
|
1605
|
+
(item) => patternToDiscoveryCandidate({
|
|
1606
|
+
id: item.slug || item.name || "pattern",
|
|
1607
|
+
slug: item.slug,
|
|
1608
|
+
name: item.name,
|
|
1609
|
+
description: item.description
|
|
1610
|
+
})
|
|
1611
|
+
)
|
|
1612
|
+
);
|
|
1613
|
+
const fullCandidates = await Promise.all(
|
|
1614
|
+
preliminary.map(async (match) => {
|
|
1615
|
+
const slug = match.candidate.slug || match.candidate.id;
|
|
1616
|
+
try {
|
|
1617
|
+
const fetched = await apiClient.getPattern("@official", slug);
|
|
1618
|
+
return patternToDiscoveryCandidate(fetched, { slug, source: "hosted" });
|
|
1619
|
+
} catch {
|
|
1620
|
+
return match.candidate;
|
|
1168
1621
|
}
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1622
|
+
})
|
|
1623
|
+
);
|
|
1624
|
+
const suggestions = rankPatternCandidates(
|
|
1625
|
+
{ query: desc, route, code: sourceCode, limit: 5 },
|
|
1626
|
+
fullCandidates
|
|
1627
|
+
).map((match) => {
|
|
1628
|
+
const pattern = match.candidate.pattern;
|
|
1629
|
+
const preset = pattern?.presets ? Object.values(pattern.presets)[0] : null;
|
|
1630
|
+
return {
|
|
1631
|
+
id: match.candidate.slug || match.candidate.id,
|
|
1632
|
+
score: match.score,
|
|
1633
|
+
name: match.candidate.name || match.candidate.slug || match.candidate.id,
|
|
1634
|
+
description: match.candidate.description || "",
|
|
1635
|
+
components: match.candidate.components || [],
|
|
1636
|
+
interactions: match.candidate.interactions || [],
|
|
1637
|
+
layout: preset?.layout ? preset.layout.layout : "unknown",
|
|
1638
|
+
reasons: match.reasons,
|
|
1639
|
+
matched_terms: match.matchedTerms
|
|
1640
|
+
};
|
|
1641
|
+
});
|
|
1181
1642
|
return {
|
|
1182
1643
|
query: args.description,
|
|
1183
|
-
|
|
1184
|
-
|
|
1644
|
+
route,
|
|
1645
|
+
suggestions,
|
|
1646
|
+
total: preliminary.length
|
|
1185
1647
|
};
|
|
1186
1648
|
} catch (e) {
|
|
1187
1649
|
return { error: `Could not fetch patterns: ${e.message}` };
|
|
@@ -1855,6 +2317,118 @@ async function handleTool(name, args) {
|
|
|
1855
2317
|
hosted_fallback_error: hostedFallbackError ?? void 0
|
|
1856
2318
|
};
|
|
1857
2319
|
}
|
|
2320
|
+
case "decantr_prepare_task_context": {
|
|
2321
|
+
const routeArg = typeof args.route === "string" ? args.route : void 0;
|
|
2322
|
+
const pageArg = typeof args.page_id === "string" ? args.page_id : void 0;
|
|
2323
|
+
const task = typeof args.task === "string" ? args.task : "";
|
|
2324
|
+
if (!routeArg && !pageArg) {
|
|
2325
|
+
return { error: "Provide route or page_id." };
|
|
2326
|
+
}
|
|
2327
|
+
let essence;
|
|
2328
|
+
try {
|
|
2329
|
+
const result = await readEssenceFile();
|
|
2330
|
+
essence = result.essence;
|
|
2331
|
+
} catch {
|
|
2332
|
+
return { error: "No valid essence file found. Run decantr init first." };
|
|
2333
|
+
}
|
|
2334
|
+
if (!isV42(essence)) {
|
|
2335
|
+
return {
|
|
2336
|
+
error: "Task context requires Essence v4.0.0. Run `decantr migrate --to v4` first."
|
|
2337
|
+
};
|
|
2338
|
+
}
|
|
2339
|
+
const routeEntry = routeArg ? essence.blueprint.routes?.[routeArg] : null;
|
|
2340
|
+
const sectionId = routeEntry?.section;
|
|
2341
|
+
const pageId = pageArg || routeEntry?.page;
|
|
2342
|
+
const section = sectionId ? essence.blueprint.sections.find((entry) => entry.id === sectionId) : essence.blueprint.sections.find(
|
|
2343
|
+
(entry) => entry.pages.some((page2) => page2.id === pageId)
|
|
2344
|
+
);
|
|
2345
|
+
const page = section?.pages.find((entry) => entry.id === pageId) ?? null;
|
|
2346
|
+
if (!section || !page || !pageId) {
|
|
2347
|
+
return {
|
|
2348
|
+
error: "Could not resolve route/page to an Essence section page.",
|
|
2349
|
+
available_routes: Object.keys(essence.blueprint.routes ?? {}).sort(),
|
|
2350
|
+
available_pages: essence.blueprint.sections.flatMap(
|
|
2351
|
+
(entry) => entry.pages.map((pageEntry) => ({ section_id: entry.id, page_id: pageEntry.id }))
|
|
2352
|
+
)
|
|
2353
|
+
};
|
|
2354
|
+
}
|
|
2355
|
+
const contextDir = join2(process.cwd(), ".decantr", "context");
|
|
2356
|
+
const manifest = readJsonIfExists(join2(contextDir, "pack-manifest.json"));
|
|
2357
|
+
const pageManifest = manifest?.pages.find((entry) => entry.id === pageId) ?? null;
|
|
2358
|
+
const sectionManifest = manifest?.sections.find((entry) => entry.id === section.id) ?? null;
|
|
2359
|
+
const pagePackJson = pageManifest ? readJsonIfExists(join2(contextDir, pageManifest.json)) : null;
|
|
2360
|
+
const sectionPackJson = sectionManifest ? readJsonIfExists(join2(contextDir, sectionManifest.json)) : null;
|
|
2361
|
+
const pagePackMarkdown = pageManifest && existsSync(join2(contextDir, pageManifest.markdown)) ? readFileSync(join2(contextDir, pageManifest.markdown), "utf-8") : null;
|
|
2362
|
+
const sectionContextPath = join2(contextDir, `section-${section.id}.md`);
|
|
2363
|
+
const sectionContext = existsSync(sectionContextPath) ? readFileSync(sectionContextPath, "utf-8") : null;
|
|
2364
|
+
const pagePackSummary = summarizePackJson(pagePackJson);
|
|
2365
|
+
const sectionPackSummary = summarizePackJson(sectionPackJson);
|
|
2366
|
+
const visualManifest = readJsonIfExists(join2(process.cwd(), ".decantr", "evidence", "visual-manifest.json"));
|
|
2367
|
+
const visualRoute = visualManifest?.routes?.find((entry) => entry.route === routeArg) ?? visualManifest?.routes?.find(
|
|
2368
|
+
(entry) => entry.screenshot?.includes(routeSlug(routeArg ?? pageId))
|
|
2369
|
+
) ?? null;
|
|
2370
|
+
const health = readJsonIfExists(join2(process.cwd(), ".decantr", "health-baseline-diff.json"));
|
|
2371
|
+
const themeInventory = readJsonIfExists(
|
|
2372
|
+
join2(process.cwd(), ".decantr", "theme-inventory.json")
|
|
2373
|
+
);
|
|
2374
|
+
const patternIds = extractPagePatternIds(page);
|
|
2375
|
+
const ranked = rankPatternCandidates(
|
|
2376
|
+
{
|
|
2377
|
+
query: [task, routeArg, page.description, ...patternIds].filter(Boolean).join(" "),
|
|
2378
|
+
limit: 5
|
|
2379
|
+
},
|
|
2380
|
+
patternIds.map((id) => patternToDiscoveryCandidate({ id, name: id, description: id }))
|
|
2381
|
+
);
|
|
2382
|
+
return {
|
|
2383
|
+
route: routeArg ?? null,
|
|
2384
|
+
page_id: pageId,
|
|
2385
|
+
section_id: section.id,
|
|
2386
|
+
section_role: section.role,
|
|
2387
|
+
shell: section.shell,
|
|
2388
|
+
task,
|
|
2389
|
+
visual_target: pagePackSummary.visualTarget ?? sectionPackSummary.visualTarget ?? essence.dna.personality?.join(". ") ?? null,
|
|
2390
|
+
directives: pagePackSummary.directives,
|
|
2391
|
+
patterns: pagePackSummary.patterns.length > 0 ? pagePackSummary.patterns : patternIds,
|
|
2392
|
+
ranked_patterns: ranked.map((match) => ({
|
|
2393
|
+
id: match.candidate.slug || match.candidate.id,
|
|
2394
|
+
score: match.score,
|
|
2395
|
+
reasons: match.reasons
|
|
2396
|
+
})),
|
|
2397
|
+
shared_components: pagePackSummary.sharedComponents,
|
|
2398
|
+
section_context: sectionContext,
|
|
2399
|
+
page_pack_excerpt: pagePackMarkdown ? pagePackMarkdown.slice(0, 12e3) : null,
|
|
2400
|
+
health_evidence: health ? {
|
|
2401
|
+
baseline_path: health.baselinePath,
|
|
2402
|
+
saved_at: health.savedAt,
|
|
2403
|
+
status_changed: health.statusChanged,
|
|
2404
|
+
score_delta: health.scoreDelta,
|
|
2405
|
+
added_findings: health.addedFindings?.slice(0, 8) ?? [],
|
|
2406
|
+
resolved_findings: health.resolvedFindings?.slice(0, 8) ?? [],
|
|
2407
|
+
changed_routes: health.changedRoutes ?? [],
|
|
2408
|
+
changed_screenshots: health.changedScreenshots ?? [],
|
|
2409
|
+
contract_drift: health.contractDrift ?? []
|
|
2410
|
+
} : null,
|
|
2411
|
+
visual_evidence: visualRoute ? {
|
|
2412
|
+
screenshot: visualRoute.screenshot ?? null,
|
|
2413
|
+
screenshot_hash: visualRoute.screenshotHash ?? null,
|
|
2414
|
+
status: visualRoute.status ?? null,
|
|
2415
|
+
error: visualRoute.error ?? null
|
|
2416
|
+
} : null,
|
|
2417
|
+
theme_inventory: themeInventory ? {
|
|
2418
|
+
modes: themeInventory.modes,
|
|
2419
|
+
variants: themeInventory.variants,
|
|
2420
|
+
path: ".decantr/theme-inventory.json"
|
|
2421
|
+
} : null,
|
|
2422
|
+
local_files: {
|
|
2423
|
+
page_pack: pageManifest?.markdown ?? null,
|
|
2424
|
+
section_pack: sectionManifest?.markdown ?? null,
|
|
2425
|
+
section_context: existsSync(sectionContextPath) ? `.decantr/context/section-${section.id}.md` : null,
|
|
2426
|
+
visual_manifest: existsSync(
|
|
2427
|
+
join2(process.cwd(), ".decantr", "evidence", "visual-manifest.json")
|
|
2428
|
+
) ? ".decantr/evidence/visual-manifest.json" : null
|
|
2429
|
+
}
|
|
2430
|
+
};
|
|
2431
|
+
}
|
|
1858
2432
|
case "decantr_get_execution_pack": {
|
|
1859
2433
|
const contextDir = join2(process.cwd(), ".decantr", "context");
|
|
1860
2434
|
const manifestPath = join2(contextDir, "pack-manifest.json");
|
|
@@ -2102,6 +2676,96 @@ async function handleTool(name, args) {
|
|
|
2102
2676
|
}
|
|
2103
2677
|
return auditProject(projectRoot);
|
|
2104
2678
|
}
|
|
2679
|
+
case "decantr_get_evidence_bundle": {
|
|
2680
|
+
try {
|
|
2681
|
+
const projectRoot = resolveMcpProjectRoot(args.project_path);
|
|
2682
|
+
const state = await getMcpHealthState(projectRoot);
|
|
2683
|
+
return state.evidence;
|
|
2684
|
+
} catch (error) {
|
|
2685
|
+
return { error: error.message };
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
2688
|
+
case "decantr_workspace_health": {
|
|
2689
|
+
if (args.workspace_root != null && typeof args.workspace_root !== "string") {
|
|
2690
|
+
return { error: "Invalid workspace_root. Must be a string when provided." };
|
|
2691
|
+
}
|
|
2692
|
+
if (args.max_projects != null && (typeof args.max_projects !== "number" || !Number.isFinite(args.max_projects))) {
|
|
2693
|
+
return { error: "Invalid max_projects. Must be a finite number when provided." };
|
|
2694
|
+
}
|
|
2695
|
+
try {
|
|
2696
|
+
return await getMcpWorkspaceHealth(args);
|
|
2697
|
+
} catch (error) {
|
|
2698
|
+
return { error: error.message };
|
|
2699
|
+
}
|
|
2700
|
+
}
|
|
2701
|
+
case "decantr_get_repair_prompt": {
|
|
2702
|
+
if (args.finding_id != null && typeof args.finding_id !== "string") {
|
|
2703
|
+
return { error: "Invalid finding_id. Must be a string when provided." };
|
|
2704
|
+
}
|
|
2705
|
+
try {
|
|
2706
|
+
const projectRoot = resolveMcpProjectRoot(args.project_path);
|
|
2707
|
+
const state = await getMcpHealthState(projectRoot);
|
|
2708
|
+
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;
|
|
2709
|
+
if (!finding) {
|
|
2710
|
+
return {
|
|
2711
|
+
project: state.evidence.project,
|
|
2712
|
+
health: state.evidence.health,
|
|
2713
|
+
prompt: null,
|
|
2714
|
+
message: "No Project Health findings require repair.",
|
|
2715
|
+
commands: ["decantr health --evidence"]
|
|
2716
|
+
};
|
|
2717
|
+
}
|
|
2718
|
+
return {
|
|
2719
|
+
project: state.evidence.project,
|
|
2720
|
+
health: state.evidence.health,
|
|
2721
|
+
finding: {
|
|
2722
|
+
id: finding.id,
|
|
2723
|
+
source: finding.source,
|
|
2724
|
+
severity: finding.severity,
|
|
2725
|
+
category: finding.category,
|
|
2726
|
+
message: finding.message
|
|
2727
|
+
},
|
|
2728
|
+
prompt: finding.remediation.prompt,
|
|
2729
|
+
commands: finding.remediation.commands
|
|
2730
|
+
};
|
|
2731
|
+
} catch (error) {
|
|
2732
|
+
return { error: error.message };
|
|
2733
|
+
}
|
|
2734
|
+
}
|
|
2735
|
+
case "decantr_run_health_loop": {
|
|
2736
|
+
if (args.finding_id != null && typeof args.finding_id !== "string") {
|
|
2737
|
+
return { error: "Invalid finding_id. Must be a string when provided." };
|
|
2738
|
+
}
|
|
2739
|
+
try {
|
|
2740
|
+
const projectRoot = resolveMcpProjectRoot(args.project_path);
|
|
2741
|
+
const state = await getMcpHealthState(projectRoot);
|
|
2742
|
+
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;
|
|
2743
|
+
return {
|
|
2744
|
+
project: state.evidence.project,
|
|
2745
|
+
health: state.evidence.health,
|
|
2746
|
+
report: state.report,
|
|
2747
|
+
evidence: state.evidence,
|
|
2748
|
+
repair: finding === null ? {
|
|
2749
|
+
finding: null,
|
|
2750
|
+
prompt: null,
|
|
2751
|
+
commands: ["decantr health --evidence"],
|
|
2752
|
+
message: "No Project Health findings require repair."
|
|
2753
|
+
} : {
|
|
2754
|
+
finding: {
|
|
2755
|
+
id: finding.id,
|
|
2756
|
+
source: finding.source,
|
|
2757
|
+
severity: finding.severity,
|
|
2758
|
+
category: finding.category,
|
|
2759
|
+
message: finding.message
|
|
2760
|
+
},
|
|
2761
|
+
prompt: finding.remediation.prompt,
|
|
2762
|
+
commands: finding.remediation.commands
|
|
2763
|
+
}
|
|
2764
|
+
};
|
|
2765
|
+
} catch (error) {
|
|
2766
|
+
return { error: error.message };
|
|
2767
|
+
}
|
|
2768
|
+
}
|
|
2105
2769
|
default:
|
|
2106
2770
|
return { error: `Unknown tool: ${name}` };
|
|
2107
2771
|
}
|
|
@@ -2261,7 +2925,7 @@ function describeUpdate(operation, payload) {
|
|
|
2261
2925
|
}
|
|
2262
2926
|
|
|
2263
2927
|
// src/index.ts
|
|
2264
|
-
var VERSION = "2.
|
|
2928
|
+
var VERSION = "2.2.0";
|
|
2265
2929
|
var server = new Server({ name: "decantr", version: VERSION }, { capabilities: { tools: {} } });
|
|
2266
2930
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
2267
2931
|
return { tools: TOOLS };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import "./chunk-
|
|
1
|
+
import "./chunk-SVLMT45O.js";
|
package/package.json
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decantr/mcp-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.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/
|
|
50
|
-
"@decantr/verifier": "2.
|
|
51
|
-
"@decantr/
|
|
52
|
+
"@decantr/essence-spec": "2.0.1",
|
|
53
|
+
"@decantr/verifier": "2.2.0",
|
|
54
|
+
"@decantr/registry": "2.2.0"
|
|
52
55
|
},
|
|
53
56
|
"scripts": {
|
|
54
57
|
"build": "tsup",
|