diffninja 0.3.1 → 0.3.2
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/dist/review/call-flow-html.d.ts +3 -1
- package/dist/review/call-flow-html.js +12 -11
- package/dist/review/connected-html.js +14 -2
- package/dist/review/html.d.ts +3 -2
- package/dist/review/html.js +8 -15
- package/dist/review/mcp.js +4 -7
- package/dist/review/process-html.d.ts +1 -5
- package/dist/review/process-html.js +3 -12
- package/dist/review/report-pages.js +1 -1
- package/dist/review/types.d.ts +4 -0
- package/dist/review/types.js +2 -1
- package/package.json +1 -1
|
@@ -21,7 +21,9 @@ import type { ReviewReport } from "./types.js";
|
|
|
21
21
|
* reaches the reviewer as a glyph plus color for a call that was added,
|
|
22
22
|
* removed, or contains a change, and plain dimmed text for an unchanged call.
|
|
23
23
|
*/
|
|
24
|
-
export declare function renderCallFlows(report: ReviewReport
|
|
24
|
+
export declare function renderCallFlows(report: ReviewReport, options?: {
|
|
25
|
+
readonly treeOnly?: boolean;
|
|
26
|
+
}): string;
|
|
25
27
|
export declare const CALL_FLOW_STYLES = "\n.cf {\n display: flex; flex-direction: column; gap: 12px; margin-top: 18px;\n --cf-add: #1a7f37; --cf-add-soft: #dafbe1;\n --cf-del: #cf222e; --cf-del-soft: #ffebe9;\n --cf-chg: #9a6700; --cf-chg-soft: #fff8c5;\n --cf-quiet: var(--ink-soft);\n}\n@media (prefers-color-scheme: dark) {\n .cf {\n --cf-add: #3fb950; --cf-add-soft: rgba(46, 160, 67, 0.15);\n --cf-del: #f85149; --cf-del-soft: rgba(248, 81, 73, 0.12);\n --cf-chg: #d29922; --cf-chg-soft: rgba(187, 128, 9, 0.15);\n }\n}\n/* Mode sections, folded files and the breadcrumb are toggled with the hidden\n attribute, so the module carries that rule itself instead of relying on the page. */\n.cf [hidden] { display: none !important; }\n.cf-head { display: flex; flex-direction: column; gap: 4px; }\n.cf-prov { font-size: 12px; color: var(--ink-soft); }\n.cf-note { font-size: 12px; color: var(--ink-soft); overflow-wrap: anywhere; }\n.cf-absence { padding: 16px 0; }\n.cf-bounds {\n margin: 10px 0 0;\n padding: 2px 0 2px 10px;\n border-left: 2px solid var(--cf-chg);\n color: var(--ink-soft);\n font-size: 12px;\n overflow-wrap: anywhere;\n}\n.cf-controls { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; }\n.cf-ready .cf-controls { position: sticky; top: var(--toolbar-height, 0px); z-index: 4; background: var(--bg); padding: 8px 0; }\n.cf, .cf-files, .cf-file-body, .cf-mode { min-width: 0; }\n.cf-modes {\n display: inline-flex;\n align-items: stretch;\n border: 1px solid var(--line);\n border-radius: 6px;\n overflow: hidden;\n background: var(--panel);\n}\n.cf-mode-link {\n display: inline-flex;\n align-items: center;\n padding: 5px 14px;\n font-size: 12px;\n color: var(--ink-soft);\n text-decoration: none;\n border-right: 1px solid var(--line);\n}\n.cf-mode-link:last-child { border-right: 0; }\n.cf-mode-link:hover, .cf-mode-link:focus-visible { color: var(--ink); background: var(--sunken); }\n.js .cf-mode-link[aria-current] { color: var(--ink); background: var(--sunken); font-weight: 600; }\n.cf-jump ul {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n list-style: none;\n margin: 0;\n padding: 0;\n max-height: 22vh;\n overflow-y: auto;\n}\n.cf-jump-link {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n max-width: 100%;\n min-width: 0;\n padding: 4px 10px 4px 9px;\n border: 1px solid var(--line);\n border-radius: 6px;\n background: var(--panel);\n color: var(--ink);\n font-size: 12px;\n text-decoration: none;\n}\n.cf-jump-link:hover, .cf-jump-link:focus-visible { border-color: var(--cursor); }\n.cf-jump-path { min-width: 0; max-width: 32ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.cf-jump-count { color: var(--ink-soft); white-space: nowrap; }\n.cf-crumbs {\n display: flex;\n align-items: center;\n flex-wrap: nowrap;\n gap: 6px;\n font-size: 12.5px;\n padding-bottom: 3px;\n overflow-x: auto;\n overscroll-behavior-x: contain;\n}\n.cf-crumb {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n flex: 0 0 auto;\n max-width: 34ch;\n font: inherit;\n font-size: 12px;\n padding: 3px 10px;\n border: 1px solid var(--line-strong);\n border-radius: 999px;\n background: var(--panel);\n color: var(--ink);\n cursor: pointer;\n white-space: nowrap;\n}\n.cf-crumb[aria-current] { border-color: var(--cursor); font-weight: 600; }\n.cf-crumb-label { overflow: hidden; text-overflow: ellipsis; }\n.cf-crumb-file {\n flex: 0 0 auto;\n max-width: 22ch;\n overflow: hidden;\n text-overflow: ellipsis;\n font-size: 11px;\n color: var(--ink-soft);\n}\n.cf-sep { flex: 0 0 auto; color: var(--ink-soft); }\n.cf-files { display: flex; flex-direction: column; gap: 12px; }\n.cf-file {\n background: var(--panel);\n border: 1px solid var(--line);\n border-left: 3px solid var(--line-strong);\n border-radius: 6px;\n overflow: hidden;\n}\n.cf-file { --cf-color: var(--ink-soft); --cf-fill: var(--sunken); }\n.cf-file-attention { border-left-color: var(--alarm); --cf-color: var(--alarm); --cf-fill: var(--alarm-bg); }\n.cf-file-uncertain { border-left-color: var(--warn); --cf-color: var(--warn); --cf-fill: var(--warn-bg); }\n.cf-file-low { border-left-color: var(--line-strong); }\n.cf-file-passed { border-left-color: var(--teal); --cf-color: var(--teal); --cf-fill: var(--teal-bg); }\n.cf-file-head {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 10px;\n padding: 8px 12px;\n background: var(--sunken);\n cursor: pointer;\n list-style: none;\n}\n.cf-file-head::-webkit-details-marker { display: none; }\n.cf-file-head::after {\n content: \"\u25B8\";\n margin-left: auto;\n font-size: 11px;\n color: var(--ink-soft);\n transition: transform 0.15s ease;\n}\n.cf-file[open] > .cf-file-head::after { transform: rotate(90deg); }\n.cf-file-head:focus-visible { outline: 2px solid var(--cursor); outline-offset: -2px; }\n.cf-file-path { flex: 1 1 260px; min-width: 0; font-size: 12.5px; font-weight: 600; overflow-wrap: anywhere; }\n.cf-file-count { font-size: 12px; color: var(--ink-soft); white-space: nowrap; }\n.cf-diff-link { font-size: 12px; white-space: nowrap; }\n.cf-file-body { border-top: 1px solid var(--line); padding: 0 12px 12px; }\n.cf-mode { padding-top: 12px; }\n.cf-noscript { display: block; }\n.cf-noscript .cf-note { margin: 6px 0 0; }\n.cf-mode + .cf-mode { margin-top: 12px; border-top: 1px dashed var(--line); }\n.cf-mode-head {\n margin: 0 0 8px;\n font-size: 11.5px;\n font-weight: 700;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n color: var(--ink-soft);\n}\n.cf-ready .cf-mode-head { display: none; }\n.cf-ready .cf-mode + .cf-mode { margin-top: 0; border-top: 0; }\n.cf-tree, .cf-children { list-style: none; margin: 0; padding: 0; }\n.cf-children { margin-left: 15px; padding-left: 10px; border-left: 1px dashed var(--line); }\n.cf-row { display: flex; align-items: center; flex-wrap: wrap; gap: 0 8px; padding: 2px 6px; border-radius: 6px; min-width: 0; }\n.cf-row:hover { background: var(--sunken); }\n.cf-fold > .cf-row { cursor: pointer; list-style: none; }\n.cf-fold > .cf-row::-webkit-details-marker { display: none; }\n.cf-fold > .cf-row::before { content: \"\u25B8\"; font-size: 10px; color: var(--ink-soft); transition: transform 0.15s ease; }\n.cf-fold[open] > .cf-row::before { transform: rotate(90deg); }\n.cf-badge, .cf-chip-badge {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: 0 0 auto;\n min-width: 16px;\n height: 16px;\n border: 1px solid transparent;\n border-radius: 4px;\n font-family: var(--mono);\n font-size: 11px;\n font-weight: 700;\n}\n.cf-badge, .cf-chip-badge { color: var(--cf-node-color); background: var(--cf-node-fill); }\n.cf-label {\n font: 400 12.5px/1.5 var(--mono);\n padding: 0 2px;\n border: 1px solid transparent;\n border-radius: 4px;\n background: none;\n text-align: left;\n /* Capped so a long call never pushes its row onto a second line; the\n location takes what is left and truncates first. */\n flex: 0 1 auto;\n min-width: 0;\n max-width: 62%;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.js .cf-label { cursor: pointer; }\n.js .cf-label:hover, .cf-label:focus-visible { text-decoration: underline; }\n.cf-label:focus-visible { outline: 2px solid var(--cursor); outline-offset: 1px; }\n.cf-loc { flex: 1 1 0; min-width: 0; font-size: 11px; color: var(--ink-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.cf-loc-none { font-style: italic; }\n.cf-node, .cf-gnode, .cf-chip { --cf-node-color: var(--cf-chg); --cf-node-fill: var(--cf-chg-soft); --cf-node-weight: 600; --cf-node-decoration: none; }\n.cf-st-added { --cf-node-color: var(--cf-add); --cf-node-fill: var(--cf-add-soft); }\n.cf-st-removed { --cf-node-color: var(--cf-del); --cf-node-fill: var(--cf-del-soft); --cf-node-decoration: line-through; }\n.cf-node.cf-st-same, .cf-gnode.cf-st-same, .cf-chip.cf-st-same { --cf-node-color: var(--ink-soft); --cf-node-fill: var(--sunken); --cf-node-weight: 400; }\n.cf-label { color: var(--cf-node-color); font-weight: var(--cf-node-weight); text-decoration: var(--cf-node-decoration); }\n.cf-node.cf-ancestor > .cf-fold > .cf-row { display: none; }\n.cf-node.cf-ancestor > .cf-fold > .cf-children { margin: 0; padding: 0; border: 0; }\n.cf-svg-wrap {\n max-width: 100%;\n overflow: auto;\n border: 1px solid var(--line);\n border-radius: 6px;\n background: var(--sunken);\n overscroll-behavior: contain;\n}\n.cf-svg { display: block; max-width: none; }\n.cf-ready .cf-svg-wrap { height: clamp(220px, 42vh, 400px); overflow: hidden; touch-action: none; cursor: grab; }\n.cf-diagram-btn, .cf-diagram-close {\n display: inline-flex; align-items: center; gap: 6px; height: 29px; padding: 0 12px;\n border: 1px solid var(--line); border-radius: 6px; background: var(--panel);\n font: 500 12px/1 var(--sans, inherit); color: var(--ink); cursor: pointer;\n}\n.cf-diagram-btn { color: var(--cursor); border-color: var(--cursor); }\n.cf-diagram-btn::after { content: \"\\2197\"; font-size: 12px; }\n.cf-diagram-btn:hover, .cf-diagram-close:hover { background: var(--sunken); }\n.cf-diagram-close { margin-left: auto; }\n.cf-diagram-hint { margin: 0 0 0 auto; font-size: 12px; color: var(--ink-soft); }\n.cf-diagram-hint + .cf-diagram-close { margin-left: 12px; }\n.cf.cf-diagram-open .cf-head { display: none; }\n/* The diagram view fills the browser window, and the graph gets nearly all of its height. */\n.cf.cf-diagram-open {\n position: fixed; inset: 0; z-index: 50; margin: 0; padding: 12px 20px 20px;\n background: var(--bg); overflow: auto; overscroll-behavior: contain;\n}\n.cf.cf-diagram-open .cf-controls { position: sticky; top: -12px; z-index: 5; background: var(--bg); padding: 10px 0; }\n.cf.cf-diagram-open .cf-modes, .cf.cf-diagram-open .cf-diagram-btn { display: none !important; }\n.cf.cf-diagram-open { padding-left: 292px; }\n.cf.cf-diagram-open .cf-svg-wrap { height: calc(100vh - 150px); min-height: 320px; }\n/* One canvas at a time: the entry point picked in the list, in its own file. */\n.cf.cf-diagram-open .cf-jump, .cf.cf-diagram-open .cf-bounds, .cf.cf-diagram-open .cf-omitted { display: none !important; }\n.cf.cf-diagram-open .cf-file:not(.cf-file-current) { display: none; }\n.cf.cf-diagram-open .cf-graph-frame:not(.cf-frame-current) { display: none; }\n.cf.cf-diagram-open .cf-file-body { padding-bottom: 0; }\n.cf-diagram-nav {\n position: fixed; top: 0; bottom: 0; left: 0; width: 276px; overflow-y: auto; z-index: 6;\n padding: 16px 12px; background: var(--sunken); border-right: 1px solid var(--line);\n}\n.cf-diagram-nav-head { margin: 0 0 10px; font-size: 13px; font-weight: 600; color: var(--ink); }\n.cf-diagram-nav-file {\n margin: 14px 0 4px; font: 600 12px var(--mono); color: var(--ink-soft);\n white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\n}\n.cf-diagram-nav ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }\n.cf-diagram-nav button {\n display: flex; flex-direction: column; align-items: flex-start; gap: 1px; width: 100%;\n padding: 6px 8px; border: 0; border-radius: 6px; background: none; color: var(--ink); text-align: left; cursor: pointer;\n}\n.cf-diagram-nav button:hover { background: var(--panel); }\n.cf-diagram-nav button[aria-current] { background: var(--panel); box-shadow: inset 3px 0 0 var(--cursor); }\n.cf-diagram-nav-name { max-width: 100%; font: 600 12px/1.35 var(--mono); overflow-wrap: anywhere; }\n.cf-diagram-nav-count { max-width: 100%; font-size: 11px; color: var(--ink-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n@media (max-width: 760px) {\n .cf.cf-diagram-open { padding-left: 12px; padding-top: 12px; }\n .cf-diagram-nav { position: static; width: auto; max-height: 30vh; margin: 0 0 8px; border: 1px solid var(--line); border-radius: 6px; }\n}\n.cf-ready .cf-svg-wrap:focus-visible { outline: 2px solid var(--cursor); outline-offset: 2px; }\n.cf-ready .cf-svg-wrap.cf-dragging { cursor: grabbing; user-select: none; }\n.cf-ready .cf-svg { width: 100%; height: 100%; }\n.cf-graph-frame + .cf-graph-frame { margin-top: 14px; }\n.cf-camera-tools { align-items: center; flex-wrap: wrap; gap: 5px; margin-bottom: 6px; }\n.cf-camera-tools button { min-height: 28px; padding: 0 10px; font-size: 12px; }\n.cf-scale { min-width: 4ch; text-align: center; font: 11px var(--mono); color: var(--ink-soft); }\n.cf-omitted { display: none; }\n.cf-ready .cf-omitted { display: block; }\n.cf-node.cf-selected > .cf-row, .cf-node.cf-selected > .cf-fold > .cf-row,\n.cf-chip.cf-selected { outline: 2px solid var(--cursor); outline-offset: 1px; border-radius: 5px; }\n.cf-edge { fill: none; stroke: var(--line-strong); stroke-width: 1.5; }\n.cf-gnode rect { fill: var(--panel); stroke: var(--line-strong); stroke-width: 1.5; }\n.cf-gnode:focus-visible { outline: none; }\n.cf-gzoom circle { fill: var(--panel); stroke: var(--line-strong); }\n.cf-gzoom text { fill: var(--ink); font: 14px var(--mono); }\n.cf-gzoom:hover circle, .cf-gzoom:focus-visible circle { stroke: var(--cursor); stroke-width: 2; }\n.cf-gzoom:focus-visible { outline: none; }\n.cf-glabel { font-family: var(--mono); font-size: 12px; fill: var(--ink); }\n.cf-gloc { font-family: var(--mono); font-size: 10px; fill: var(--ink-soft); }\n.cf-gnode.cf-st-same rect { stroke: var(--line); fill: transparent; }\n.cf-gnode.cf-st-same .cf-glabel { fill: var(--ink-soft); }\n.cf-gnode:not(.cf-st-same) rect { stroke: var(--cf-node-color); fill: var(--panel); }\n.cf-gnode:not(.cf-st-same) .cf-glabel { fill: var(--cf-node-color); font-weight: 600; }\n.cf-gnode:hover rect, .cf-gnode:focus-visible rect { stroke: var(--cursor); stroke-width: 2.5; }\n.cf-gnode.cf-selected rect { stroke: var(--cursor); stroke-width: 3; }\n.cf-paths { display: flex; flex-direction: column; gap: 6px; list-style: none; margin: 0; padding: 0; }\n.cf-path {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 4px 6px;\n padding: 6px 8px;\n border: 1px solid var(--line);\n border-radius: 6px;\n background: var(--panel);\n}\n.cf-chip {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n max-width: 100%;\n min-width: 0;\n padding: 2px 8px 2px 4px;\n border: 1px solid var(--line);\n border-left: 3px solid var(--cf-node-color);\n border-radius: 6px;\n background: var(--panel);\n color: var(--ink);\n}\n.cf-chip:hover, .cf-chip:focus-within { border-color: var(--cursor); }\n.cf-chip-zoom {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n max-width: 100%;\n min-width: 0;\n color: inherit;\n text-decoration: none;\n}\n.cf-chip-label { min-width: 0; max-width: 36ch; font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.cf-chip-loc { min-width: 0; max-width: 28ch; font-size: 10.5px; color: var(--ink-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.cf-chip.cf-st-same .cf-chip-label { color: var(--ink-soft); }\n.cf-chip:not(.cf-st-same) .cf-chip-label { color: var(--cf-node-color); font-weight: 600; }\n.cf-chip .cf-desc { max-width: 28ch; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.cf-arrow { color: var(--ink-soft); font-size: 12px; }\n/* One description line per call, only when the backend attached one. */\n.cf-desc { font-size: 11.5px; color: var(--ink-soft); font-style: italic; overflow-wrap: anywhere; }\n.cf-business { display: flex; flex-direction: column; gap: 6px; margin: 4px 0 10px; }\n.cf-business-note { font-size: 13px; color: var(--ink-soft); }\n.cf-plumbing-toggle { display: inline-flex; align-items: center; gap: 7px; font-size: 13px; color: var(--ink-soft); cursor: pointer; width: fit-content; }\n.cf:has(.cf-show-plumbing:not(:checked)) .cf-node.cf-plumbing { display: none; }\n.cf-purpose { font-family: var(--sans); font-size: 13.5px; font-weight: 550; color: var(--ink); overflow-wrap: anywhere; }\n.cf-row > .cf-purpose { flex: 1 0 100%; order: -1; padding-left: 26px; }\n.cf-fold > .cf-row > .cf-purpose { padding-left: 16px; }\n.cf-node.cf-explained > .cf-row .cf-label, .cf-node.cf-explained > .cf-fold > .cf-row .cf-label { font-size: 11.5px; }\n.cf-chip .cf-purpose { font-size: 12.5px; max-width: 34ch; }\n.cf-gpurpose { font-family: var(--sans); font-size: 12px; font-weight: 600; fill: var(--ink); }\n.cf-diagram-nav-name.cf-diagram-nav-purpose { font-family: var(--sans); white-space: normal; }\n.cf-row > .cf-desc { flex: 1 0 100%; order: 5; padding-left: 26px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.cf-row > .cf-src-link { order: 4; }\n.cf-depth { align-items: center; gap: 4px; }\n.cf-depth-label { font-size: 12px; color: var(--ink-soft); }\n.cf-depth-btn { padding: 3px 9px; font-size: 12px; }\n.cf-depth-btn[aria-pressed=\"true\"] { border-color: var(--cursor); color: var(--cursor); font-weight: 600; }\n/* Numbered, clickable call order on the graph edges. */\n.cf-edge-num circle { fill: var(--panel); stroke: var(--line-strong); stroke-width: 1.5; }\n.cf-edge-num text { font-family: var(--mono); font-size: 10.5px; fill: var(--ink-soft); }\n.cf-edge-num:hover circle, .cf-edge-num:focus-visible circle { stroke: var(--cursor); stroke-width: 2; }\n.cf-edge-num:hover text, .cf-edge-num:focus-visible text { fill: var(--cursor); font-weight: 700; }\n.cf-edge-num:focus-visible { outline: none; }\n.cf-gdesc { font-family: var(--mono); font-size: 10px; font-style: italic; fill: var(--ink-soft); }\n/* Resolved definition of one call: a native disclosure without the script, and\n the body the script clones into its single shared panel. */\n.cf-source { margin: 3px 0 3px 24px; }\n.cf-ready .cf-source { display: none; }\n.cf-src-sum { width: fit-content; cursor: pointer; list-style: none; font-size: 11.5px; color: var(--ink-soft); }\n.cf-src-sum::-webkit-details-marker { display: none; }\n.cf-src-sum::before { content: \"\u25B8\"; margin-right: 6px; font-size: 10px; transition: transform 0.15s ease; display: inline-block; }\n.cf-source[open] > .cf-src-sum::before { transform: rotate(90deg); }\n.cf-src-sum:hover, .cf-src-sum:focus-visible { color: var(--cursor); }\n.cf-src-body { padding: 6px 0 4px; }\n.cf-src-ref { margin: 0 0 5px; font-size: 11.5px; color: var(--ink-soft); overflow-wrap: anywhere; }\n.cf-src-code {\n margin: 0;\n padding: 6px 9px;\n border: 1px solid var(--line);\n border-radius: 6px;\n background: var(--sunken);\n font-family: var(--mono);\n font-size: 11.5px;\n line-height: 1.5;\n overflow-x: auto;\n}\n.cf-src-line { display: block; white-space: pre; }\n.cf-src-no {\n display: inline-block;\n width: 4ch;\n margin-right: 10px;\n text-align: right;\n color: var(--ink-soft);\n user-select: none;\n}\n.cf-src-note { margin: 0; font-size: 11.5px; color: var(--ink-soft); font-style: italic; }\n.cf-src-panel {\n padding: 8px 12px 10px;\n border: 1px solid var(--line-strong);\n border-radius: 6px;\n background: var(--panel);\n}\n.cf-src-panel-sum { width: fit-content; cursor: pointer; font-size: 12.5px; font-weight: 600; }\n.cf-src-panel-body { padding-top: 6px; }\n.cf-src-panel-body { max-height: 40vh; overflow: auto; }\n.cf-src-head-line { margin: 0 0 6px; font-size: 12px; color: var(--ink-soft); overflow-wrap: anywhere; }\n.cf-src-missing { margin: 0; }\n.cf-src-link {\n flex: 0 0 auto;\n font-size: 11px;\n color: var(--ink-soft);\n text-decoration: none;\n white-space: nowrap;\n padding: 0 6px;\n border: 1px solid var(--line);\n border-radius: 999px;\n}\n.cf-src-link:hover { text-decoration: none; }\n.cf-src-link:hover, .cf-src-link:focus-visible { color: var(--cursor); }\n@media (max-width: 680px) {\n .cf-jump ul { max-height: 38vh; }\n .cf-jump-link { width: 100%; }\n .cf-jump-path { max-width: none; flex: 1 1 auto; }\n .cf-file-body { padding: 0 11px 12px; }\n .cf-crumb { max-width: 24ch; }\n .cf-crumb-file { max-width: 12ch; }\n .cf-source { margin-left: 10px; }\n .cf-src-no { width: 3ch; margin-right: 7px; }\n .cf-chip, .cf-chip-zoom { min-width: 0; flex-wrap: wrap; }\n .cf-chip-loc { overflow-wrap: anywhere; }\n}\n";
|
|
26
28
|
/**
|
|
27
29
|
* Progressive enhancement for the rendered call flow. Nothing here is
|
|
@@ -24,7 +24,8 @@ import { nodeFunctionId, purposesOf, wrapPurpose } from "./explanation.js";
|
|
|
24
24
|
* reaches the reviewer as a glyph plus color for a call that was added,
|
|
25
25
|
* removed, or contains a change, and plain dimmed text for an unchanged call.
|
|
26
26
|
*/
|
|
27
|
-
export function renderCallFlows(report) {
|
|
27
|
+
export function renderCallFlows(report, options = {}) {
|
|
28
|
+
const treeOnly = options.treeOnly === true;
|
|
28
29
|
const purposes = purposesOf(report);
|
|
29
30
|
const files = collectFiles(report.callFlows, report.items, purposes);
|
|
30
31
|
if (files.length === 0) {
|
|
@@ -35,7 +36,7 @@ export function renderCallFlows(report) {
|
|
|
35
36
|
'<div class="cf">',
|
|
36
37
|
renderSummary(files, new Set(report.items.map(item => item.file)).size),
|
|
37
38
|
renderPlumbingToggle(plumbing, report.agentExplanation?.explainedBy),
|
|
38
|
-
renderControls(),
|
|
39
|
+
treeOnly ? "" : renderControls(),
|
|
39
40
|
renderJump(files),
|
|
40
41
|
'<nav class="cf-crumbs" id="cf-crumbs" aria-label="Visited call trail" hidden></nav>',
|
|
41
42
|
// One panel the script fills with the focused call's definition; without the
|
|
@@ -45,7 +46,7 @@ export function renderCallFlows(report) {
|
|
|
45
46
|
'<div class="cf-src-panel-body" id="cf-src-panel-body"></div>',
|
|
46
47
|
"</details>",
|
|
47
48
|
'<div class="cf-files">',
|
|
48
|
-
files.map((view, index) => renderFile(view, index + 1)).join("\n"),
|
|
49
|
+
files.map((view, index) => renderFile(view, index + 1, treeOnly)).join("\n"),
|
|
49
50
|
"</div>",
|
|
50
51
|
"</div>",
|
|
51
52
|
].join("\n");
|
|
@@ -267,7 +268,7 @@ function renderJump(files) {
|
|
|
267
268
|
].join("\n");
|
|
268
269
|
}
|
|
269
270
|
/** One file section: severity, path, call count and its diff link. */
|
|
270
|
-
function renderFile(view, at) {
|
|
271
|
+
function renderFile(view, at, treeOnly) {
|
|
271
272
|
const { dot, tone, count } = fileFacts(view);
|
|
272
273
|
const status = view.status;
|
|
273
274
|
const diff = `<a class="cf-diff-link" data-open-hunk href="#item-${view.rank}">View diff #${view.rank}</a>`;
|
|
@@ -284,8 +285,8 @@ function renderFile(view, at) {
|
|
|
284
285
|
view.truncated
|
|
285
286
|
? `<p class="cf-bounds">Bounds reached for this file: ${escapeHtml(BOUNDS_TEXT)}. Calls cut at a bound are omitted.</p>`
|
|
286
287
|
: "",
|
|
287
|
-
modesNote(),
|
|
288
|
-
renderModes(view, at),
|
|
288
|
+
treeOnly ? "" : modesNote(),
|
|
289
|
+
treeOnly ? renderTreeMode(view, at, false) : renderModes(view, at),
|
|
289
290
|
"</div>",
|
|
290
291
|
"</details>",
|
|
291
292
|
]
|
|
@@ -303,7 +304,7 @@ function renderFile(view, at) {
|
|
|
303
304
|
*/
|
|
304
305
|
function renderModes(view, at) {
|
|
305
306
|
return [
|
|
306
|
-
renderTreeMode(view, at),
|
|
307
|
+
renderTreeMode(view, at, true),
|
|
307
308
|
lazyMode("graph", at, renderGraphMode(view, at)),
|
|
308
309
|
lazyMode("sequence", at, renderSequenceMode(view, at)),
|
|
309
310
|
].join("\n");
|
|
@@ -321,10 +322,10 @@ function modesNote() {
|
|
|
321
322
|
`resolved definition opens on its own. The diff holds every changed line.` +
|
|
322
323
|
`</p></noscript>`);
|
|
323
324
|
}
|
|
324
|
-
function modeSection(mode, at, body) {
|
|
325
|
+
function modeSection(mode, at, body, headed = true) {
|
|
325
326
|
return [
|
|
326
327
|
`<section class="cf-mode cf-mode-${mode}" id="cf-f${at}-${mode}" data-cf-mode-body="${mode}">`,
|
|
327
|
-
`<h3 class="cf-mode-head">${MODE_LABEL[mode]}</h3
|
|
328
|
+
headed ? `<h3 class="cf-mode-head">${MODE_LABEL[mode]}</h3>` : "",
|
|
328
329
|
body,
|
|
329
330
|
"</section>",
|
|
330
331
|
].join("\n");
|
|
@@ -333,11 +334,11 @@ function modeSection(mode, at, body) {
|
|
|
333
334
|
function lazyMode(mode, at, section) {
|
|
334
335
|
return `<template data-cf-lazy-mode="${mode}" data-cf-file="${at}">${section}</template>`;
|
|
335
336
|
}
|
|
336
|
-
function renderTreeMode(view, at) {
|
|
337
|
+
function renderTreeMode(view, at, headed) {
|
|
337
338
|
const trees = view.trees
|
|
338
339
|
.map((tree, root) => renderTreeNode(tree, [root], at, view.file, view.purposes))
|
|
339
340
|
.join("\n");
|
|
340
|
-
return modeSection("tree", at, `<ul class="cf-tree">${trees}</ul
|
|
341
|
+
return modeSection("tree", at, `<ul class="cf-tree">${trees}</ul>`, headed);
|
|
341
342
|
}
|
|
342
343
|
/** The resolved definition of one call. It is a native disclosure, so the actual
|
|
343
344
|
* source is readable without the script, and the script clones this body into
|
|
@@ -1843,7 +1843,8 @@ function script(csrf) {
|
|
|
1843
1843
|
for (var i = 0; i < list.length; i += 1) {
|
|
1844
1844
|
var s = list[i];
|
|
1845
1845
|
var shaped = s && typeof s === 'object' && typeof s.path === 'string' && typeof s.line === 'number'
|
|
1846
|
-
&& (s.side === 'LEFT' || s.side === 'RIGHT') && typeof s.body === 'string' && s.body !== ''
|
|
1846
|
+
&& (s.side === 'LEFT' || s.side === 'RIGHT') && typeof s.body === 'string' && s.body !== ''
|
|
1847
|
+
&& SEVERITY_LABELS[s.severity] !== undefined;
|
|
1847
1848
|
if (!shaped || !currentLine(s) || settled[suggestionKey(current.snapshotId, s)]) continue;
|
|
1848
1849
|
out.push(s);
|
|
1849
1850
|
}
|
|
@@ -1910,12 +1911,17 @@ function script(csrf) {
|
|
|
1910
1911
|
render();
|
|
1911
1912
|
}
|
|
1912
1913
|
|
|
1914
|
+
var SEVERITY_LABELS = { critical: 'Critical', major: 'Major', minor: 'Minor' };
|
|
1915
|
+
|
|
1913
1916
|
function suggestionRow(suggestion, disabled) {
|
|
1914
1917
|
var wrap = make('div', 'suggestion');
|
|
1915
1918
|
var head = make('p', 'suggestion-by');
|
|
1916
1919
|
head.appendChild(make('span', 'agent-dot', ''));
|
|
1917
1920
|
head.appendChild(make('strong', '', suggestedBy()));
|
|
1918
1921
|
head.appendChild(document.createTextNode(' suggests'));
|
|
1922
|
+
var severity = make('span', 'severity severity-' + suggestion.severity, SEVERITY_LABELS[suggestion.severity]);
|
|
1923
|
+
severity.title = 'How much the agent thinks this matters';
|
|
1924
|
+
head.appendChild(severity);
|
|
1919
1925
|
wrap.appendChild(head);
|
|
1920
1926
|
wrap.appendChild(make('p', 'suggestion-body', suggestion.body));
|
|
1921
1927
|
var actions = make('div', 'suggestion-actions');
|
|
@@ -3071,8 +3077,14 @@ kbd {
|
|
|
3071
3077
|
.editor { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent-soft); }
|
|
3072
3078
|
.editor-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
|
|
3073
3079
|
.editor-input { flex: 1 1 240px; min-width: 0; }
|
|
3074
|
-
.suggestion-by { font-size: 12px; color: var(--ink-soft); }
|
|
3080
|
+
.suggestion-by { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; font-size: 12px; color: var(--ink-soft); }
|
|
3075
3081
|
.suggestion-by strong { color: var(--ink); font-weight: 600; }
|
|
3082
|
+
.severity {
|
|
3083
|
+
margin-left: 6px; padding: 1px 7px; border-radius: 4px; font-size: 11.5px; font-weight: 600; letter-spacing: 0.02em;
|
|
3084
|
+
color: var(--ink-soft); background: var(--neutral-soft);
|
|
3085
|
+
}
|
|
3086
|
+
.severity-critical { color: var(--alarm); background: var(--alarm-bg); }
|
|
3087
|
+
.severity-major { color: var(--warn); background: var(--warn-soft); }
|
|
3076
3088
|
.suggestion-body { font-size: 14px; line-height: 1.5; overflow-wrap: anywhere; }
|
|
3077
3089
|
.suggestion-actions { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
|
|
3078
3090
|
|
package/dist/review/html.d.ts
CHANGED
|
@@ -35,7 +35,8 @@ export declare function renderBusinessPage(report: ReviewReport): string;
|
|
|
35
35
|
/**
|
|
36
36
|
* The call flows of one review as a page of their own, for the connected pull
|
|
37
37
|
* request page to show beside its diff: every changed file with call flows, or
|
|
38
|
-
* only `file` when given.
|
|
39
|
-
*
|
|
38
|
+
* only `file` when given. Only the Tree view is offered, and the business view
|
|
39
|
+
* stays out of it: the page around it already shows that view. Links into the
|
|
40
|
+
* report's diff are dropped, since the page showing this has its own.
|
|
40
41
|
*/
|
|
41
42
|
export declare function renderCallFlowPage(report: ReviewReport, file?: string): string;
|
package/dist/review/html.js
CHANGED
|
@@ -110,7 +110,6 @@ const BUSINESS_VIEW_STYLES = `
|
|
|
110
110
|
.business { padding-top: 14px; }
|
|
111
111
|
.business-title { font-size: 20px; }
|
|
112
112
|
.business-lede { margin-top: 4px; color: var(--ink-soft); font-size: 14px; max-width: 90ch; }
|
|
113
|
-
.flow-section-title { margin: 22px 0 0; font-size: 18px; }
|
|
114
113
|
`;
|
|
115
114
|
/**
|
|
116
115
|
* Posts the document's height to the page that frames it, so the pull request
|
|
@@ -122,7 +121,9 @@ const BUSINESS_FRAME_SCRIPT = `
|
|
|
122
121
|
'use strict';
|
|
123
122
|
function post() {
|
|
124
123
|
if (window.parent === window) return;
|
|
125
|
-
|
|
124
|
+
// The html element's own box, not scrollHeight: scrollHeight never drops below the frame's
|
|
125
|
+
// height, so the frame could grow but never shrink back around its content.
|
|
126
|
+
window.parent.postMessage({ type: 'diffninja-business-height', height: Math.ceil(document.documentElement.getBoundingClientRect().height) }, window.location.origin);
|
|
126
127
|
}
|
|
127
128
|
window.addEventListener('load', post);
|
|
128
129
|
if (typeof ResizeObserver !== 'undefined') new ResizeObserver(post).observe(document.body);
|
|
@@ -173,8 +174,9 @@ body { background: var(--bg); font-family: var(--sans); }
|
|
|
173
174
|
/**
|
|
174
175
|
* The call flows of one review as a page of their own, for the connected pull
|
|
175
176
|
* request page to show beside its diff: every changed file with call flows, or
|
|
176
|
-
* only `file` when given.
|
|
177
|
-
*
|
|
177
|
+
* only `file` when given. Only the Tree view is offered, and the business view
|
|
178
|
+
* stays out of it: the page around it already shows that view. Links into the
|
|
179
|
+
* report's diff are dropped, since the page showing this has its own.
|
|
178
180
|
*/
|
|
179
181
|
export function renderCallFlowPage(report, file) {
|
|
180
182
|
const scoped = file === undefined ? report : { ...report, callFlows: report.callFlows.filter((entry) => entry.file === file) };
|
|
@@ -186,20 +188,11 @@ export function renderCallFlowPage(report, file) {
|
|
|
186
188
|
'<meta name="viewport" content="width=device-width, initial-scale=1">',
|
|
187
189
|
'<meta name="color-scheme" content="light dark">',
|
|
188
190
|
`<title>${escapeHtml(file === undefined ? "Call flows" : `Call flow: ${file}`)}</title>`,
|
|
189
|
-
`<style>${STYLES}\n${CALL_FLOW_STYLES}\n${PALETTE_STYLES}\n${
|
|
191
|
+
`<style>${STYLES}\n${CALL_FLOW_STYLES}\n${PALETTE_STYLES}\n${EMBEDDED_FLOW_STYLES}</style>`,
|
|
190
192
|
"</head>",
|
|
191
193
|
"<body>",
|
|
192
|
-
report.agentExplanation === undefined
|
|
193
|
-
? ""
|
|
194
|
-
: [
|
|
195
|
-
'<section class="flow-embed business" aria-labelledby="flow-business-title">',
|
|
196
|
-
`<h2 class="business-title" id="flow-business-title">${file === undefined ? "How it works" : "How this file fits the process"}</h2>`,
|
|
197
|
-
renderBusinessView(report, { file, glossary: false }),
|
|
198
|
-
"</section>",
|
|
199
|
-
].join("\n"),
|
|
200
194
|
`<section id="view-call-flow" class="flow-embed${file === undefined ? "" : " flow-single"}" aria-label="Call flow">`,
|
|
201
|
-
|
|
202
|
-
renderCallFlows(scoped),
|
|
195
|
+
renderCallFlows(scoped, { treeOnly: true }),
|
|
203
196
|
"</section>",
|
|
204
197
|
`<script>document.documentElement.classList.add('js');\n${CALL_FLOW_SCRIPT}</script>`,
|
|
205
198
|
"</body>",
|
package/dist/review/mcp.js
CHANGED
|
@@ -9,6 +9,7 @@ import { detectPullRequest } from "./pr-input.js";
|
|
|
9
9
|
import { renderBusinessPage, renderCallFlowPage, renderReview } from "./html.js";
|
|
10
10
|
import { MAX_SUGGESTED_COMMENTS, ReportPages } from "./report-pages.js";
|
|
11
11
|
import { reviewDiff } from "./service.js";
|
|
12
|
+
import { COMMENT_SEVERITIES } from "./types.js";
|
|
12
13
|
import { MAX_BRANCH_CHARS, MAX_DETAIL_CHARS, MAX_EXPLAINED_FUNCTIONS, MAX_PROCESSES, MAX_PROCESS_STEPS, MAX_PURPOSE_CHARS, MAX_RULES, MAX_RULE_CHARS, MAX_STEP_CHARS, MAX_STEP_EXITS, MAX_TITLE_CHARS, MIN_PROCESS_STEPS, } from "./explanation.js";
|
|
13
14
|
import { packageVersion } from "./version.js";
|
|
14
15
|
const PR_LINK_ERROR = "A pull request review needs exactly one full github.com pull request URL, for example https://github.com/OWNER/REPO/pull/123. Ask the user for their link; do not guess, search, or invent one.";
|
|
@@ -245,8 +246,9 @@ const commentSchema = z.object({
|
|
|
245
246
|
line: z.number().int().positive().describe("The line number on that side."),
|
|
246
247
|
side: z.enum(["LEFT", "RIGHT"]).describe("RIGHT for an added or context line (new side), LEFT for a removed line (old side)."),
|
|
247
248
|
body: z.string().max(1000).describe("The comment, as the reviewer would write it: one short line, no labels or formatting."),
|
|
249
|
+
severity: z.enum(COMMENT_SEVERITIES).describe("How much it matters, so the human can triage: critical for a bug, data loss, security or contract break that should block the merge; major for a real risk or missing case worth fixing before merge; minor for a nit, naming or style point. Give it here, never inside the body."),
|
|
248
250
|
}).strict();
|
|
249
|
-
const COMMENT_RULES = "Only comment where a maintainer would actually ask for something or point something out: a bug, a risk, a missing case, a confusing name, a missing test; never pad. Write each one as the reviewer would type it on GitHub, in their own voice: short (one line, at most 280 characters), concrete, conversational, e.g. \"This drops the error from Close(); should we return it?\" or \"nit: could this reuse parseVersion?\". No report scaffolding: no headings, bold, list markers, numbering, or labels such as Finding, Issue, Attention, Error, Severity. Each names a line of the diff: path, line, and side RIGHT for an added or context line, LEFT for a removed line; at most one per line and 30 in all.";
|
|
251
|
+
const COMMENT_RULES = "Only comment where a maintainer would actually ask for something or point something out: a bug, a risk, a missing case, a confusing name, a missing test; never pad. Write each one as the reviewer would type it on GitHub, in their own voice: short (one line, at most 280 characters), concrete, conversational, e.g. \"This drops the error from Close(); should we return it?\" or \"nit: could this reuse parseVersion?\". No report scaffolding: no headings, bold, list markers, numbering, or labels such as Finding, Issue, Attention, Error, Severity. Each names a line of the diff: path, line, and side RIGHT for an added or context line, LEFT for a removed line; at most one per line and 30 in all. Each also carries a severity (critical, major or minor) in its own field, which the page shows next to the comment so the human reviews the important ones first; the words critical, major and minor do not belong in the body.";
|
|
250
252
|
/**
|
|
251
253
|
* What the goal summary is for. It is the agent's own paragraph for the human
|
|
252
254
|
* reading the pull request, written from the author's own title and
|
|
@@ -462,12 +464,7 @@ export function createReviewServer() {
|
|
|
462
464
|
description: "Update the line comments suggested for a pull request review after finish_review, or before it. " + COMMENT_RULES + " The whole call is refused, and the previous suggestions kept, if any comment breaks these rules. A later call replaces the earlier suggestions; an empty list clears them. Nothing is posted: the page shows each suggestion under its line, attributed to this MCP client, and the human adds it to their own review, edits it, or dismisses it. This returns no page link: only finish_review does.",
|
|
463
465
|
inputSchema: z.object({
|
|
464
466
|
reviewId: z.string().regex(/^[a-f0-9]{32}$/).describe("The reviewId a review_diff result returned on this connection."),
|
|
465
|
-
comments: z.array(
|
|
466
|
-
path: z.string().min(1).max(1024).describe("The file's path in the diff."),
|
|
467
|
-
line: z.number().int().positive().describe("The line number on that side."),
|
|
468
|
-
side: z.enum(["LEFT", "RIGHT"]).describe("RIGHT for an added or context line (new side), LEFT for a removed line (old side)."),
|
|
469
|
-
body: z.string().max(1000).describe("The comment, as the reviewer would write it: one short line, no labels or formatting."),
|
|
470
|
-
}).strict()).max(MAX_SUGGESTED_COMMENTS),
|
|
467
|
+
comments: z.array(commentSchema).max(MAX_SUGGESTED_COMMENTS),
|
|
471
468
|
}).strict(),
|
|
472
469
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
473
470
|
}, async ({ reviewId, comments }) => {
|
|
@@ -22,8 +22,6 @@ import type { ReviewReport } from "./types.js";
|
|
|
22
22
|
export interface BusinessViewOptions {
|
|
23
23
|
/** Link for a hunk id, or undefined when this page has no diff to link to. */
|
|
24
24
|
readonly hunkHref?: (itemId: string) => string | undefined;
|
|
25
|
-
/** Draw only the processes and rules that touch this changed file. */
|
|
26
|
-
readonly file?: string;
|
|
27
25
|
/** Leave out the function glossary (the call-flow view shows the purposes). */
|
|
28
26
|
readonly glossary?: boolean;
|
|
29
27
|
/** Leave out the attribution line when the page around it already says who explained it. */
|
|
@@ -32,9 +30,7 @@ export interface BusinessViewOptions {
|
|
|
32
30
|
readonly stepsOpen?: boolean;
|
|
33
31
|
}
|
|
34
32
|
/**
|
|
35
|
-
* The whole business view, or a note saying why there is none.
|
|
36
|
-
* scopes it to one changed file: the processes whose steps name a hunk or a
|
|
37
|
-
* function of that file, and the rules that name one of its hunks.
|
|
33
|
+
* The whole business view, or a note saying why there is none.
|
|
38
34
|
*/
|
|
39
35
|
export declare function renderBusinessView(report: ReviewReport, options?: BusinessViewOptions): string;
|
|
40
36
|
interface StepBox {
|
|
@@ -49,9 +49,7 @@ const LANE_GAP = 18;
|
|
|
49
49
|
const CHART_PAD = 14;
|
|
50
50
|
const DECISION_INSET = 16;
|
|
51
51
|
/**
|
|
52
|
-
* The whole business view, or a note saying why there is none.
|
|
53
|
-
* scopes it to one changed file: the processes whose steps name a hunk or a
|
|
54
|
-
* function of that file, and the rules that name one of its hunks.
|
|
52
|
+
* The whole business view, or a note saying why there is none.
|
|
55
53
|
*/
|
|
56
54
|
export function renderBusinessView(report, options = {}) {
|
|
57
55
|
const explanation = report.agentExplanation;
|
|
@@ -62,11 +60,7 @@ export function renderBusinessView(report, options = {}) {
|
|
|
62
60
|
const purposes = purposesOf(report);
|
|
63
61
|
const ranks = new Map(report.items.map((item, index) => [item.id, index + 1]));
|
|
64
62
|
const itemFiles = new Map(report.items.map((item) => [item.id, item.file]));
|
|
65
|
-
const
|
|
66
|
-
(hunks ?? []).some((id) => itemFiles.get(id) === options.file) ||
|
|
67
|
-
(fns ?? []).some((id) => functions.get(id)?.file === options.file);
|
|
68
|
-
const processes = explanation.processes.filter((process) => process.steps.some((step) => touches(step.hunks, step.functions)));
|
|
69
|
-
const rules = explanation.rules.filter((rule) => touches(rule.hunks, undefined));
|
|
63
|
+
const { processes, rules } = explanation;
|
|
70
64
|
const context = { functions, purposes, ranks, itemFiles, hunkHref: options.hunkHref, stepsOpen: options.stepsOpen !== false };
|
|
71
65
|
const parts = [
|
|
72
66
|
'<div class="bp">',
|
|
@@ -77,13 +71,10 @@ export function renderBusinessView(report, options = {}) {
|
|
|
77
71
|
renderLegend(),
|
|
78
72
|
"</div>",
|
|
79
73
|
].filter((part) => part !== "");
|
|
80
|
-
if (processes.length === 0 && rules.length === 0) {
|
|
81
|
-
parts.push(`<p class="bp-note">No process or rule in the explanation names this file's hunks or functions.</p>`);
|
|
82
|
-
}
|
|
83
74
|
processes.forEach((process, index) => parts.push(renderProcess(process, index + 1, context)));
|
|
84
75
|
if (rules.length > 0)
|
|
85
76
|
parts.push(renderRules(rules, context));
|
|
86
|
-
if (options.glossary !== false
|
|
77
|
+
if (options.glossary !== false)
|
|
87
78
|
parts.push(renderGlossary(report.functions ?? [], purposes));
|
|
88
79
|
parts.push("</div>");
|
|
89
80
|
return parts.join("\n");
|
|
@@ -117,7 +117,7 @@ function checkComments(report, comments) {
|
|
|
117
117
|
}
|
|
118
118
|
function applyComments(report, comments, suggestedBy) {
|
|
119
119
|
report.agentComments = {
|
|
120
|
-
comments: comments.map(({ path, line, side, body }) => ({ path, line, side, body: body.trim() })),
|
|
120
|
+
comments: comments.map(({ path, line, side, body, severity }) => ({ path, line, side, body: body.trim(), severity })),
|
|
121
121
|
suggestedBy,
|
|
122
122
|
suggestedAt: new Date().toISOString(),
|
|
123
123
|
};
|
package/dist/review/types.d.ts
CHANGED
|
@@ -132,12 +132,16 @@ export interface AgentOrder {
|
|
|
132
132
|
/** diffninja's own order of the same items, kept so the page can still offer it. */
|
|
133
133
|
diffninjaIds: string[];
|
|
134
134
|
}
|
|
135
|
+
/** How much a suggested comment matters, most to least: what a reviewer triages by. */
|
|
136
|
+
export declare const COMMENT_SEVERITIES: readonly ["critical", "major", "minor"];
|
|
137
|
+
export type CommentSeverity = (typeof COMMENT_SEVERITIES)[number];
|
|
135
138
|
/** One line comment the reviewing agent suggests; the human adds it to their review or not. */
|
|
136
139
|
export interface SuggestedComment {
|
|
137
140
|
path: string;
|
|
138
141
|
line: number;
|
|
139
142
|
side: "LEFT" | "RIGHT";
|
|
140
143
|
body: string;
|
|
144
|
+
severity: CommentSeverity;
|
|
141
145
|
}
|
|
142
146
|
export interface AgentComments {
|
|
143
147
|
comments: SuggestedComment[];
|
package/dist/review/types.js
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
/** How much a suggested comment matters, most to least: what a reviewer triages by. */
|
|
2
|
+
export const COMMENT_SEVERITIES = ["critical", "major", "minor"];
|