@decantr/cli 3.4.1 → 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 +7 -4
- package/dist/bin.js +4 -4
- package/dist/{chunk-33IZQXZH.js → chunk-LTNGAAV4.js} +791 -469
- package/dist/{chunk-2GCVVEQC.js → chunk-PM7DKABI.js} +27 -9
- package/dist/{chunk-UGRAHWCG.js → chunk-RJ6PT7WP.js} +36 -11
- package/dist/{chunk-BDVI3SSP.js → chunk-T45GFC6F.js} +111 -6
- package/dist/{heal-OKZ5QZN7.js → heal-YXWB6LTI.js} +1 -1
- package/dist/{health-G7HQCDXX.js → health-RKUKPG72.js} +2 -2
- package/dist/index.js +4 -4
- package/dist/{studio-T73T4Q2V.js → studio-C2F72QL6.js} +352 -9
- package/dist/{workspace-3AJEXXUL.js → workspace-RJCFPVBL.js} +3 -3
- package/package.json +4 -4
- package/src/templates/DECANTR.md.template +24 -12
|
@@ -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.
|
|
52
|
+
"@decantr/core": "3.5.0",
|
|
53
53
|
"@decantr/registry": "3.4.0",
|
|
54
|
-
"@decantr/
|
|
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",
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
This project uses **Decantr** for AI Frontend Governance. Read this file before generating or editing UI code.
|
|
4
4
|
|
|
5
|
+
**Before editing any route, run `decantr task <route> "<intent>"`.** Use that task context as the working contract for the edit. If you are an AI assistant, do not start route-level source changes until task context has been loaded or the user explicitly tells you to proceed without it.
|
|
6
|
+
|
|
5
7
|
---
|
|
6
8
|
|
|
7
9
|
## What is Decantr?
|
|
@@ -16,13 +18,13 @@ Decantr is a Contract / Context / Evidence layer that sits between you (the AI c
|
|
|
16
18
|
|
|
17
19
|
### DNA (Design Axioms)
|
|
18
20
|
|
|
19
|
-
DNA defines the foundational design rules. **DNA violations are errors** -- they must never happen without updating the essence first.
|
|
21
|
+
DNA defines the foundational design rules. **DNA violations are blocking contract errors** -- they must never happen without updating the essence first.
|
|
20
22
|
|
|
21
23
|
DNA axioms include: Theme (id, mode, shape), Spacing (density, content gap), Typography (scale, weights), Color (palette, accent count), Radius (philosophy, base), Elevation (system, levels), Motion (preference, reduce-motion), Accessibility (WCAG level, focus-visible), and Personality traits.
|
|
22
24
|
|
|
23
25
|
### Blueprint (Structural Layout)
|
|
24
26
|
|
|
25
|
-
Blueprint defines sections, pages, routes, features, and pattern layouts. **Blueprint deviations are warnings** -- they should be corrected but do not block generation.
|
|
27
|
+
Blueprint defines sections, pages, routes, features, and pattern layouts. **Blueprint deviations are usually advisory structural warnings** -- they should be corrected or reviewed, but they do not normally block generation unless the active workflow makes them strict.
|
|
26
28
|
|
|
27
29
|
Blueprint includes: Sections (grouped by archetype with role, shell, and scoped features), Page definitions with layouts and pattern references, Routes (URL mapping), and Features (resolved from archetype union + blueprint overrides).
|
|
28
30
|
|
|
@@ -82,6 +84,15 @@ When a user request would violate guard rules:
|
|
|
82
84
|
|
|
83
85
|
`decantr.essence.json` is the structural spec. Tools and guards read this.
|
|
84
86
|
|
|
87
|
+
Authority order for this project:
|
|
88
|
+
|
|
89
|
+
1. In Brownfield and Hybrid workflows, the existing production source is the observed implementation truth.
|
|
90
|
+
2. Accepted `.decantr/local-patterns.json`, `.decantr/rules.json`, and `.decantr/style-bridge.json` are project-owned local law where present.
|
|
91
|
+
3. `decantr.essence.json` is the structural contract for routes, sections, DNA, guard mode, and intended product shape.
|
|
92
|
+
4. Hosted registry patterns and execution packs are guidance unless the project maps them into accepted local law.
|
|
93
|
+
|
|
94
|
+
When runtime source and Decantr context disagree, report the drift and run `decantr doctor` or `decantr verify --brownfield --local-patterns`; do not guess which side wins.
|
|
95
|
+
|
|
85
96
|
### Initial scaffolding
|
|
86
97
|
|
|
87
98
|
This project is using Decantr in **{{WORKFLOW_MODE}}** mode.
|
|
@@ -95,6 +106,8 @@ Then read `.decantr/context/section-{name}.md` for the fuller context. Prefer th
|
|
|
95
106
|
|
|
96
107
|
### Working on a route
|
|
97
108
|
|
|
109
|
+
Run `decantr task <route> "<intent>"` before editing. Read the files it lists first and preserve the authority block it prints.
|
|
110
|
+
|
|
98
111
|
Read `.decantr/context/page-{name}-pack.md` for the most local compiled route contract before editing a specific page. Route-local packs should win over broader narrative docs when there is any mismatch.
|
|
99
112
|
|
|
100
113
|
### Editing rules
|
|
@@ -114,21 +127,20 @@ Read `.decantr/context/page-{name}-pack.md` for the most local compiled route co
|
|
|
114
127
|
|
|
115
128
|
### Validation
|
|
116
129
|
|
|
117
|
-
Run `decantr
|
|
118
|
-
Run `decantr health` for the broader Project Health view before handoff, pull requests, or CI. Use `decantr health init-ci` to install the default GitHub Actions health gate, `decantr health --prompt <finding-id>` to generate a scoped remediation prompt for a specific issue, and `decantr studio` to inspect local drift, routes, findings, remediation, CI, and pack state in a localhost dashboard.
|
|
130
|
+
Run `decantr verify` for the broader Project Health view before handoff, pull requests, or CI. Use `decantr ci init` to install the default GitHub Actions gate, `decantr health --prompt <finding-id>` to generate a scoped remediation prompt for a specific issue, and `decantr studio` to inspect local drift, routes, findings, remediation, CI, and pack state in a localhost dashboard.
|
|
119
131
|
Declared command palettes and hotkeys must be implemented, not merely acknowledged.
|
|
120
132
|
|
|
121
133
|
### Quick Commands
|
|
122
134
|
|
|
123
135
|
```bash
|
|
124
|
-
decantr
|
|
125
|
-
decantr
|
|
126
|
-
decantr
|
|
127
|
-
decantr
|
|
128
|
-
decantr
|
|
129
|
-
decantr
|
|
130
|
-
decantr
|
|
131
|
-
decantr
|
|
136
|
+
decantr setup # Detect project state and next workflow step
|
|
137
|
+
decantr scan # Read-only Brownfield preview; writes no files
|
|
138
|
+
decantr adopt --yes # Attach Decantr to an existing app
|
|
139
|
+
decantr doctor # Explain current state, authority, and next steps
|
|
140
|
+
decantr task <route> "<intent>" # Load route/task context before AI edits
|
|
141
|
+
decantr verify --brownfield # Run local Project Health and drift checks
|
|
142
|
+
decantr ci init # Install the pinned CI gate
|
|
143
|
+
decantr studio # Open the local health dashboard
|
|
132
144
|
```
|
|
133
145
|
|
|
134
146
|
---
|