mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-23 05:01:31 +00:00
* chore(labels): cool label palette * chore(labels): soften taxonomy colors * chore(labels): finalize label palette * chore(labels): harden final palette
301 lines
9.5 KiB
JavaScript
301 lines
9.5 KiB
JavaScript
#!/usr/bin/env node
|
|
import { execFileSync } from "node:child_process";
|
|
|
|
const REPO = "openclaw/openclaw";
|
|
const APPLY = process.argv.includes("--apply");
|
|
|
|
const COLORS = {
|
|
saturatedRed: "B60205",
|
|
saturatedBugRed: "D73A4A",
|
|
saturatedOrangeRed: "D93F0B",
|
|
saturatedAmber: "FBCA04",
|
|
softerAmber: "F9D65C",
|
|
paleYellow: "F7E7A1",
|
|
saturatedGreen: "0E8A16",
|
|
mutedGreen: "8C959F",
|
|
paleGreen: "D6E3DA",
|
|
proofGreen: "2DA44E",
|
|
mutedProofGreen: "1A7F37",
|
|
overrideGreen: "2DA44E",
|
|
saturatedBlue: "0F2CCE",
|
|
paleBlue: "0A3069",
|
|
channelBlue: "0969DA",
|
|
dedupeBlue: "57606A",
|
|
triageBlue: "0969DA",
|
|
saturatedPurple: "7057FF",
|
|
mutedPurple: "57606A",
|
|
taxonomyGray: "6E7781",
|
|
taxonomySteel: "57606A",
|
|
appPurple: "6E7781",
|
|
neutralGray: "E5E7EB",
|
|
duplicateGray: "D1D5DB",
|
|
darkGray: "8C8C8C",
|
|
mutedRose: "8C959F",
|
|
mutedRed: "E99695",
|
|
black: "000000",
|
|
white: "FFFFFF",
|
|
clawsweeperOrange: "F97316",
|
|
helpGreen: "008672",
|
|
maintainerYellow: "FFD700",
|
|
};
|
|
|
|
const EXACT_COLORS = new Map(
|
|
Object.entries({
|
|
P0: COLORS.saturatedRed,
|
|
P1: COLORS.saturatedOrangeRed,
|
|
P2: COLORS.saturatedAmber,
|
|
P3: COLORS.mutedGreen,
|
|
"rating: 🦀 challenger crab": "1F883D",
|
|
"rating: 🦞 diamond lobster": "0969DA",
|
|
"rating: 🐚 platinum hermit": "0F766E",
|
|
"rating: 🦐 gold shrimp": "B7791F",
|
|
"rating: 🦪 silver shellfish": "7A828E",
|
|
"rating: 🧂 unranked krab": "8C2F39",
|
|
"rating: 🌊 off-meta tidepool": "6E7781",
|
|
"impact:data-loss": COLORS.saturatedRed,
|
|
"impact:security": COLORS.saturatedRed,
|
|
"impact:crash-loop": COLORS.saturatedOrangeRed,
|
|
"impact:message-loss": COLORS.saturatedOrangeRed,
|
|
"impact:auth-provider": COLORS.softerAmber,
|
|
"impact:session-state": COLORS.softerAmber,
|
|
bug: COLORS.saturatedBugRed,
|
|
"bug:crash": COLORS.saturatedRed,
|
|
"bug:behavior": COLORS.saturatedBugRed,
|
|
regression: COLORS.saturatedOrangeRed,
|
|
security: COLORS.saturatedRed,
|
|
"beta-blocker": COLORS.saturatedOrangeRed,
|
|
"proof: supplied": COLORS.proofGreen,
|
|
"proof: sufficient": COLORS.mutedProofGreen,
|
|
"proof: override": COLORS.overrideGreen,
|
|
"triage:blocked": COLORS.saturatedAmber,
|
|
"triage:bug": COLORS.saturatedBugRed,
|
|
"triage:done": COLORS.mutedGreen,
|
|
"triage:needs-review": COLORS.paleBlue,
|
|
"triage:started": COLORS.mutedPurple,
|
|
agents: COLORS.taxonomySteel,
|
|
docs: COLORS.paleBlue,
|
|
cli: COLORS.paleBlue,
|
|
commands: COLORS.paleBlue,
|
|
scripts: COLORS.mutedPurple,
|
|
gateway: COLORS.mutedPurple,
|
|
codex: COLORS.taxonomySteel,
|
|
docker: COLORS.paleGreen,
|
|
tui: COLORS.paleGreen,
|
|
"extensions: NEW": COLORS.channelBlue,
|
|
clawsweeper: COLORS.clawsweeperOrange,
|
|
"clawsweeper:human-review": COLORS.saturatedRed,
|
|
"clawsweeper:needs-security-review": COLORS.saturatedRed,
|
|
"clawsweeper:needs-live-repro": COLORS.saturatedAmber,
|
|
"clawsweeper:needs-maintainer-review": COLORS.saturatedAmber,
|
|
"clawsweeper:needs-product-decision": COLORS.saturatedAmber,
|
|
"clawsweeper:automerge": COLORS.saturatedGreen,
|
|
"clawsweeper:queueable-fix": COLORS.saturatedGreen,
|
|
"clawsweeper:fix-shape-clear": COLORS.mutedProofGreen,
|
|
"clawsweeper:autofix": COLORS.paleBlue,
|
|
"clawsweeper:commit-finding": COLORS.paleBlue,
|
|
"clawsweeper:autogenerated": COLORS.mutedPurple,
|
|
"clawsweeper:linked-pr-open": COLORS.mutedPurple,
|
|
"clawsweeper:merge-ready": COLORS.mutedPurple,
|
|
"clawsweeper:current-main-repro": COLORS.paleBlue,
|
|
"clawsweeper:source-repro": COLORS.paleBlue,
|
|
"clawsweeper:not-repro-on-main": COLORS.proofGreen,
|
|
"clawsweeper:no-new-fix-pr": COLORS.neutralGray,
|
|
"clawsweeper:needs-info": COLORS.appPurple,
|
|
"close:spam": COLORS.black,
|
|
"close:duplicate": COLORS.mutedRed,
|
|
"close:already-fixed": COLORS.paleBlue,
|
|
"close:cannot-repro": COLORS.paleYellow,
|
|
"close:invalid": COLORS.neutralGray,
|
|
"close:not-planned": COLORS.darkGray,
|
|
"close:superseded": COLORS.mutedPurple,
|
|
duplicate: COLORS.duplicateGray,
|
|
invalid: COLORS.paleYellow,
|
|
wontfix: COLORS.white,
|
|
"r: spam": COLORS.saturatedRed,
|
|
"r: moltbook": COLORS.saturatedRed,
|
|
"r: too-many-prs": COLORS.saturatedOrangeRed,
|
|
"r: no-ci-pr": COLORS.saturatedOrangeRed,
|
|
"r: bluebubbles": COLORS.saturatedOrangeRed,
|
|
"r: testflight": COLORS.saturatedOrangeRed,
|
|
"r: false-positive": COLORS.saturatedOrangeRed,
|
|
"r: skill": COLORS.mutedPurple,
|
|
"r: third-party-extension": COLORS.mutedPurple,
|
|
"r: support": COLORS.mutedGreen,
|
|
"r: too-many-prs-override": COLORS.overrideGreen,
|
|
maintainer: COLORS.maintainerYellow,
|
|
"trusted-contributor": COLORS.neutralGray,
|
|
"help wanted": COLORS.helpGreen,
|
|
"good first issue": COLORS.saturatedPurple,
|
|
"In Progress": COLORS.saturatedBlue,
|
|
"no-stale": COLORS.mutedGreen,
|
|
stale: COLORS.paleYellow,
|
|
"trigger-response": COLORS.saturatedAmber,
|
|
"bad-barnacle": COLORS.mutedRed,
|
|
clownfish: COLORS.neutralGray,
|
|
"mantis: telegram-visible-proof": COLORS.mutedPurple,
|
|
"dedupe:parent": COLORS.dedupeBlue,
|
|
"dedupe:child": COLORS.paleBlue,
|
|
dependencies: COLORS.neutralGray,
|
|
"dependencies-changed": COLORS.paleYellow,
|
|
github_actions: COLORS.neutralGray,
|
|
go: COLORS.neutralGray,
|
|
java: COLORS.neutralGray,
|
|
swift_package_manager: COLORS.neutralGray,
|
|
"cannot-reproduce": COLORS.dedupeBlue,
|
|
question: COLORS.appPurple,
|
|
enhancement: COLORS.channelBlue,
|
|
"pi-issue": COLORS.saturatedOrangeRed,
|
|
aardvark: COLORS.neutralGray,
|
|
"cohort-4": COLORS.mutedPurple,
|
|
dirty: COLORS.saturatedRed,
|
|
}),
|
|
);
|
|
|
|
const FAMILY_RULES = [
|
|
{
|
|
family: "channel",
|
|
match: (name) => name.startsWith("channel: "),
|
|
color: COLORS.channelBlue,
|
|
reason: "routine channel taxonomy stays visually quiet",
|
|
},
|
|
{
|
|
family: "app",
|
|
match: (name) => name.startsWith("app: "),
|
|
color: COLORS.appPurple,
|
|
reason: "app platform taxonomy stays muted",
|
|
},
|
|
{
|
|
family: "extension",
|
|
match: (name) => name.startsWith("extensions: "),
|
|
color: COLORS.taxonomyGray,
|
|
reason: "plugin implementation taxonomy should not compete with priority",
|
|
},
|
|
{
|
|
family: "plugin",
|
|
match: (name) => name.startsWith("plugin: "),
|
|
color: COLORS.taxonomyGray,
|
|
reason: "plugin taxonomy stays neutral unless it becomes an action gate",
|
|
},
|
|
{
|
|
family: "size",
|
|
match: (name) => name.startsWith("size: "),
|
|
color: COLORS.mutedRose,
|
|
reason: "size text carries the value, so one muted family color is enough",
|
|
},
|
|
{
|
|
family: "triage-candidate",
|
|
match: (name) => name.startsWith("triage:"),
|
|
color: COLORS.triageBlue,
|
|
reason: "routine triage candidates stay softer than blockers and priorities",
|
|
},
|
|
];
|
|
|
|
function wantedPolicy(name) {
|
|
if (EXACT_COLORS.has(name)) {
|
|
return { color: EXACT_COLORS.get(name), family: exactFamily(name), source: "exact" };
|
|
}
|
|
const rule = FAMILY_RULES.find((candidate) => candidate.match(name));
|
|
if (rule) {
|
|
return { color: rule.color, family: rule.family, source: "family" };
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function exactFamily(name) {
|
|
if (/^P[0-3]$/.test(name)) {
|
|
return "priority";
|
|
}
|
|
if (name.startsWith("rating:")) {
|
|
return "rating";
|
|
}
|
|
if (name.startsWith("impact:")) {
|
|
return "impact";
|
|
}
|
|
if (name.startsWith("clawsweeper")) {
|
|
return "clawsweeper";
|
|
}
|
|
if (name.startsWith("close:") || name.startsWith("r:")) {
|
|
return "resolution";
|
|
}
|
|
if (name.startsWith("proof:")) {
|
|
return "proof";
|
|
}
|
|
if (name.startsWith("triage:")) {
|
|
return "triage-status";
|
|
}
|
|
if (name.startsWith("bug") || name === "security" || name === "regression") {
|
|
return "risk";
|
|
}
|
|
return "specific";
|
|
}
|
|
|
|
function ghJson(args) {
|
|
return JSON.parse(execFileSync("gh", args, { encoding: "utf8" }));
|
|
}
|
|
|
|
function gh(args) {
|
|
execFileSync("gh", args, { encoding: "utf8", stdio: APPLY ? "inherit" : "pipe" });
|
|
}
|
|
|
|
function fetchLabels() {
|
|
const labels = [];
|
|
let cursor = null;
|
|
for (;;) {
|
|
const query = `query($cursor:String) {
|
|
repository(owner:"openclaw", name:"openclaw") {
|
|
labels(first:100, after:$cursor, orderBy:{field:NAME,direction:ASC}) {
|
|
nodes { name color description }
|
|
pageInfo { hasNextPage endCursor }
|
|
}
|
|
}
|
|
}`;
|
|
const response = ghJson([
|
|
"api",
|
|
"graphql",
|
|
"-f",
|
|
`query=${query}`,
|
|
"-f",
|
|
`cursor=${cursor ?? ""}`,
|
|
]);
|
|
const page = response.data.repository.labels;
|
|
labels.push(...page.nodes);
|
|
if (!page.pageInfo.hasNextPage) {
|
|
return labels;
|
|
}
|
|
cursor = page.pageInfo.endCursor;
|
|
}
|
|
}
|
|
|
|
const changes = fetchLabels()
|
|
.map((label) => ({
|
|
name: label.name,
|
|
current: label.color.toUpperCase(),
|
|
policy: wantedPolicy(label.name),
|
|
}))
|
|
.filter((label) => label.policy && label.current !== label.policy.color)
|
|
.toSorted((a, b) => a.name.localeCompare(b.name));
|
|
|
|
if (changes.length === 0) {
|
|
console.log("No label color changes needed.");
|
|
process.exit(0);
|
|
}
|
|
|
|
const familyCounts = new Map();
|
|
for (const change of changes) {
|
|
familyCounts.set(change.policy.family, (familyCounts.get(change.policy.family) ?? 0) + 1);
|
|
console.log(
|
|
`${APPLY ? "update" : "would update"} [${change.policy.family}] ${change.name}: ${change.current} -> ${change.policy.color}`,
|
|
);
|
|
if (APPLY) {
|
|
gh(["label", "edit", change.name, "--repo", REPO, "--color", change.policy.color]);
|
|
}
|
|
}
|
|
|
|
console.log("Family summary:");
|
|
for (const [family, count] of [...familyCounts.entries()].toSorted(([a], [b]) =>
|
|
a.localeCompare(b),
|
|
)) {
|
|
console.log(`- ${family}: ${count}`);
|
|
}
|
|
console.log(`${APPLY ? "Updated" : "Would update"} ${changes.length} labels.`);
|