@decantr/cli 3.4.2 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -4
- package/dist/bin.js +4 -4
- package/dist/{chunk-32WTNJQU.js → chunk-LTNGAAV4.js} +749 -465
- package/dist/{chunk-2GCVVEQC.js → chunk-PM7DKABI.js} +27 -9
- package/dist/{chunk-BDA6TWV3.js → chunk-RJ6PT7WP.js} +36 -11
- package/dist/{chunk-PQKTJGYL.js → chunk-T45GFC6F.js} +80 -5
- package/dist/{heal-OKZ5QZN7.js → heal-YXWB6LTI.js} +1 -1
- package/dist/{health-U7O6PCLZ.js → health-RKUKPG72.js} +2 -2
- package/dist/index.js +4 -4
- package/dist/{studio-X565XJKV.js → studio-C2F72QL6.js} +352 -9
- package/dist/{workspace-NE232D4K.js → workspace-RJCFPVBL.js} +3 -3
- package/package.json +4 -4
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createWorkspaceHealthReport
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-RJ6PT7WP.js";
|
|
4
4
|
import {
|
|
5
5
|
createProjectHealthReport
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-T45GFC6F.js";
|
|
7
7
|
import {
|
|
8
8
|
sendStudioHealthRefreshedTelemetry,
|
|
9
9
|
sendStudioStartedTelemetry
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-PM7DKABI.js";
|
|
11
11
|
|
|
12
12
|
// src/commands/studio.ts
|
|
13
|
-
import { readFileSync } from "fs";
|
|
13
|
+
import { existsSync, readFileSync } from "fs";
|
|
14
14
|
import { createServer } from "http";
|
|
15
|
-
import { isAbsolute, resolve } from "path";
|
|
15
|
+
import { isAbsolute, join, resolve } from "path";
|
|
16
|
+
import { PROJECT_HEALTH_REPORT_V2_SCHEMA_URL } from "@decantr/verifier";
|
|
16
17
|
var GREEN = "\x1B[32m";
|
|
17
18
|
var CYAN = "\x1B[36m";
|
|
18
19
|
var RESET = "\x1B[0m";
|
|
19
|
-
var PROJECT_HEALTH_SCHEMA_URL =
|
|
20
|
+
var PROJECT_HEALTH_SCHEMA_URL = PROJECT_HEALTH_REPORT_V2_SCHEMA_URL;
|
|
20
21
|
function sendJson(res, status, value) {
|
|
21
22
|
const body = JSON.stringify(value, null, 2);
|
|
22
23
|
res.writeHead(status, {
|
|
@@ -45,9 +46,9 @@ function isRecord(value) {
|
|
|
45
46
|
function readProjectHealthReport(reportPath) {
|
|
46
47
|
const parsed = JSON.parse(readFileSync(reportPath, "utf-8"));
|
|
47
48
|
if (!isRecord(parsed) || parsed.$schema !== PROJECT_HEALTH_SCHEMA_URL) {
|
|
48
|
-
throw new Error("Report file is not a Decantr Project Health JSON document.");
|
|
49
|
+
throw new Error("Report file is not a Decantr Project Health v2 JSON document.");
|
|
49
50
|
}
|
|
50
|
-
if (typeof parsed.generatedAt !== "string" || typeof parsed.projectRoot !== "string" || !["healthy", "warning", "error"].includes(String(parsed.status)) || typeof parsed.score !== "number" || !isRecord(parsed.summary) || !isRecord(parsed.routes) || !isRecord(parsed.packs) || !isRecord(parsed.ci) || !Array.isArray(parsed.findings)) {
|
|
51
|
+
if (typeof parsed.generatedAt !== "string" || typeof parsed.projectRoot !== "string" || !["healthy", "warning", "error"].includes(String(parsed.status)) || typeof parsed.score !== "number" || !isRecord(parsed.summary) || !isRecord(parsed.routes) || !isRecord(parsed.packs) || !isRecord(parsed.loop) || !isRecord(parsed.authority) || !isRecord(parsed.evidenceTier) || !isRecord(parsed.ci) || !Array.isArray(parsed.findings)) {
|
|
51
52
|
throw new Error("Report file is not a Decantr Project Health JSON document.");
|
|
52
53
|
}
|
|
53
54
|
return parsed;
|
|
@@ -1147,6 +1148,294 @@ function workspaceStudioHtml() {
|
|
|
1147
1148
|
</body>
|
|
1148
1149
|
</html>`;
|
|
1149
1150
|
}
|
|
1151
|
+
function studioControlRoomPayload(report) {
|
|
1152
|
+
return {
|
|
1153
|
+
generatedAt: report.generatedAt,
|
|
1154
|
+
projectRoot: report.projectRoot,
|
|
1155
|
+
status: report.status,
|
|
1156
|
+
score: report.score,
|
|
1157
|
+
summary: report.summary,
|
|
1158
|
+
loop: report.loop,
|
|
1159
|
+
authority: report.authority,
|
|
1160
|
+
evidenceTier: report.evidenceTier,
|
|
1161
|
+
graph: report.graph,
|
|
1162
|
+
blockingFindings: report.findings.filter((finding) => finding.severity === "error" || finding.loopVerdict !== "verified").slice(0, 12).map((finding) => ({
|
|
1163
|
+
id: finding.id,
|
|
1164
|
+
severity: finding.severity,
|
|
1165
|
+
source: finding.source,
|
|
1166
|
+
category: finding.category,
|
|
1167
|
+
message: finding.message,
|
|
1168
|
+
graph: finding.graph,
|
|
1169
|
+
authorityLane: finding.authorityLane,
|
|
1170
|
+
loopVerdict: finding.loopVerdict,
|
|
1171
|
+
commands: finding.repairPlan?.commands ?? finding.remediation.commands
|
|
1172
|
+
}))
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
function studioTaskPreview(projectRoot, route, intent) {
|
|
1176
|
+
return {
|
|
1177
|
+
route,
|
|
1178
|
+
intent,
|
|
1179
|
+
command: route && intent ? `decantr task ${JSON.stringify(route)} ${JSON.stringify(intent)}` : 'decantr task <route> "<intent>"',
|
|
1180
|
+
notes: [
|
|
1181
|
+
"Run this before editing a route.",
|
|
1182
|
+
"If runtime source and Decantr context disagree, stop and report drift."
|
|
1183
|
+
],
|
|
1184
|
+
projectRoot
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
function readStudioProofReport(projectRoot) {
|
|
1188
|
+
const candidates = [
|
|
1189
|
+
join(projectRoot, ".decantr", "benchmarks", "proof-field-report.v2.json"),
|
|
1190
|
+
join(projectRoot, ".decantr", "proof-field-report.v2.json"),
|
|
1191
|
+
join(projectRoot, "docs", "benchmarks", "decantr-3-5-proof-field-report.json")
|
|
1192
|
+
];
|
|
1193
|
+
for (const candidate of candidates) {
|
|
1194
|
+
if (existsSync(candidate)) {
|
|
1195
|
+
return JSON.parse(readFileSync(candidate, "utf-8"));
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
return {
|
|
1199
|
+
$schema: "https://decantr.ai/schemas/proof-field-report.v2.json",
|
|
1200
|
+
schemaVersion: 2,
|
|
1201
|
+
status: "missing",
|
|
1202
|
+
message: "No local proof field report was found for this project.",
|
|
1203
|
+
searched: candidates
|
|
1204
|
+
};
|
|
1205
|
+
}
|
|
1206
|
+
function controlRoomHtml(reportMode = false, legacyRenderer) {
|
|
1207
|
+
void legacyRenderer;
|
|
1208
|
+
return `<!doctype html>
|
|
1209
|
+
<html lang="en">
|
|
1210
|
+
<head>
|
|
1211
|
+
<meta charset="utf-8">
|
|
1212
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
1213
|
+
<title>Decantr Control Room</title>
|
|
1214
|
+
<style>
|
|
1215
|
+
:root {
|
|
1216
|
+
color-scheme: dark;
|
|
1217
|
+
--bg: #111315;
|
|
1218
|
+
--surface: #181b1f;
|
|
1219
|
+
--surface-2: #20242a;
|
|
1220
|
+
--line: rgba(238, 241, 238, 0.13);
|
|
1221
|
+
--text: #eef1ee;
|
|
1222
|
+
--muted: #a9b1ac;
|
|
1223
|
+
--good: #6ae3a1;
|
|
1224
|
+
--warn: #f4c768;
|
|
1225
|
+
--bad: #ff7782;
|
|
1226
|
+
--accent: #8fd7ff;
|
|
1227
|
+
--ink: #0b0d0f;
|
|
1228
|
+
}
|
|
1229
|
+
* { box-sizing: border-box; }
|
|
1230
|
+
body {
|
|
1231
|
+
margin: 0;
|
|
1232
|
+
background: var(--bg);
|
|
1233
|
+
color: var(--text);
|
|
1234
|
+
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
1235
|
+
line-height: 1.45;
|
|
1236
|
+
}
|
|
1237
|
+
button { font: inherit; }
|
|
1238
|
+
code {
|
|
1239
|
+
display: inline-block;
|
|
1240
|
+
max-width: 100%;
|
|
1241
|
+
overflow-wrap: anywhere;
|
|
1242
|
+
border: 1px solid var(--line);
|
|
1243
|
+
background: #0e1012;
|
|
1244
|
+
color: var(--accent);
|
|
1245
|
+
padding: 0.25rem 0.4rem;
|
|
1246
|
+
border-radius: 6px;
|
|
1247
|
+
}
|
|
1248
|
+
.shell { min-height: 100vh; display: grid; grid-template-columns: minmax(220px, 280px) 1fr; }
|
|
1249
|
+
.rail { border-right: 1px solid var(--line); padding: 1rem; background: #14171a; position: sticky; top: 0; height: 100vh; }
|
|
1250
|
+
.brand { display: grid; gap: 0.2rem; margin-bottom: 1rem; }
|
|
1251
|
+
.brand h1 { font-size: 1.15rem; margin: 0; letter-spacing: 0; }
|
|
1252
|
+
.brand p { margin: 0; color: var(--muted); font-size: 0.85rem; }
|
|
1253
|
+
.tabs { display: grid; gap: 0.3rem; }
|
|
1254
|
+
.tab {
|
|
1255
|
+
width: 100%;
|
|
1256
|
+
min-height: 40px;
|
|
1257
|
+
border: 1px solid transparent;
|
|
1258
|
+
background: transparent;
|
|
1259
|
+
color: var(--muted);
|
|
1260
|
+
text-align: left;
|
|
1261
|
+
padding: 0.6rem 0.7rem;
|
|
1262
|
+
border-radius: 7px;
|
|
1263
|
+
cursor: pointer;
|
|
1264
|
+
}
|
|
1265
|
+
.tab.active, .tab:hover { color: var(--text); background: var(--surface-2); border-color: var(--line); }
|
|
1266
|
+
.main { padding: 1rem; display: grid; gap: 1rem; align-content: start; }
|
|
1267
|
+
.topbar { display: flex; justify-content: space-between; gap: 1rem; align-items: center; border-bottom: 1px solid var(--line); padding-bottom: 1rem; }
|
|
1268
|
+
.topbar h2 { margin: 0; font-size: 1.25rem; letter-spacing: 0; }
|
|
1269
|
+
.topbar p { margin: 0.2rem 0 0; color: var(--muted); font-size: 0.9rem; }
|
|
1270
|
+
.refresh {
|
|
1271
|
+
min-height: 38px;
|
|
1272
|
+
border: 1px solid var(--line);
|
|
1273
|
+
background: var(--surface-2);
|
|
1274
|
+
color: var(--text);
|
|
1275
|
+
border-radius: 7px;
|
|
1276
|
+
padding: 0 0.8rem;
|
|
1277
|
+
cursor: pointer;
|
|
1278
|
+
}
|
|
1279
|
+
.grid { display: grid; grid-template-columns: repeat(12, 1fr); gap: 0.8rem; }
|
|
1280
|
+
.panel {
|
|
1281
|
+
grid-column: span 6;
|
|
1282
|
+
border: 1px solid var(--line);
|
|
1283
|
+
background: var(--surface);
|
|
1284
|
+
border-radius: 8px;
|
|
1285
|
+
padding: 0.9rem;
|
|
1286
|
+
min-width: 0;
|
|
1287
|
+
}
|
|
1288
|
+
.panel.wide { grid-column: 1 / -1; }
|
|
1289
|
+
.panel h3 { margin: 0 0 0.6rem; font-size: 0.95rem; letter-spacing: 0; }
|
|
1290
|
+
.metric { display: flex; align-items: baseline; gap: 0.45rem; margin: 0.2rem 0; }
|
|
1291
|
+
.metric strong { font-size: 1.65rem; letter-spacing: 0; }
|
|
1292
|
+
.muted { color: var(--muted); }
|
|
1293
|
+
.status-healthy { color: var(--good); }
|
|
1294
|
+
.status-warning { color: var(--warn); }
|
|
1295
|
+
.status-error, .status-blocked { color: var(--bad); }
|
|
1296
|
+
.list { display: grid; gap: 0.55rem; }
|
|
1297
|
+
.row { border-top: 1px solid var(--line); padding-top: 0.55rem; min-width: 0; }
|
|
1298
|
+
.row:first-child { border-top: 0; padding-top: 0; }
|
|
1299
|
+
.row-title { display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center; }
|
|
1300
|
+
.pill { border: 1px solid var(--line); border-radius: 999px; padding: 0.1rem 0.45rem; color: var(--muted); font-size: 0.78rem; }
|
|
1301
|
+
.pre { white-space: pre-wrap; overflow-wrap: anywhere; color: var(--muted); margin: 0; }
|
|
1302
|
+
.hide { display: none; }
|
|
1303
|
+
@media (max-width: 820px) {
|
|
1304
|
+
.shell { grid-template-columns: 1fr; }
|
|
1305
|
+
.rail { position: static; height: auto; }
|
|
1306
|
+
.tabs { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
1307
|
+
.panel { grid-column: 1 / -1; }
|
|
1308
|
+
.topbar { align-items: stretch; flex-direction: column; }
|
|
1309
|
+
}
|
|
1310
|
+
</style>
|
|
1311
|
+
</head>
|
|
1312
|
+
<body>
|
|
1313
|
+
<div class="shell">
|
|
1314
|
+
<aside class="rail">
|
|
1315
|
+
<div class="brand">
|
|
1316
|
+
<h1>Decantr Control Room</h1>
|
|
1317
|
+
<p>${reportMode ? "Report artifact" : "Local project"}</p>
|
|
1318
|
+
</div>
|
|
1319
|
+
<nav class="tabs" aria-label="Studio views">
|
|
1320
|
+
<button class="tab active" data-view="control">Control</button>
|
|
1321
|
+
<button class="tab" data-view="routes">Routes</button>
|
|
1322
|
+
<button class="tab" data-view="graph">Graph</button>
|
|
1323
|
+
<button class="tab" data-view="authority">Authority</button>
|
|
1324
|
+
<button class="tab" data-view="evidence">Evidence</button>
|
|
1325
|
+
<button class="tab" data-view="repairs">Repairs</button>
|
|
1326
|
+
<button class="tab" data-view="ci">CI</button>
|
|
1327
|
+
</nav>
|
|
1328
|
+
</aside>
|
|
1329
|
+
<main class="main">
|
|
1330
|
+
<header class="topbar">
|
|
1331
|
+
<div>
|
|
1332
|
+
<h2 id="title">Control</h2>
|
|
1333
|
+
<p id="subtitle">Loading local health state...</p>
|
|
1334
|
+
</div>
|
|
1335
|
+
<button class="refresh" id="refresh" type="button">Refresh</button>
|
|
1336
|
+
</header>
|
|
1337
|
+
<section id="content" class="grid" aria-live="polite"></section>
|
|
1338
|
+
</main>
|
|
1339
|
+
</div>
|
|
1340
|
+
<script>
|
|
1341
|
+
const state = { view: 'control', health: null, control: null, resolve: null, evidence: null, graph: null, proof: null };
|
|
1342
|
+
const esc = (value) => String(value ?? '').replace(/[&<>"']/g, (char) => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[char]));
|
|
1343
|
+
const cls = (status) => 'status-' + String(status || 'warning').replace(/_/g, '-');
|
|
1344
|
+
async function getJson(path, options) {
|
|
1345
|
+
const response = await fetch(path, options);
|
|
1346
|
+
if (!response.ok) throw new Error(await response.text());
|
|
1347
|
+
return response.json();
|
|
1348
|
+
}
|
|
1349
|
+
async function load(refresh = false) {
|
|
1350
|
+
state.health = await getJson(refresh ? '/api/refresh' : '/api/health', refresh ? { method: 'POST' } : undefined);
|
|
1351
|
+
state.control = await getJson('/api/control-room');
|
|
1352
|
+
state.resolve = await getJson('/api/resolve');
|
|
1353
|
+
state.evidence = await getJson('/api/evidence');
|
|
1354
|
+
state.graph = await getJson('/api/graph-impact');
|
|
1355
|
+
state.proof = await getJson('/api/proof');
|
|
1356
|
+
render();
|
|
1357
|
+
}
|
|
1358
|
+
function panel(title, body, wide = false) {
|
|
1359
|
+
return '<article class="panel' + (wide ? ' wide' : '') + '"><h3>' + esc(title) + '</h3>' + body + '</article>';
|
|
1360
|
+
}
|
|
1361
|
+
function rows(items, empty) {
|
|
1362
|
+
if (!items || items.length === 0) return '<p class="muted">' + esc(empty) + '</p>';
|
|
1363
|
+
return '<div class="list">' + items.map((item) => '<div class="row">' + item + '</div>').join('') + '</div>';
|
|
1364
|
+
}
|
|
1365
|
+
function renderControl() {
|
|
1366
|
+
const report = state.health;
|
|
1367
|
+
const loop = report.loop || {};
|
|
1368
|
+
return [
|
|
1369
|
+
panel('Project Health', '<div class="metric"><strong class="' + cls(report.status) + '">' + esc(report.score) + '</strong><span>/100</span></div><p class="muted">' + esc(report.status) + ' | ' + esc(report.summary.findingCount) + ' finding(s)</p>'),
|
|
1370
|
+
panel('Loop State', '<div class="metric"><strong class="' + cls(loop.status) + '">' + esc(loop.state) + '</strong></div><p class="muted">' + esc(loop.nextActions?.[0] || loop.verdict) + '</p>'),
|
|
1371
|
+
panel('Authority Lane', '<p><strong>' + esc(report.authority?.activeLane) + '</strong></p><p class="muted">' + esc(report.authority?.summary) + '</p>', true),
|
|
1372
|
+
panel('Blocking Findings', rows(state.control?.blockingFindings?.map((finding) => '<div class="row-title"><strong>' + esc(finding.id) + '</strong><span class="pill">' + esc(finding.severity) + '</span><span class="pill">' + esc(finding.authorityLane) + '</span></div><p class="muted">' + esc(finding.message) + '</p>'), 'No blocking findings.'), true)
|
|
1373
|
+
].join('');
|
|
1374
|
+
}
|
|
1375
|
+
function renderRoutes() {
|
|
1376
|
+
const routes = state.health.routes || {};
|
|
1377
|
+
const items = (routes.declared || []).map((route) => '<div class="row-title"><strong>' + esc(route) + '</strong><code>decantr task ' + esc(JSON.stringify(route)) + ' "<intent>"</code></div>');
|
|
1378
|
+
return [
|
|
1379
|
+
panel('Declared Routes', rows(items, 'No declared routes.'), true),
|
|
1380
|
+
panel('Runtime Coverage', '<p class="muted">Checked ' + esc((routes.runtimeChecked || []).length) + ' route(s), matched ' + esc(routes.runtimeMatched) + '.</p><pre class="pre">' + esc((routes.issues || []).join('\\n')) + '</pre>', true)
|
|
1381
|
+
].join('');
|
|
1382
|
+
}
|
|
1383
|
+
function renderGraph() {
|
|
1384
|
+
const graph = state.graph?.graph || state.health.graph || {};
|
|
1385
|
+
const findings = state.graph?.findings || [];
|
|
1386
|
+
return [
|
|
1387
|
+
panel('Graph Impact', '<p><strong>' + esc(graph.current === false ? 'stale' : graph.ready ? 'ready' : 'missing') + '</strong></p><p class="muted">Snapshot ' + esc(graph.snapshotId || 'none') + ' | sources ' + esc(graph.sourceArtifactCount || 0) + '</p>'),
|
|
1388
|
+
panel('Anchored Findings', rows(findings.map((finding) => '<strong>' + esc(finding.id) + '</strong><p class="muted">' + esc(finding.graph?.node_type) + ' ' + esc(finding.graph?.node_id) + '</p>'), 'No graph-anchored findings.'))
|
|
1389
|
+
].join('');
|
|
1390
|
+
}
|
|
1391
|
+
function renderAuthority() {
|
|
1392
|
+
const resolution = state.resolve || {};
|
|
1393
|
+
return [
|
|
1394
|
+
panel('Order', rows((resolution.order || []).map((item) => '<strong>' + esc(item.rank) + '. ' + item.label + '</strong><p class="muted">' + esc(item.role) + '</p>'), 'No authority order.'), true),
|
|
1395
|
+
panel('Conflicts', rows((resolution.conflicts || []).map((item) => '<div class="row-title"><strong>' + esc(item.id) + '</strong><span class="pill">' + esc(item.status) + '</span></div><p class="muted">' + esc(item.message) + '</p><pre class="pre">' + esc((item.recommendedActions || []).map((action) => action.kind + (action.command ? ' -> ' + action.command : '')).join('\\n')) + '</pre>'), 'No authority conflicts.'), true)
|
|
1396
|
+
].join('');
|
|
1397
|
+
}
|
|
1398
|
+
function renderEvidence() {
|
|
1399
|
+
const tier = state.evidence?.evidenceTier || state.health.evidenceTier || {};
|
|
1400
|
+
return [
|
|
1401
|
+
panel('Evidence Tier', '<div class="metric"><strong>' + esc(tier.stage) + '</strong><span>' + esc(tier.confidence?.level) + ' ' + esc(tier.confidence?.score) + '</span></div><p class="muted">' + esc((tier.capabilities || []).join(', ')) + '</p>'),
|
|
1402
|
+
panel('Coverage', '<pre class="pre">' + esc(JSON.stringify(tier.coverage || {}, null, 2)) + '</pre>'),
|
|
1403
|
+
panel('Evidence Findings', rows((state.evidence?.findings || []).map((finding) => '<strong>' + esc(finding.id) + '</strong><p class="muted">' + esc(finding.source) + ' | ' + esc(finding.severity) + '</p>'), 'No evidence findings.'), true)
|
|
1404
|
+
].join('');
|
|
1405
|
+
}
|
|
1406
|
+
function renderRepairs() {
|
|
1407
|
+
return panel('Repair Plans', rows((state.health.findings || []).map((finding) => '<div class="row-title"><strong>' + esc(finding.id) + '</strong><span class="pill">' + esc(finding.severity) + '</span></div><p class="muted">' + esc(finding.remediation?.summary || finding.message) + '</p><pre class="pre">' + esc((finding.repairPlan?.commands || finding.remediation?.commands || []).join('\\n')) + '</pre>'), 'No repair plans.'), true);
|
|
1408
|
+
}
|
|
1409
|
+
function renderCi() {
|
|
1410
|
+
const proof = state.proof || {};
|
|
1411
|
+
return [
|
|
1412
|
+
panel('CI Command', '<code>' + esc(state.health.ci?.recommendedCommand) + '</code><p class="muted">Loop verify: ' + esc(state.health.loop?.verifyCommand) + '</p>', true),
|
|
1413
|
+
panel('Proof Report', '<pre class="pre">' + esc(JSON.stringify(proof.summary || proof.message || proof, null, 2)) + '</pre>', true)
|
|
1414
|
+
].join('');
|
|
1415
|
+
}
|
|
1416
|
+
function render() {
|
|
1417
|
+
const titles = { control: 'Control', routes: 'Routes', graph: 'Graph Impact', authority: 'Authority Resolver', evidence: 'Evidence', repairs: 'Repairs', ci: 'CI / Benchmarks' };
|
|
1418
|
+
document.getElementById('title').textContent = titles[state.view] || 'Control';
|
|
1419
|
+
document.getElementById('subtitle').textContent = (state.health?.projectRoot || '') + ' | ' + (state.health?.generatedAt || '');
|
|
1420
|
+
const renderers = { control: renderControl, routes: renderRoutes, graph: renderGraph, authority: renderAuthority, evidence: renderEvidence, repairs: renderRepairs, ci: renderCi };
|
|
1421
|
+
document.getElementById('content').innerHTML = (renderers[state.view] || renderControl)();
|
|
1422
|
+
}
|
|
1423
|
+
for (const button of document.querySelectorAll('.tab')) {
|
|
1424
|
+
button.addEventListener('click', () => {
|
|
1425
|
+
state.view = button.dataset.view;
|
|
1426
|
+
for (const tab of document.querySelectorAll('.tab')) tab.classList.toggle('active', tab === button);
|
|
1427
|
+
render();
|
|
1428
|
+
});
|
|
1429
|
+
}
|
|
1430
|
+
document.getElementById('refresh').addEventListener('click', () => load(true).catch((error) => alert(error.message)));
|
|
1431
|
+
load().catch((error) => {
|
|
1432
|
+
document.getElementById('subtitle').textContent = 'Load failed';
|
|
1433
|
+
document.getElementById('content').innerHTML = panel('Error', '<pre class="pre">' + esc(error.message) + '</pre>', true);
|
|
1434
|
+
});
|
|
1435
|
+
</script>
|
|
1436
|
+
</body>
|
|
1437
|
+
</html>`;
|
|
1438
|
+
}
|
|
1150
1439
|
function createStudioRequestHandler(projectRoot, options = {}) {
|
|
1151
1440
|
const reportPath = resolveReportPath(projectRoot, options.report);
|
|
1152
1441
|
const loadReport = () => reportPath ? readProjectHealthReport(reportPath) : createProjectHealthReport(projectRoot);
|
|
@@ -1159,7 +1448,7 @@ function createStudioRequestHandler(projectRoot, options = {}) {
|
|
|
1159
1448
|
sendHtml(res, workspaceStudioHtml());
|
|
1160
1449
|
return;
|
|
1161
1450
|
}
|
|
1162
|
-
sendHtml(res,
|
|
1451
|
+
sendHtml(res, controlRoomHtml(Boolean(reportPath), studioHtml));
|
|
1163
1452
|
return;
|
|
1164
1453
|
}
|
|
1165
1454
|
if (options.workspace && req.method === "GET" && url.pathname === "/api/workspace") {
|
|
@@ -1174,6 +1463,60 @@ function createStudioRequestHandler(projectRoot, options = {}) {
|
|
|
1174
1463
|
sendJson(res, 200, await loadReport());
|
|
1175
1464
|
return;
|
|
1176
1465
|
}
|
|
1466
|
+
if (req.method === "GET" && url.pathname === "/api/control-room") {
|
|
1467
|
+
sendJson(res, 200, studioControlRoomPayload(await loadReport()));
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1470
|
+
if (req.method === "GET" && url.pathname === "/api/resolve") {
|
|
1471
|
+
const report = await loadReport();
|
|
1472
|
+
sendJson(res, 200, report.authority);
|
|
1473
|
+
return;
|
|
1474
|
+
}
|
|
1475
|
+
if (req.method === "GET" && url.pathname === "/api/evidence") {
|
|
1476
|
+
const report = await loadReport();
|
|
1477
|
+
sendJson(res, 200, {
|
|
1478
|
+
evidenceTier: report.evidenceTier,
|
|
1479
|
+
findings: report.findings.map((finding) => ({
|
|
1480
|
+
id: finding.id,
|
|
1481
|
+
severity: finding.severity,
|
|
1482
|
+
source: finding.source,
|
|
1483
|
+
graph: finding.graph,
|
|
1484
|
+
evidenceTier: finding.evidenceTier,
|
|
1485
|
+
repairPlan: finding.repairPlan
|
|
1486
|
+
}))
|
|
1487
|
+
});
|
|
1488
|
+
return;
|
|
1489
|
+
}
|
|
1490
|
+
if (req.method === "GET" && url.pathname === "/api/graph-impact") {
|
|
1491
|
+
const report = await loadReport();
|
|
1492
|
+
sendJson(res, 200, {
|
|
1493
|
+
graph: report.graph,
|
|
1494
|
+
loopImpact: report.loop.graphImpact,
|
|
1495
|
+
findings: report.findings.filter((finding) => finding.graph).map((finding) => ({
|
|
1496
|
+
id: finding.id,
|
|
1497
|
+
severity: finding.severity,
|
|
1498
|
+
message: finding.message,
|
|
1499
|
+
graph: finding.graph
|
|
1500
|
+
}))
|
|
1501
|
+
});
|
|
1502
|
+
return;
|
|
1503
|
+
}
|
|
1504
|
+
if (req.method === "GET" && url.pathname === "/api/task-preview") {
|
|
1505
|
+
sendJson(
|
|
1506
|
+
res,
|
|
1507
|
+
200,
|
|
1508
|
+
studioTaskPreview(
|
|
1509
|
+
projectRoot,
|
|
1510
|
+
url.searchParams.get("route"),
|
|
1511
|
+
url.searchParams.get("intent")
|
|
1512
|
+
)
|
|
1513
|
+
);
|
|
1514
|
+
return;
|
|
1515
|
+
}
|
|
1516
|
+
if (req.method === "GET" && url.pathname === "/api/proof") {
|
|
1517
|
+
sendJson(res, 200, readStudioProofReport(projectRoot));
|
|
1518
|
+
return;
|
|
1519
|
+
}
|
|
1177
1520
|
if (req.method === "POST" && url.pathname === "/api/refresh") {
|
|
1178
1521
|
const startedAt = Date.now();
|
|
1179
1522
|
const report = await loadReport();
|
|
@@ -7,9 +7,9 @@ import {
|
|
|
7
7
|
listWorkspaceProjects,
|
|
8
8
|
parseWorkspaceArgs,
|
|
9
9
|
shouldFailWorkspaceHealth
|
|
10
|
-
} from "./chunk-
|
|
11
|
-
import "./chunk-
|
|
12
|
-
import "./chunk-
|
|
10
|
+
} from "./chunk-RJ6PT7WP.js";
|
|
11
|
+
import "./chunk-T45GFC6F.js";
|
|
12
|
+
import "./chunk-PM7DKABI.js";
|
|
13
13
|
export {
|
|
14
14
|
cmdWorkspace,
|
|
15
15
|
createWorkspaceHealthReport,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decantr/cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.0",
|
|
4
4
|
"description": "Decantr CLI - adopt, verify, graph, and govern frontend codebases touched by AI agents",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"decantr",
|
|
@@ -49,11 +49,11 @@
|
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"ajv": "^8.20.0",
|
|
52
|
-
"@decantr/core": "3.
|
|
53
|
-
"@decantr/essence-spec": "3.4.0",
|
|
52
|
+
"@decantr/core": "3.5.0",
|
|
54
53
|
"@decantr/registry": "3.4.0",
|
|
54
|
+
"@decantr/verifier": "3.5.0",
|
|
55
55
|
"@decantr/telemetry": "3.4.0",
|
|
56
|
-
"@decantr/
|
|
56
|
+
"@decantr/essence-spec": "3.4.0"
|
|
57
57
|
},
|
|
58
58
|
"scripts": {
|
|
59
59
|
"build": "tsup",
|