

























@@ -0,0 +1,288 @@
1+#!/usr/bin/env node
2+import { execFileSync } from "node:child_process";
3+4+const REPO = "openclaw/openclaw";
5+const APPLY = process.argv.includes("--apply");
6+7+const COLORS = {
8+saturatedRed: "B60205",
9+saturatedBugRed: "D73A4A",
10+saturatedOrangeRed: "D93F0B",
11+saturatedAmber: "FBCA04",
12+softerAmber: "F9D65C",
13+paleYellow: "F7E7A1",
14+saturatedGreen: "0E8A16",
15+mutedGreen: "B8E0B0",
16+paleGreen: "DDF4E4",
17+proofGreen: "C2E0C6",
18+mutedProofGreen: "9BD3A0",
19+overrideGreen: "DDECCF",
20+saturatedBlue: "0F2CCE",
21+paleBlue: "CFE3F8",
22+channelBlue: "DDEBFA",
23+dedupeBlue: "BFD4F2",
24+triageBlue: "D8E8F8",
25+saturatedPurple: "7057FF",
26+mutedPurple: "D9CCF5",
27+appPurple: "EADFF8",
28+neutralGray: "EDEDED",
29+duplicateGray: "CFD3D7",
30+darkGray: "8C8C8C",
31+mutedRose: "E8C4CB",
32+mutedRed: "E99695",
33+black: "000000",
34+white: "FFFFFF",
35+clawsweeperOrange: "F97316",
36+helpGreen: "008672",
37+maintainerYellow: "FFD700",
38+};
39+40+const EXACT_COLORS = new Map(
41+Object.entries({
42+P0: COLORS.saturatedRed,
43+P1: COLORS.saturatedOrangeRed,
44+P2: COLORS.saturatedAmber,
45+P3: COLORS.mutedGreen,
46+"impact:data-loss": COLORS.saturatedRed,
47+"impact:security": COLORS.saturatedRed,
48+"impact:crash-loop": COLORS.saturatedOrangeRed,
49+"impact:message-loss": COLORS.saturatedOrangeRed,
50+"impact:auth-provider": COLORS.softerAmber,
51+"impact:session-state": COLORS.softerAmber,
52+bug: COLORS.saturatedBugRed,
53+"bug:crash": COLORS.saturatedRed,
54+"bug:behavior": COLORS.saturatedBugRed,
55+regression: COLORS.saturatedOrangeRed,
56+security: COLORS.saturatedRed,
57+"beta-blocker": COLORS.saturatedOrangeRed,
58+"proof: supplied": COLORS.proofGreen,
59+"proof: sufficient": COLORS.mutedProofGreen,
60+"proof: override": COLORS.overrideGreen,
61+"triage:blocked": COLORS.saturatedAmber,
62+"triage:bug": COLORS.saturatedBugRed,
63+"triage:done": COLORS.mutedGreen,
64+"triage:needs-review": COLORS.paleBlue,
65+"triage:started": COLORS.mutedPurple,
66+agents: COLORS.mutedPurple,
67+docs: COLORS.paleBlue,
68+cli: COLORS.paleBlue,
69+commands: COLORS.paleBlue,
70+scripts: COLORS.mutedPurple,
71+gateway: COLORS.mutedPurple,
72+codex: COLORS.neutralGray,
73+docker: COLORS.paleGreen,
74+tui: COLORS.paleGreen,
75+"extensions: NEW": COLORS.channelBlue,
76+clawsweeper: COLORS.clawsweeperOrange,
77+"clawsweeper:human-review": COLORS.saturatedRed,
78+"clawsweeper:needs-security-review": COLORS.saturatedRed,
79+"clawsweeper:needs-live-repro": COLORS.saturatedAmber,
80+"clawsweeper:needs-maintainer-review": COLORS.saturatedAmber,
81+"clawsweeper:needs-product-decision": COLORS.saturatedAmber,
82+"clawsweeper:automerge": COLORS.saturatedGreen,
83+"clawsweeper:queueable-fix": COLORS.saturatedGreen,
84+"clawsweeper:fix-shape-clear": COLORS.mutedProofGreen,
85+"clawsweeper:autofix": COLORS.paleBlue,
86+"clawsweeper:commit-finding": COLORS.paleBlue,
87+"clawsweeper:autogenerated": COLORS.mutedPurple,
88+"clawsweeper:linked-pr-open": COLORS.mutedPurple,
89+"clawsweeper:merge-ready": COLORS.mutedPurple,
90+"clawsweeper:current-main-repro": COLORS.paleBlue,
91+"clawsweeper:source-repro": COLORS.paleBlue,
92+"clawsweeper:not-repro-on-main": COLORS.proofGreen,
93+"clawsweeper:no-new-fix-pr": COLORS.neutralGray,
94+"clawsweeper:needs-info": COLORS.appPurple,
95+"close:spam": COLORS.black,
96+"close:duplicate": COLORS.mutedRed,
97+"close:already-fixed": COLORS.paleBlue,
98+"close:cannot-repro": COLORS.paleYellow,
99+"close:invalid": COLORS.neutralGray,
100+"close:not-planned": COLORS.darkGray,
101+"close:superseded": COLORS.mutedPurple,
102+duplicate: COLORS.duplicateGray,
103+invalid: COLORS.paleYellow,
104+wontfix: COLORS.white,
105+"r: spam": COLORS.saturatedRed,
106+"r: moltbook": COLORS.saturatedRed,
107+"r: too-many-prs": COLORS.saturatedOrangeRed,
108+"r: no-ci-pr": COLORS.saturatedOrangeRed,
109+"r: bluebubbles": COLORS.saturatedOrangeRed,
110+"r: testflight": COLORS.saturatedOrangeRed,
111+"r: false-positive": COLORS.saturatedOrangeRed,
112+"r: skill": COLORS.mutedPurple,
113+"r: third-party-extension": COLORS.mutedPurple,
114+"r: support": COLORS.mutedGreen,
115+"r: too-many-prs-override": COLORS.overrideGreen,
116+maintainer: COLORS.maintainerYellow,
117+"trusted-contributor": COLORS.neutralGray,
118+"help wanted": COLORS.helpGreen,
119+"good first issue": COLORS.saturatedPurple,
120+"In Progress": COLORS.saturatedBlue,
121+"no-stale": COLORS.mutedGreen,
122+stale: COLORS.paleYellow,
123+"trigger-response": COLORS.saturatedAmber,
124+"bad-barnacle": COLORS.mutedRed,
125+clownfish: COLORS.neutralGray,
126+"mantis: telegram-visible-proof": COLORS.mutedPurple,
127+"dedupe:parent": COLORS.dedupeBlue,
128+"dedupe:child": COLORS.paleBlue,
129+dependencies: COLORS.neutralGray,
130+"dependencies-changed": COLORS.paleYellow,
131+github_actions: COLORS.neutralGray,
132+go: COLORS.neutralGray,
133+java: COLORS.neutralGray,
134+swift_package_manager: COLORS.neutralGray,
135+"cannot-reproduce": COLORS.dedupeBlue,
136+question: COLORS.appPurple,
137+enhancement: COLORS.channelBlue,
138+"pi-issue": COLORS.saturatedOrangeRed,
139+aardvark: COLORS.neutralGray,
140+"cohort-4": COLORS.mutedPurple,
141+dirty: COLORS.saturatedRed,
142+}),
143+);
144+145+const FAMILY_RULES = [
146+{
147+family: "channel",
148+match: (name) => name.startsWith("channel: "),
149+color: COLORS.channelBlue,
150+reason: "routine channel taxonomy stays visually quiet",
151+},
152+{
153+family: "app",
154+match: (name) => name.startsWith("app: "),
155+color: COLORS.appPurple,
156+reason: "app platform taxonomy stays muted",
157+},
158+{
159+family: "extension",
160+match: (name) => name.startsWith("extensions: "),
161+color: COLORS.neutralGray,
162+reason: "plugin implementation taxonomy should not compete with priority",
163+},
164+{
165+family: "plugin",
166+match: (name) => name.startsWith("plugin: "),
167+color: COLORS.neutralGray,
168+reason: "plugin taxonomy stays neutral unless it becomes an action gate",
169+},
170+{
171+family: "size",
172+match: (name) => name.startsWith("size: "),
173+color: COLORS.mutedRose,
174+reason: "size text carries the value, so one muted family color is enough",
175+},
176+{
177+family: "triage-candidate",
178+match: (name) => name.startsWith("triage:"),
179+color: COLORS.triageBlue,
180+reason: "routine triage candidates stay softer than blockers and priorities",
181+},
182+];
183+184+function wantedPolicy(name) {
185+if (EXACT_COLORS.has(name)) {
186+return { color: EXACT_COLORS.get(name), family: exactFamily(name), source: "exact" };
187+}
188+const rule = FAMILY_RULES.find((candidate) => candidate.match(name));
189+if (rule) {
190+return { color: rule.color, family: rule.family, source: "family" };
191+}
192+return null;
193+}
194+195+function exactFamily(name) {
196+if (/^P[0-3]$/.test(name)) {
197+return "priority";
198+}
199+if (name.startsWith("impact:")) {
200+return "impact";
201+}
202+if (name.startsWith("clawsweeper")) {
203+return "clawsweeper";
204+}
205+if (name.startsWith("close:") || name.startsWith("r:")) {
206+return "resolution";
207+}
208+if (name.startsWith("proof:")) {
209+return "proof";
210+}
211+if (name.startsWith("triage:")) {
212+return "triage-status";
213+}
214+if (name.startsWith("bug") || name === "security" || name === "regression") {
215+return "risk";
216+}
217+return "specific";
218+}
219+220+function ghJson(args) {
221+return JSON.parse(execFileSync("gh", args, { encoding: "utf8" }));
222+}
223+224+function gh(args) {
225+execFileSync("gh", args, { encoding: "utf8", stdio: APPLY ? "inherit" : "pipe" });
226+}
227+228+function fetchLabels() {
229+const labels = [];
230+let cursor = null;
231+for (;;) {
232+const query = `query($cursor:String) {
233+ repository(owner:"openclaw", name:"openclaw") {
234+ labels(first:100, after:$cursor, orderBy:{field:NAME,direction:ASC}) {
235+ nodes { name color description }
236+ pageInfo { hasNextPage endCursor }
237+ }
238+ }
239+ }`;
240+const response = ghJson([
241+"api",
242+"graphql",
243+"-f",
244+`query=${query}`,
245+"-f",
246+`cursor=${cursor ?? ""}`,
247+]);
248+const page = response.data.repository.labels;
249+labels.push(...page.nodes);
250+if (!page.pageInfo.hasNextPage) {
251+return labels;
252+}
253+cursor = page.pageInfo.endCursor;
254+}
255+}
256+257+const changes = fetchLabels()
258+.map((label) => ({
259+name: label.name,
260+current: label.color.toUpperCase(),
261+policy: wantedPolicy(label.name),
262+}))
263+.filter((label) => label.policy && label.current !== label.policy.color)
264+.toSorted((a, b) => a.name.localeCompare(b.name));
265+266+if (changes.length === 0) {
267+console.log("No label color changes needed.");
268+process.exit(0);
269+}
270+271+const familyCounts = new Map();
272+for (const change of changes) {
273+familyCounts.set(change.policy.family, (familyCounts.get(change.policy.family) ?? 0) + 1);
274+console.log(
275+`${APPLY ? "update" : "would update"} [${change.policy.family}] ${change.name}: ${change.current} -> ${change.policy.color}`,
276+);
277+if (APPLY) {
278+gh(["label", "edit", change.name, "--repo", REPO, "--color", change.policy.color]);
279+}
280+}
281+282+console.log("Family summary:");
283+for (const [family, count] of [...familyCounts.entries()].toSorted(([a], [b]) =>
284+a.localeCompare(b),
285+)) {
286+console.log(`- ${family}: ${count}`);
287+}
288+console.log(`${APPLY ? "Updated" : "Would update"} ${changes.length} labels.`);
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。