thurview 0.14.0 → 0.16.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 +140 -87
- package/dist/cli.js +24 -1
- package/dist/cli.js.map +1 -1
- package/dist/document/compile.js +107 -1
- package/dist/document/compile.js.map +1 -1
- package/dist/document/schema.js +17 -0
- package/dist/document/schema.js.map +1 -1
- package/dist/queue.js +123 -0
- package/dist/queue.js.map +1 -0
- package/dist/server/server.js +10 -10
- package/dist/server/server.js.map +1 -1
- package/dist/store.js +10 -0
- package/dist/store.js.map +1 -1
- package/dist/ui/app.css +25 -0
- package/dist/ui/app.js +141 -12
- package/dist/ui/app.js.map +2 -2
- package/package.json +1 -1
- package/skills/thurview/SKILL.md +32 -15
- package/skills/thurview/references/components.md +38 -0
- package/skills/thurview/references/document-authoring.md +19 -0
- package/skills/thurview/references/lifecycle.md +21 -0
- package/skills/thurview-design/SKILL.md +5 -5
- package/skills/{thurview/references/code-explainer.md → thurview-explain/SKILL.md} +95 -30
package/dist/ui/app.js
CHANGED
|
@@ -1135,6 +1135,7 @@
|
|
|
1135
1135
|
root.appendChild(
|
|
1136
1136
|
kind() === "explainer" ? coveragePanel(state.data?.coverage ?? null) : interfaceDelta(doc.interfaces)
|
|
1137
1137
|
);
|
|
1138
|
+
if (kind() === "review") root.appendChild(securityPanel(doc.security));
|
|
1138
1139
|
const layout = h("div", { class: "doc-layout" });
|
|
1139
1140
|
const toc = h(
|
|
1140
1141
|
"nav",
|
|
@@ -1208,6 +1209,7 @@
|
|
|
1208
1209
|
}
|
|
1209
1210
|
var DELTA_BLOCK = "interface-delta";
|
|
1210
1211
|
var COVERAGE_BLOCK = "coverage";
|
|
1212
|
+
var SECURITY_BLOCK = "security";
|
|
1211
1213
|
var DELTA_SHOWN = 12;
|
|
1212
1214
|
var CHANGE_CLASS = {
|
|
1213
1215
|
removed: "del",
|
|
@@ -1350,6 +1352,80 @@
|
|
|
1350
1352
|
wrap.appendChild(h("div", { class: "thread-pins" }, threads.map(threadPinRow)));
|
|
1351
1353
|
return wrap;
|
|
1352
1354
|
}
|
|
1355
|
+
function securityPanel(sec) {
|
|
1356
|
+
const wrap = h("div", { class: "block ifd", "data-block": SECURITY_BLOCK });
|
|
1357
|
+
const threads = threadsFor((t) => t.type === "document" && t.blockId === SECURITY_BLOCK);
|
|
1358
|
+
if (threads.length) wrap.classList.add("has-threads");
|
|
1359
|
+
const actions = h(
|
|
1360
|
+
"div",
|
|
1361
|
+
{ class: "block-actions" },
|
|
1362
|
+
h(
|
|
1363
|
+
"button",
|
|
1364
|
+
{
|
|
1365
|
+
class: threads.length ? "count" : "",
|
|
1366
|
+
title: "Comment on the trust boundaries",
|
|
1367
|
+
onclick: (e) => {
|
|
1368
|
+
if (state.viewingRevision !== null) return;
|
|
1369
|
+
popover(commentPopover({ type: "document", blockId: SECURITY_BLOCK }), {
|
|
1370
|
+
x: e.pageX + 10,
|
|
1371
|
+
y: e.pageY
|
|
1372
|
+
});
|
|
1373
|
+
}
|
|
1374
|
+
},
|
|
1375
|
+
threads.length ? String(threads.length) : "+"
|
|
1376
|
+
)
|
|
1377
|
+
);
|
|
1378
|
+
const body = h("div", { class: "ifd-body" });
|
|
1379
|
+
const toggle = h("span", { class: "heading-toggle" }, "hide");
|
|
1380
|
+
toggle.addEventListener("click", () => {
|
|
1381
|
+
const hidden = body.hidden;
|
|
1382
|
+
body.hidden = !hidden;
|
|
1383
|
+
toggle.textContent = hidden ? "hide" : "show";
|
|
1384
|
+
});
|
|
1385
|
+
const badge = !sec ? null : sec.state === "none" ? h("span", { class: "badge ok" }, "none") : sec.state === "pending" ? h("span", { class: "badge warn" }, "not assessed") : h("span", { class: "badge warn" }, `${sec.crossings.length} crossed`);
|
|
1386
|
+
wrap.appendChild(
|
|
1387
|
+
h(
|
|
1388
|
+
"div",
|
|
1389
|
+
{ class: "ifd-head" },
|
|
1390
|
+
h("span", { class: "t" }, "Trust boundaries"),
|
|
1391
|
+
badge,
|
|
1392
|
+
h("span", { class: "spacer" }),
|
|
1393
|
+
toggle,
|
|
1394
|
+
actions
|
|
1395
|
+
)
|
|
1396
|
+
);
|
|
1397
|
+
body.appendChild(
|
|
1398
|
+
h(
|
|
1399
|
+
"p",
|
|
1400
|
+
{ class: "ifd-verdict" },
|
|
1401
|
+
sec ? sec.verdict : "Unavailable: this revision carries no security dimension."
|
|
1402
|
+
)
|
|
1403
|
+
);
|
|
1404
|
+
for (const c of sec?.crossings ?? []) {
|
|
1405
|
+
const anchor = state.data?.document?.anchors[c.anchor];
|
|
1406
|
+
if (!anchor) continue;
|
|
1407
|
+
body.appendChild(
|
|
1408
|
+
h(
|
|
1409
|
+
"div",
|
|
1410
|
+
{
|
|
1411
|
+
class: "ifd-entry",
|
|
1412
|
+
title: `Open ${anchor.title}`,
|
|
1413
|
+
onclick: () => openAnchorPeek(anchor)
|
|
1414
|
+
},
|
|
1415
|
+
h("div", { class: "ifd-what" }, h("div", { class: "ifd-cap" }, c.boundary)),
|
|
1416
|
+
h(
|
|
1417
|
+
"span",
|
|
1418
|
+
{ class: "ifd-where mono" },
|
|
1419
|
+
anchor.peek ? `${anchor.peek.file}:${anchor.peek.from}` : anchor.title
|
|
1420
|
+
)
|
|
1421
|
+
)
|
|
1422
|
+
);
|
|
1423
|
+
}
|
|
1424
|
+
wrap.appendChild(body);
|
|
1425
|
+
if (threads.length)
|
|
1426
|
+
wrap.appendChild(h("div", { class: "thread-pins" }, threads.map(threadPinRow)));
|
|
1427
|
+
return wrap;
|
|
1428
|
+
}
|
|
1353
1429
|
function interfaceRow(e) {
|
|
1354
1430
|
const anchor = kind() === "design" && e.anchor ? state.data?.document?.anchors[e.anchor] : null;
|
|
1355
1431
|
return h(
|
|
@@ -2646,18 +2722,9 @@
|
|
|
2646
2722
|
async function home() {
|
|
2647
2723
|
clear(app);
|
|
2648
2724
|
const reviews = await api.reviews();
|
|
2649
|
-
const el = h("div", { class: "home" }, h("h2", null, "
|
|
2725
|
+
const el = h("div", { class: "home" }, h("h2", null, "Queue"));
|
|
2650
2726
|
const active = reviews.filter((r) => !r.dismissed);
|
|
2651
2727
|
const dismissed = reviews.filter((r) => r.dismissed);
|
|
2652
|
-
const item = (r) => h(
|
|
2653
|
-
"div",
|
|
2654
|
-
{ class: "item", onclick: () => location.href = `/review/${r.id}` },
|
|
2655
|
-
h("span", { class: "t" }, r.title),
|
|
2656
|
-
h("span", { class: `badge ${statusClass2(r.status)}` }, r.status),
|
|
2657
|
-
r.openThreads ? h("span", { class: "badge accent" }, `${r.openThreads} open`) : null,
|
|
2658
|
-
h("span", { class: "muted mono", style: { fontSize: "12px" } }, bindingLabel(r)),
|
|
2659
|
-
h("span", { class: "muted", style: { fontSize: "12px" } }, timeAgo(r.updatedAt))
|
|
2660
|
-
);
|
|
2661
2728
|
if (!active.length)
|
|
2662
2729
|
el.appendChild(
|
|
2663
2730
|
h(
|
|
@@ -2666,13 +2733,75 @@
|
|
|
2666
2733
|
"Nothing published yet. Ask your agent for a review of a change, an explainer of the codebase, or a design of what to build next."
|
|
2667
2734
|
)
|
|
2668
2735
|
);
|
|
2669
|
-
|
|
2736
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2737
|
+
for (const r of active) groups.set(r.queue.repo, [...groups.get(r.queue.repo) ?? [], r]);
|
|
2738
|
+
for (const [repo, rows] of groups) {
|
|
2739
|
+
const yours = rows.filter((r) => r.queue.turn === "you").length;
|
|
2740
|
+
el.appendChild(
|
|
2741
|
+
h(
|
|
2742
|
+
"h3",
|
|
2743
|
+
{ class: "repo" },
|
|
2744
|
+
repo,
|
|
2745
|
+
yours ? h("span", { class: "badge accent" }, `${yours} your turn`) : null
|
|
2746
|
+
)
|
|
2747
|
+
);
|
|
2748
|
+
rows.forEach((r) => el.appendChild(queueItem(r)));
|
|
2749
|
+
}
|
|
2670
2750
|
if (dismissed.length) {
|
|
2671
2751
|
el.appendChild(h("h3", { class: "muted" }, "Dismissed"));
|
|
2672
|
-
dismissed.forEach((r) => el.appendChild(
|
|
2752
|
+
dismissed.forEach((r) => el.appendChild(queueItem(r)));
|
|
2673
2753
|
}
|
|
2674
2754
|
app.appendChild(el);
|
|
2675
2755
|
}
|
|
2756
|
+
var TURN = { you: "your turn", agent: "agent", nobody: "nobody" };
|
|
2757
|
+
var DECISION = { approve: "approved", "request-changes": "changes requested", close: "closed" };
|
|
2758
|
+
function queueItem(r) {
|
|
2759
|
+
const q = r.queue;
|
|
2760
|
+
const cr = q.change;
|
|
2761
|
+
const age = q.factsAt ? `forge read ${timeAgo(q.factsAt)}` : cr ? "forge not read yet" : "";
|
|
2762
|
+
return h(
|
|
2763
|
+
"div",
|
|
2764
|
+
{ class: "item", onclick: () => location.href = `/review/${r.id}` },
|
|
2765
|
+
h("span", { class: `badge turn ${q.turn === "you" ? "accent" : ""}` }, TURN[q.turn]),
|
|
2766
|
+
h(
|
|
2767
|
+
"span",
|
|
2768
|
+
{ class: "t" },
|
|
2769
|
+
r.title,
|
|
2770
|
+
h("span", { class: "why" }, [r.kind ?? "review", q.why, age].filter(Boolean).join(" \xB7 "))
|
|
2771
|
+
),
|
|
2772
|
+
cr ? h(
|
|
2773
|
+
"a",
|
|
2774
|
+
{
|
|
2775
|
+
class: "mono muted cr",
|
|
2776
|
+
href: cr.url ?? void 0,
|
|
2777
|
+
target: "_blank",
|
|
2778
|
+
rel: "noopener",
|
|
2779
|
+
onclick: (e) => e.stopPropagation()
|
|
2780
|
+
},
|
|
2781
|
+
`#${cr.number}${cr.state && cr.state !== "open" ? ` ${cr.state}` : ""}`
|
|
2782
|
+
) : h("span", { class: "mono muted" }, bindingLabel(r)),
|
|
2783
|
+
r.openThreads ? h("span", { class: "badge accent" }, `${r.openThreads} open`) : null,
|
|
2784
|
+
q.pin ? h(
|
|
2785
|
+
"span",
|
|
2786
|
+
{ class: `badge ${q.pin === "behind" ? "warn" : "ok"}` },
|
|
2787
|
+
q.pin === "behind" ? "pin behind head" : "at head"
|
|
2788
|
+
) : null,
|
|
2789
|
+
q.decision ? h(
|
|
2790
|
+
"span",
|
|
2791
|
+
{
|
|
2792
|
+
class: `badge ${q.decision.posted === false ? "warn" : ""}`,
|
|
2793
|
+
title: q.decision.posted === null ? null : q.decision.posted ? "posted to the change request" : "not posted to the change request yet"
|
|
2794
|
+
},
|
|
2795
|
+
DECISION[q.decision.decision] + (q.decision.posted === null ? "" : q.decision.posted ? " \xB7 posted" : " \xB7 not posted")
|
|
2796
|
+
) : null,
|
|
2797
|
+
q.ci ? h(
|
|
2798
|
+
"span",
|
|
2799
|
+
{ class: `badge ${q.ci.trustworthy ? "ok" : "warn"}`, title: q.ci.verdict },
|
|
2800
|
+
q.ci.trustworthy ? "CI gates" : "CI not a gate"
|
|
2801
|
+
) : null,
|
|
2802
|
+
h("span", { class: "muted", style: { fontSize: "12px" } }, timeAgo(r.updatedAt))
|
|
2803
|
+
);
|
|
2804
|
+
}
|
|
2676
2805
|
function bindingLabel(r) {
|
|
2677
2806
|
if (r.binding.kind === "pr") return `PR #${r.binding.name}`;
|
|
2678
2807
|
if (r.binding.kind === "codebase") return r.binding.name === "**" ? "codebase" : r.binding.name;
|