ci(labeler): enforce ordered gradient palette and compact module labels
This commit is contained in:
parent
004fc4590f
commit
3a25f4fa3a
3 changed files with 96 additions and 83 deletions
177
.github/workflows/labeler.yml
vendored
177
.github/workflows/labeler.yml
vendored
|
|
@ -108,39 +108,39 @@ jobs:
|
||||||
{ root: "src/tunnel/", prefix: "tunnel", coreEntries: new Set(["mod.rs"]) },
|
{ root: "src/tunnel/", prefix: "tunnel", coreEntries: new Set(["mod.rs"]) },
|
||||||
];
|
];
|
||||||
const managedModulePrefixes = [...new Set(moduleNamespaceRules.map((rule) => `${rule.prefix}:`))];
|
const managedModulePrefixes = [...new Set(moduleNamespaceRules.map((rule) => `${rule.prefix}:`))];
|
||||||
const modulePrefixPriority = [
|
const otherLabelDisplayOrder = [
|
||||||
"security",
|
"health",
|
||||||
"runtime",
|
|
||||||
"gateway",
|
|
||||||
"tool",
|
"tool",
|
||||||
"provider",
|
|
||||||
"channel",
|
|
||||||
"config",
|
|
||||||
"memory",
|
|
||||||
"agent",
|
"agent",
|
||||||
"integration",
|
"memory",
|
||||||
"observability",
|
"channel",
|
||||||
"onboard",
|
|
||||||
"service",
|
"service",
|
||||||
|
"integration",
|
||||||
"tunnel",
|
"tunnel",
|
||||||
"cron",
|
"config",
|
||||||
|
"observability",
|
||||||
|
"docs",
|
||||||
|
"dev",
|
||||||
|
"tests",
|
||||||
|
"skills",
|
||||||
|
"skillforge",
|
||||||
|
"provider",
|
||||||
|
"runtime",
|
||||||
|
"heartbeat",
|
||||||
"daemon",
|
"daemon",
|
||||||
"doctor",
|
"doctor",
|
||||||
"health",
|
"onboard",
|
||||||
"heartbeat",
|
"cron",
|
||||||
"skillforge",
|
|
||||||
"skills",
|
|
||||||
];
|
|
||||||
const pathLabelPriority = [
|
|
||||||
...modulePrefixPriority,
|
|
||||||
"core",
|
|
||||||
"ci",
|
"ci",
|
||||||
"dependencies",
|
"dependencies",
|
||||||
"tests",
|
"gateway",
|
||||||
|
"security",
|
||||||
|
"core",
|
||||||
"scripts",
|
"scripts",
|
||||||
"dev",
|
|
||||||
"docs",
|
|
||||||
];
|
];
|
||||||
|
const modulePrefixSet = new Set(moduleNamespaceRules.map((rule) => rule.prefix));
|
||||||
|
const modulePrefixPriority = otherLabelDisplayOrder.filter((label) => modulePrefixSet.has(label));
|
||||||
|
const pathLabelPriority = [...otherLabelDisplayOrder];
|
||||||
const riskDisplayOrder = ["risk: high", "risk: medium", "risk: low", "risk: manual"];
|
const riskDisplayOrder = ["risk: high", "risk: medium", "risk: low", "risk: manual"];
|
||||||
const sizeDisplayOrder = ["size: XS", "size: S", "size: M", "size: L", "size: XL"];
|
const sizeDisplayOrder = ["size: XS", "size: S", "size: M", "size: L", "size: XL"];
|
||||||
const contributorDisplayOrder = [
|
const contributorDisplayOrder = [
|
||||||
|
|
@ -164,44 +164,72 @@ jobs:
|
||||||
contributorDisplayOrder.map((label, index) => [label, index])
|
contributorDisplayOrder.map((label, index) => [label, index])
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function hslToHex(h, s, l) {
|
||||||
|
const saturation = s / 100;
|
||||||
|
const lightness = l / 100;
|
||||||
|
const chroma = (1 - Math.abs(2 * lightness - 1)) * saturation;
|
||||||
|
const huePrime = ((h % 360) + 360) % 360 / 60;
|
||||||
|
const x = chroma * (1 - Math.abs((huePrime % 2) - 1));
|
||||||
|
let red = 0;
|
||||||
|
let green = 0;
|
||||||
|
let blue = 0;
|
||||||
|
|
||||||
|
if (huePrime >= 0 && huePrime < 1) {
|
||||||
|
red = chroma;
|
||||||
|
green = x;
|
||||||
|
} else if (huePrime < 2) {
|
||||||
|
red = x;
|
||||||
|
green = chroma;
|
||||||
|
} else if (huePrime < 3) {
|
||||||
|
green = chroma;
|
||||||
|
blue = x;
|
||||||
|
} else if (huePrime < 4) {
|
||||||
|
green = x;
|
||||||
|
blue = chroma;
|
||||||
|
} else if (huePrime < 5) {
|
||||||
|
red = x;
|
||||||
|
blue = chroma;
|
||||||
|
} else {
|
||||||
|
red = chroma;
|
||||||
|
blue = x;
|
||||||
|
}
|
||||||
|
|
||||||
|
const match = lightness - chroma / 2;
|
||||||
|
const toHex = (value) =>
|
||||||
|
Math.round((value + match) * 255)
|
||||||
|
.toString(16)
|
||||||
|
.padStart(2, "0");
|
||||||
|
|
||||||
|
return `${toHex(red)}${toHex(green)}${toHex(blue)}`.toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildGradientColorMap(labels) {
|
||||||
|
const colorMap = {};
|
||||||
|
const lastIndex = Math.max(labels.length - 1, 1);
|
||||||
|
|
||||||
|
for (let index = 0; index < labels.length; index += 1) {
|
||||||
|
const ratio = index / lastIndex;
|
||||||
|
const hue = 155 - ratio * 147;
|
||||||
|
const saturation = 34 + ratio * 8;
|
||||||
|
const lightness = 74 - ratio * 8;
|
||||||
|
colorMap[labels[index]] = hslToHex(hue, saturation, lightness);
|
||||||
|
}
|
||||||
|
|
||||||
|
return colorMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
const otherLabelColors = buildGradientColorMap(otherLabelDisplayOrder);
|
||||||
const staticLabelColors = {
|
const staticLabelColors = {
|
||||||
"size: XS": "E9F0F3",
|
"size: XS": "EAF1F4",
|
||||||
"size: S": "DDE8EE",
|
"size: S": "DEE9EF",
|
||||||
"size: M": "CEDBE4",
|
"size: M": "D0DDE6",
|
||||||
"size: L": "BDCEDB",
|
"size: L": "C1D0DC",
|
||||||
"size: XL": "AEBFCD",
|
"size: XL": "B2C3D1",
|
||||||
"risk: low": "B8D8B0",
|
"risk: low": "BFD8B5",
|
||||||
"risk: medium": "E2D391",
|
"risk: medium": "E4D39B",
|
||||||
"risk: high": "E0A090",
|
"risk: high": "E1A39A",
|
||||||
"risk: manual": "B7AFCF",
|
"risk: manual": "B9B1D2",
|
||||||
docs: "B7CAD6",
|
...otherLabelColors,
|
||||||
dependencies: "D8C99A",
|
|
||||||
ci: "AFA2CF",
|
|
||||||
core: "4A4F4A",
|
|
||||||
agent: "9FC4B8",
|
|
||||||
channel: "AFC4D6",
|
|
||||||
config: "C3BCD8",
|
|
||||||
cron: "C7D6A5",
|
|
||||||
daemon: "7C7F95",
|
|
||||||
doctor: "A8D6CD",
|
|
||||||
gateway: "D8A58F",
|
|
||||||
health: "A7DCCB",
|
|
||||||
heartbeat: "B7ACE0",
|
|
||||||
integration: "8CAFC4",
|
|
||||||
memory: "7F96B2",
|
|
||||||
observability: "6D7482",
|
|
||||||
onboard: "E6E0C8",
|
|
||||||
provider: "8A7896",
|
|
||||||
runtime: "8E88AF",
|
|
||||||
security: "D99084",
|
|
||||||
service: "B3C7D6",
|
|
||||||
skillforge: "B9B2DA",
|
|
||||||
skills: "C8C2E0",
|
|
||||||
tool: "9BCFBF",
|
|
||||||
tunnel: "8DAEC0",
|
|
||||||
tests: "DCE9EE",
|
|
||||||
scripts: "E7DFC6",
|
|
||||||
dev: "C4D3DE",
|
|
||||||
};
|
};
|
||||||
const staticLabelDescriptions = {
|
const staticLabelDescriptions = {
|
||||||
"size: XS": "Auto size: <=80 non-doc changed lines.",
|
"size: XS": "Auto size: <=80 non-doc changed lines.",
|
||||||
|
|
@ -250,29 +278,12 @@ jobs:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const modulePrefixColors = {
|
const modulePrefixColors = Object.fromEntries(
|
||||||
"agent:": "9FC4B8",
|
modulePrefixPriority.map((prefix) => [
|
||||||
"channel:": "AFC4D6",
|
`${prefix}:`,
|
||||||
"config:": "C3BCD8",
|
otherLabelColors[prefix] || "BFDADC",
|
||||||
"cron:": "C7D6A5",
|
])
|
||||||
"daemon:": "7C7F95",
|
);
|
||||||
"doctor:": "A8D6CD",
|
|
||||||
"gateway:": "D8A58F",
|
|
||||||
"health:": "A7DCCB",
|
|
||||||
"heartbeat:": "B7ACE0",
|
|
||||||
"integration:": "8CAFC4",
|
|
||||||
"memory:": "7F96B2",
|
|
||||||
"observability:": "6D7482",
|
|
||||||
"onboard:": "E6E0C8",
|
|
||||||
"provider:": "8A7896",
|
|
||||||
"runtime:": "8E88AF",
|
|
||||||
"security:": "D99084",
|
|
||||||
"service:": "B3C7D6",
|
|
||||||
"skillforge:": "B9B2DA",
|
|
||||||
"skills:": "C8C2E0",
|
|
||||||
"tool:": "9BCFBF",
|
|
||||||
"tunnel:": "8DAEC0",
|
|
||||||
};
|
|
||||||
|
|
||||||
const providerKeywordHints = [
|
const providerKeywordHints = [
|
||||||
"deepseek",
|
"deepseek",
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ Merge-blocking checks should stay small and deterministic. Optional checks are u
|
||||||
- Additional behavior: noisy namespaces (`tool`, `provider`, `channel`) are compacted — one specific module keeps `prefix:component`; multiple specifics collapse to just `prefix`
|
- Additional behavior: noisy namespaces (`tool`, `provider`, `channel`) are compacted — one specific module keeps `prefix:component`; multiple specifics collapse to just `prefix`
|
||||||
- Additional behavior: applies contributor tiers on PRs by merged PR count (`experienced` >=10, `principal` >=20, `distinguished` >=50)
|
- Additional behavior: applies contributor tiers on PRs by merged PR count (`experienced` >=10, `principal` >=20, `distinguished` >=50)
|
||||||
- Additional behavior: final label set is priority-sorted (`risk:*` first, then `size:*`, then contributor tier, then module/path labels)
|
- Additional behavior: final label set is priority-sorted (`risk:*` first, then `size:*`, then contributor tier, then module/path labels)
|
||||||
|
- Additional behavior: managed label colors follow display order to produce a smooth left-to-right gradient when many labels are present
|
||||||
- Additional behavior: risk + size labels are auto-corrected on manual PR label edits (`labeled`/`unlabeled` events); apply `risk: manual` when maintainers intentionally override automated risk selection
|
- Additional behavior: risk + size labels are auto-corrected on manual PR label edits (`labeled`/`unlabeled` events); apply `risk: manual` when maintainers intentionally override automated risk selection
|
||||||
- High-risk heuristic paths: `src/security/**`, `src/runtime/**`, `src/gateway/**`, `src/tools/**`, `.github/workflows/**`
|
- High-risk heuristic paths: `src/security/**`, `src/runtime/**`, `src/gateway/**`, `src/tools/**`, `.github/workflows/**`
|
||||||
- Guardrail: maintainers can apply `risk: manual` to freeze automated risk recalculation
|
- Guardrail: maintainers can apply `risk: manual` to freeze automated risk recalculation
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ Maintain these branch protection rules on `main`:
|
||||||
- For `tool` / `provider` / `channel`, module labels are compacted to reduce noise: one specific module keeps `prefix:component`, but multiple specifics collapse to the base scope label.
|
- For `tool` / `provider` / `channel`, module labels are compacted to reduce noise: one specific module keeps `prefix:component`, but multiple specifics collapse to the base scope label.
|
||||||
- Label ordering is priority-first: `risk:*` -> `size:*` -> contributor tier -> module/path labels.
|
- Label ordering is priority-first: `risk:*` -> `size:*` -> contributor tier -> module/path labels.
|
||||||
- Hovering a label in GitHub shows its auto-managed description (rule/threshold summary).
|
- Hovering a label in GitHub shows its auto-managed description (rule/threshold summary).
|
||||||
|
- Managed label colors are arranged by display order to create a smooth gradient across long label rows.
|
||||||
- `Auto Response` posts first-time guidance and handles label-driven routing for low-signal items.
|
- `Auto Response` posts first-time guidance and handles label-driven routing for low-signal items.
|
||||||
|
|
||||||
### Step B: Validation
|
### Step B: Validation
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue