@christianmorup/review-intent 0.1.0 → 0.1.1
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/render.js +1306 -259
- package/dist/review-order.js +78 -0
- package/dist/skill.js +6 -7
- package/dist/themes.js +176 -0
- package/package.json +1 -1
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { isNoisePath } from "./scorecard.js";
|
|
2
|
+
const norm = (p) => p.replace(/\\/g, "/");
|
|
3
|
+
/** Stable, unique, deterministic anchor id for a file section. */
|
|
4
|
+
export function fileSlug(index) {
|
|
5
|
+
return `file-${index}`;
|
|
6
|
+
}
|
|
7
|
+
/** Highest CCN of a measured hotspot matching this file, or null. Matches
|
|
8
|
+
* lizard's (possibly differently-rooted) path by suffix/basename — same
|
|
9
|
+
* heuristic the change-map uses. */
|
|
10
|
+
function hotspotCcn(model, path) {
|
|
11
|
+
const cx = model.complexity;
|
|
12
|
+
if (!cx.available || cx.hotspots.length === 0)
|
|
13
|
+
return null;
|
|
14
|
+
const p = norm(path);
|
|
15
|
+
const base = p.split("/").pop() ?? p;
|
|
16
|
+
let max = null;
|
|
17
|
+
for (const h of cx.hotspots) {
|
|
18
|
+
const hp = norm(h.file);
|
|
19
|
+
const hit = hp === p ||
|
|
20
|
+
hp.endsWith("/" + p) ||
|
|
21
|
+
p.endsWith("/" + hp) ||
|
|
22
|
+
(hp.split("/").pop() ?? hp) === base;
|
|
23
|
+
if (hit)
|
|
24
|
+
max = max === null ? h.ccn : Math.max(max, h.ccn);
|
|
25
|
+
}
|
|
26
|
+
return max;
|
|
27
|
+
}
|
|
28
|
+
/** Pure: one signal record per changed file, in original diff order. */
|
|
29
|
+
export function collectSignals(model) {
|
|
30
|
+
return model.files.map((f, i) => {
|
|
31
|
+
let added = 0;
|
|
32
|
+
let removed = 0;
|
|
33
|
+
for (const h of f.hunks)
|
|
34
|
+
for (const l of h.lines) {
|
|
35
|
+
if (l.type === "add")
|
|
36
|
+
added++;
|
|
37
|
+
else if (l.type === "del")
|
|
38
|
+
removed++;
|
|
39
|
+
}
|
|
40
|
+
const fanIn = model.reach.edges.reduce((n, e) => (norm(e.to) === norm(f.path) ? n + 1 : n), 0);
|
|
41
|
+
const maxCcn = hotspotCcn(model, f.path);
|
|
42
|
+
const missingIntent = !f.why || f.hunks.some((h) => h.intents.length === 0);
|
|
43
|
+
return {
|
|
44
|
+
path: f.path,
|
|
45
|
+
index: i,
|
|
46
|
+
slug: fileSlug(i),
|
|
47
|
+
status: f.status,
|
|
48
|
+
added,
|
|
49
|
+
removed,
|
|
50
|
+
churn: added + removed,
|
|
51
|
+
hunks: f.hunks.length,
|
|
52
|
+
fanIn,
|
|
53
|
+
hotspot: maxCcn !== null,
|
|
54
|
+
maxCcn,
|
|
55
|
+
missingIntent,
|
|
56
|
+
isNoise: isNoisePath(f.path),
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/** Pure: files ranked by review priority (most attention-worthy first). Score
|
|
61
|
+
* blends normalized churn + reach with flat bonuses for a complexity hotspot
|
|
62
|
+
* and for unexplained changes, then demotes noise. Ties break by churn, then
|
|
63
|
+
* original diff order — fully deterministic. */
|
|
64
|
+
export function reviewOrder(model) {
|
|
65
|
+
const sig = collectSignals(model);
|
|
66
|
+
const maxChurn = Math.max(1, ...sig.map((s) => s.churn));
|
|
67
|
+
const maxFan = Math.max(1, ...sig.map((s) => s.fanIn));
|
|
68
|
+
const sq = (v, max) => Math.sqrt(v) / Math.sqrt(max);
|
|
69
|
+
const scored = sig.map((s, i) => {
|
|
70
|
+
const base = sq(s.churn, maxChurn) +
|
|
71
|
+
sq(s.fanIn, maxFan) +
|
|
72
|
+
(s.hotspot ? 0.6 : 0) +
|
|
73
|
+
(s.missingIntent ? 0.5 : 0);
|
|
74
|
+
return { sig: s, i, score: s.isNoise ? base * 0.25 : base };
|
|
75
|
+
});
|
|
76
|
+
scored.sort((a, b) => b.score - a.score || b.sig.churn - a.sig.churn || a.i - b.i);
|
|
77
|
+
return scored.map((x, idx) => ({ ...x.sig, score: x.score, rank: idx + 1 }));
|
|
78
|
+
}
|
package/dist/skill.js
CHANGED
|
@@ -131,13 +131,12 @@ This is the reviewer's highest-value target, so write it to honesty-rule #3:
|
|
|
131
131
|
"low risk" while the scorecard flags \`touches auth/, 0 test files\` or a
|
|
132
132
|
changed function jumps to CCN 30 and you never mention it, the contradiction is
|
|
133
133
|
visible at a glance.
|
|
134
|
-
- **There is
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
the
|
|
139
|
-
|
|
140
|
-
the place to justify it.
|
|
134
|
+
- **There is a "change map"** that plots each changed file by *measured*
|
|
135
|
+
downstream reach (how many files import it) against *measured* churn, flagging
|
|
136
|
+
the ones that also carry a complexity hotspot. The biggest, most depended-on
|
|
137
|
+
files land in the top-right — that's where a reviewer looks first, so those are
|
|
138
|
+
the files whose *why* had better be airtight. If a hunk added real
|
|
139
|
+
branching/complexity, the *why* is the place to justify it.
|
|
141
140
|
|
|
142
141
|
### The tests section (\`tests\`)
|
|
143
142
|
|
package/dist/themes.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/** Pure: visual theme catalog + CSS emitter. No I/O, no Date/random.
|
|
2
|
+
* `paper` (the default) lives in render.ts `:root`; this module supplies
|
|
3
|
+
* every *other* theme as a `[data-theme="id"]` override block. */
|
|
4
|
+
/** Canonical token contract — every theme must define all of these. */
|
|
5
|
+
export const TOKEN_KEYS = [
|
|
6
|
+
"--paper", "--surface", "--surface-2", "--ink", "--ink-soft", "--muted",
|
|
7
|
+
"--line", "--line-2", "--accent", "--accent-soft",
|
|
8
|
+
"--add", "--add-soft", "--del", "--del-soft", "--warn", "--warn-soft",
|
|
9
|
+
"--add-border", "--del-border", "--warn-border", "--accent-border",
|
|
10
|
+
"--accent-shadow", "--on-accent", "--glass", "--code-add", "--code-del",
|
|
11
|
+
"--viz-add", "--viz-add-ink", "--viz-del", "--viz-del-ink", "--viz-warn",
|
|
12
|
+
"--viz-accent", "--viz-line", "--viz-node", "--viz-node-stroke",
|
|
13
|
+
"--viz-accent-stroke", "--viz-cell-stroke", "--viz-noise", "--viz-other",
|
|
14
|
+
"--viz-cell-label", "--viz-zone", "--kind-e2e",
|
|
15
|
+
"--viz-s1", "--viz-s2", "--viz-s3", "--viz-s4",
|
|
16
|
+
"--viz-s5", "--viz-s6", "--viz-s7", "--viz-s8",
|
|
17
|
+
];
|
|
18
|
+
const DEFAULT_SERIES = [
|
|
19
|
+
"#5b7db1", "#5fa389", "#b08a5a", "#a07ba6",
|
|
20
|
+
"#c47d72", "#7fa86a", "#d0a85a", "#7a93b8",
|
|
21
|
+
];
|
|
22
|
+
/** Expand core palette into the full token record. Derived tokens alias core
|
|
23
|
+
* values (no color math) so a theme stays ~16 lines; any token can still be
|
|
24
|
+
* overridden by adding it to the returned record after the fact. */
|
|
25
|
+
export function makeTheme(id, label, group, c) {
|
|
26
|
+
const s = c.series ?? DEFAULT_SERIES;
|
|
27
|
+
const tokens = {
|
|
28
|
+
"--paper": c.paper, "--surface": c.surface, "--surface-2": c.surface2,
|
|
29
|
+
"--ink": c.ink, "--ink-soft": c.inkSoft, "--muted": c.muted,
|
|
30
|
+
"--line": c.line, "--line-2": c.line2,
|
|
31
|
+
"--accent": c.accent, "--accent-soft": c.accentSoft,
|
|
32
|
+
"--add": c.add, "--add-soft": c.addSoft,
|
|
33
|
+
"--del": c.del, "--del-soft": c.delSoft,
|
|
34
|
+
"--warn": c.warn, "--warn-soft": c.warnSoft,
|
|
35
|
+
"--add-border": c.add, "--del-border": c.del,
|
|
36
|
+
"--warn-border": c.warn, "--accent-border": c.accent,
|
|
37
|
+
"--accent-shadow": c.accentShadow ?? "rgba(0,0,0,.2)",
|
|
38
|
+
"--on-accent": c.onAccent ?? "#fff",
|
|
39
|
+
"--glass": c.glass ?? c.surface,
|
|
40
|
+
"--code-add": c.codeAdd ?? c.add,
|
|
41
|
+
"--code-del": c.codeDel ?? c.del,
|
|
42
|
+
"--viz-add": c.add, "--viz-add-ink": c.add,
|
|
43
|
+
"--viz-del": c.del, "--viz-del-ink": c.del,
|
|
44
|
+
"--viz-warn": c.warn, "--viz-accent": c.accent, "--viz-line": c.line,
|
|
45
|
+
"--viz-node": c.surface, "--viz-node-stroke": c.line2,
|
|
46
|
+
"--viz-accent-stroke": c.accent, "--viz-cell-stroke": c.surface,
|
|
47
|
+
"--viz-noise": c.muted, "--viz-other": c.muted,
|
|
48
|
+
"--viz-cell-label": c.cellLabel ?? c.ink,
|
|
49
|
+
"--viz-zone": c.delSoft, "--kind-e2e": c.accent,
|
|
50
|
+
"--viz-s1": s[0], "--viz-s2": s[1], "--viz-s3": s[2], "--viz-s4": s[3],
|
|
51
|
+
"--viz-s5": s[4], "--viz-s6": s[5], "--viz-s7": s[6], "--viz-s8": s[7],
|
|
52
|
+
};
|
|
53
|
+
if (c.sans)
|
|
54
|
+
tokens["--sans"] = c.sans;
|
|
55
|
+
if (c.mono)
|
|
56
|
+
tokens["--mono"] = c.mono;
|
|
57
|
+
return { id, label, group, tokens };
|
|
58
|
+
}
|
|
59
|
+
export const THEMES = [
|
|
60
|
+
makeTheme("dark", "Dark", "Playful", {
|
|
61
|
+
paper: "#1b1a17", surface: "#232220", surface2: "#2c2a26",
|
|
62
|
+
ink: "#ece9e1", inkSoft: "#b8b2a6", muted: "#847d6f",
|
|
63
|
+
line: "#34322c", line2: "#44413a",
|
|
64
|
+
accent: "#6fa3e0", accentSoft: "#20303f",
|
|
65
|
+
add: "#56c07a", addSoft: "#18301f", del: "#e8786c", delSoft: "#3a201d",
|
|
66
|
+
warn: "#d6a64a", warnSoft: "#332a16", onAccent: "#000",
|
|
67
|
+
}),
|
|
68
|
+
makeTheme("hacker", "Hacker", "Playful", {
|
|
69
|
+
paper: "#000800", surface: "#021202", surface2: "#001a00",
|
|
70
|
+
ink: "#33ff66", inkSoft: "#1faf47", muted: "#0f7a2e",
|
|
71
|
+
line: "#093d12", line2: "#0e5a1c",
|
|
72
|
+
accent: "#7dff7d", accentSoft: "#00270c",
|
|
73
|
+
add: "#39ff77", addSoft: "#002a0e", del: "#ff5f56", delSoft: "#2a0a08",
|
|
74
|
+
warn: "#d4ff3a", warnSoft: "#1d2a00", onAccent: "#000",
|
|
75
|
+
mono: 'ui-monospace, "JetBrains Mono", "Cascadia Code", Consolas, monospace',
|
|
76
|
+
}),
|
|
77
|
+
makeTheme("solarized-light", "Solarized Light", "Dev favorites", {
|
|
78
|
+
paper: "#fdf6e3", surface: "#fdf6e3", surface2: "#eee8d5",
|
|
79
|
+
ink: "#586e75", inkSoft: "#657b83", muted: "#6c7b7b",
|
|
80
|
+
line: "#eee8d5", line2: "#d6cfbb",
|
|
81
|
+
accent: "#268bd2", accentSoft: "#dcebf2",
|
|
82
|
+
add: "#5f6d00", addSoft: "#eef0d8", del: "#dc322f", delSoft: "#f6e0db",
|
|
83
|
+
warn: "#8a6700", warnSoft: "#f3ead0", onAccent: "#000",
|
|
84
|
+
}),
|
|
85
|
+
makeTheme("solarized-dark", "Solarized Dark", "Dev favorites", {
|
|
86
|
+
paper: "#002b36", surface: "#073642", surface2: "#003744",
|
|
87
|
+
ink: "#93a1a1", inkSoft: "#839496", muted: "#6c8088",
|
|
88
|
+
line: "#0a4250", line2: "#135561",
|
|
89
|
+
accent: "#268bd2", accentSoft: "#03323e",
|
|
90
|
+
add: "#859900", addSoft: "#14331f", del: "#dc322f", delSoft: "#33161a",
|
|
91
|
+
warn: "#b58900", warnSoft: "#2e2812", onAccent: "#000",
|
|
92
|
+
}),
|
|
93
|
+
makeTheme("nord", "Nord", "Dev favorites", {
|
|
94
|
+
paper: "#2e3440", surface: "#3b4252", surface2: "#434c5e",
|
|
95
|
+
ink: "#eceff4", inkSoft: "#d8dee9", muted: "#9aa5b5",
|
|
96
|
+
line: "#434c5e", line2: "#4c566a",
|
|
97
|
+
accent: "#88c0d0", accentSoft: "#2b333f",
|
|
98
|
+
add: "#a3be8c", addSoft: "#2c3a2e", del: "#bf616a", delSoft: "#3a2a2c",
|
|
99
|
+
warn: "#ebcb8b", warnSoft: "#3a3424", onAccent: "#000",
|
|
100
|
+
}),
|
|
101
|
+
makeTheme("gruvbox", "Gruvbox", "Dev favorites", {
|
|
102
|
+
paper: "#282828", surface: "#32302f", surface2: "#3c3836",
|
|
103
|
+
ink: "#ebdbb2", inkSoft: "#d5c4a1", muted: "#a89984",
|
|
104
|
+
line: "#3c3836", line2: "#504945",
|
|
105
|
+
accent: "#83a598", accentSoft: "#2b3331",
|
|
106
|
+
add: "#b8bb26", addSoft: "#2f3318", del: "#fb4934", delSoft: "#3a201c",
|
|
107
|
+
warn: "#fabd2f", warnSoft: "#3a3014", onAccent: "#000",
|
|
108
|
+
}),
|
|
109
|
+
makeTheme("catppuccin", "Catppuccin", "Dev favorites", {
|
|
110
|
+
paper: "#1e1e2e", surface: "#313244", surface2: "#45475a",
|
|
111
|
+
ink: "#cdd6f4", inkSoft: "#bac2de", muted: "#9399b2",
|
|
112
|
+
line: "#313244", line2: "#585b70",
|
|
113
|
+
accent: "#89b4fa", accentSoft: "#2a2c41",
|
|
114
|
+
add: "#a6e3a1", addSoft: "#26342a", del: "#f38ba8", delSoft: "#361f2a",
|
|
115
|
+
warn: "#f9e2af", warnSoft: "#36321f", onAccent: "#000",
|
|
116
|
+
}),
|
|
117
|
+
makeTheme("github", "GitHub", "Professional", {
|
|
118
|
+
paper: "#ffffff", surface: "#ffffff", surface2: "#f6f8fa",
|
|
119
|
+
ink: "#1f2328", inkSoft: "#656d76", muted: "#8c959f",
|
|
120
|
+
line: "#d0d7de", line2: "#afb8c1",
|
|
121
|
+
accent: "#0969da", accentSoft: "#ddf4ff",
|
|
122
|
+
add: "#1a7f37", addSoft: "#dafbe1", del: "#cf222e", delSoft: "#ffebe9",
|
|
123
|
+
warn: "#9a6700", warnSoft: "#fff8c5",
|
|
124
|
+
}),
|
|
125
|
+
makeTheme("high-contrast", "High Contrast", "Professional", {
|
|
126
|
+
paper: "#ffffff", surface: "#ffffff", surface2: "#f0f0f0",
|
|
127
|
+
ink: "#000000", inkSoft: "#000000", muted: "#3a3a3a",
|
|
128
|
+
line: "#000000", line2: "#000000",
|
|
129
|
+
accent: "#0000cc", accentSoft: "#e6e6ff",
|
|
130
|
+
add: "#006400", addSoft: "#e6f5e6", del: "#b00000", delSoft: "#ffe6e6",
|
|
131
|
+
warn: "#7a4d00", warnSoft: "#fff3e0",
|
|
132
|
+
}),
|
|
133
|
+
makeTheme("blueprint", "Blueprint", "Professional", {
|
|
134
|
+
paper: "#0d2747", surface: "#123257", surface2: "#16395f",
|
|
135
|
+
ink: "#dbeafe", inkSoft: "#a9c7ee", muted: "#6f93c0",
|
|
136
|
+
line: "#1d4373", line2: "#2a558c",
|
|
137
|
+
accent: "#5ad1ff", accentSoft: "#0e2c4d",
|
|
138
|
+
add: "#5ee0a0", addSoft: "#103a2c", del: "#ff8a8a", delSoft: "#3a1d22",
|
|
139
|
+
warn: "#ffd166", warnSoft: "#33301a", onAccent: "#000",
|
|
140
|
+
}),
|
|
141
|
+
makeTheme("newsprint", "Newsprint", "Editorial", {
|
|
142
|
+
paper: "#f7f5ef", surface: "#ffffff", surface2: "#ece9e1",
|
|
143
|
+
ink: "#111111", inkSoft: "#333333", muted: "#6b6b6b",
|
|
144
|
+
line: "#cfcabf", line2: "#b3ada0",
|
|
145
|
+
accent: "#8a1f11", accentSoft: "#f1e3e0",
|
|
146
|
+
add: "#1a6b34", addSoft: "#e6f0e8", del: "#a31d12", delSoft: "#f6e3e0",
|
|
147
|
+
warn: "#8a6400", warnSoft: "#f1ead4",
|
|
148
|
+
sans: 'Iowan Old Style, Georgia, "Times New Roman", Times, serif',
|
|
149
|
+
}),
|
|
150
|
+
makeTheme("sepia", "Sepia", "Editorial", {
|
|
151
|
+
paper: "#e9dcc3", surface: "#f3e9d6", surface2: "#ddcfb2",
|
|
152
|
+
ink: "#4a3b28", inkSoft: "#5f4d34", muted: "#776344",
|
|
153
|
+
line: "#d4c4a3", line2: "#c4b08a",
|
|
154
|
+
accent: "#8a5a2b", accentSoft: "#ead9bf",
|
|
155
|
+
add: "#4f5e24", addSoft: "#dfe0bf", del: "#9c3b28", delSoft: "#ecd5cc",
|
|
156
|
+
warn: "#7e5e22", warnSoft: "#ece0c0",
|
|
157
|
+
}),
|
|
158
|
+
makeTheme("synthwave", "Synthwave", "Playful", {
|
|
159
|
+
paper: "#1a1033", surface: "#251447", surface2: "#2f1a57",
|
|
160
|
+
ink: "#f6e6ff", inkSoft: "#d3b8f0", muted: "#9a7fc0",
|
|
161
|
+
line: "#3a2470", line2: "#4d2f8f",
|
|
162
|
+
accent: "#ff5fd2", accentSoft: "#2e1450",
|
|
163
|
+
add: "#36f9c5", addSoft: "#0e3a32", del: "#ff5f6d", delSoft: "#3a1622",
|
|
164
|
+
warn: "#ffd23f", warnSoft: "#332a10", onAccent: "#000",
|
|
165
|
+
}),
|
|
166
|
+
];
|
|
167
|
+
/** Emit a `[data-theme="id"]{…}` block per theme. `paper` (default) is NOT
|
|
168
|
+
* emitted — it lives in render.ts `:root`. Deterministic string output. */
|
|
169
|
+
export function themeCss() {
|
|
170
|
+
return THEMES.map((t) => {
|
|
171
|
+
const decls = Object.entries(t.tokens)
|
|
172
|
+
.map(([k, v]) => ` ${k}: ${v};`)
|
|
173
|
+
.join("\n");
|
|
174
|
+
return `[data-theme="${t.id}"] {\n${decls}\n}`;
|
|
175
|
+
}).join("\n");
|
|
176
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@christianmorup/review-intent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Render the diff between the current branch and main as an intent-annotated HTML review page with mermaid class & sequence diagrams.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|