Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
209 changes: 121 additions & 88 deletions .github/workflows/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ jobs:
{ label: "experienced contributor", minMergedPRs: 10 },
];
const contributorTierLabels = contributorTierRules.map((rule) => rule.label);
const contributorTierColor = "C5D7A2";
const contributorTierColor = "58BCFF";

const managedPathLabels = [
"docs",
Expand Down Expand Up @@ -108,39 +108,39 @@ jobs:
{ root: "src/tunnel/", prefix: "tunnel", coreEntries: new Set(["mod.rs"]) },
];
const managedModulePrefixes = [...new Set(moduleNamespaceRules.map((rule) => `${rule.prefix}:`))];
const modulePrefixPriority = [
"security",
"runtime",
"gateway",
const otherLabelDisplayOrder = [
"health",
"tool",
"provider",
"channel",
"config",
"memory",
"agent",
"integration",
"observability",
"onboard",
"memory",
"channel",
"service",
"integration",
"tunnel",
"cron",
"config",
"observability",
"docs",
"dev",
"tests",
"skills",
"skillforge",
"provider",
"runtime",
"heartbeat",
"daemon",
"doctor",
"health",
"heartbeat",
"skillforge",
"skills",
];
const pathLabelPriority = [
...modulePrefixPriority,
"core",
"onboard",
"cron",
"ci",
"dependencies",
"tests",
"gateway",
"security",
"core",
"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 sizeDisplayOrder = ["size: XS", "size: S", "size: M", "size: L", "size: XL"];
const contributorDisplayOrder = [
Expand All @@ -164,44 +164,47 @@ jobs:
contributorDisplayOrder.map((label, index) => [label, index])
);

const otherLabelColors = {
health: "74AF9E",
tool: "58A89B",
agent: "63A685",
memory: "6FAF87",
channel: "4D90E4",
service: "81B2A4",
integration: "6FAE98",
tunnel: "83AD97",
config: "8FA3B8",
observability: "63A9B1",
docs: "6E9FC7",
dev: "9EA8B2",
tests: "86B1B0",
skills: "A3B08F",
skillforge: "AEA98A",
provider: "756AD8",
runtime: "8794BF",
heartbeat: "A1AE85",
daemon: "AAA680",
doctor: "A5B88D",
onboard: "B8A66F",
cron: "B89E7E",
ci: "9098B7",
dependencies: "8298C7",
gateway: "9B8DD3",
security: "CD6B63",
core: "AE8F7D",
scripts: "AE9C8A",
};
const staticLabelColors = {
"size: XS": "E9F0F3",
"size: S": "DDE8EE",
"size: M": "CEDBE4",
"size: L": "BDCEDB",
"size: XL": "AEBFCD",
"risk: low": "B8D8B0",
"risk: medium": "E2D391",
"risk: high": "E0A090",
"risk: manual": "B7AFCF",
docs: "B7CAD6",
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",
"size: XS": "D6B1B7",
"size: S": "CFA0A8",
"size: M": "C78F98",
"size: L": "BF7E89",
"size: XL": "B66E7A",
"risk: low": "7CBB8A",
"risk: medium": "D8B46B",
"risk: high": "D96A6A",
"risk: manual": "8E7CC3",
...otherLabelColors,
};
const staticLabelDescriptions = {
"size: XS": "Auto size: <=80 non-doc changed lines.",
Expand Down Expand Up @@ -250,29 +253,12 @@ jobs:
}
}

const modulePrefixColors = {
"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",
};
const modulePrefixColors = Object.fromEntries(
modulePrefixPriority.map((prefix) => [
`${prefix}:`,
otherLabelColors[prefix] || "BFDADC",
])
);

const providerKeywordHints = [
"deepseek",
Expand Down Expand Up @@ -418,6 +404,48 @@ jobs:
return refined;
}

function compactNoisyModuleLabels(labels) {
const noisyPrefixes = new Set(["tool", "provider", "channel"]);
const groupedSegments = new Map();
const compacted = new Set();
const forcePathPrefixes = new Set();

for (const label of labels) {
const parsed = parseModuleLabel(label);
if (!parsed) continue;
if (!groupedSegments.has(parsed.prefix)) {
groupedSegments.set(parsed.prefix, new Set());
}
groupedSegments.get(parsed.prefix).add(parsed.segment);
}

for (const label of labels) {
const parsed = parseModuleLabel(label);
if (!parsed) continue;
if (!noisyPrefixes.has(parsed.prefix)) {
compacted.add(label);
}
}

for (const [prefix, segments] of groupedSegments) {
if (!noisyPrefixes.has(prefix)) continue;

const specificSegments = [...segments].filter((segment) => segment !== "core");
const uniqueSpecificSegments = [...new Set(specificSegments)];

if (uniqueSpecificSegments.length === 1) {
compacted.add(`${prefix}:${uniqueSpecificSegments[0]}`);
} else {
forcePathPrefixes.add(prefix);
}
}

return {
moduleLabels: compacted,
forcePathPrefixes,
};
}

function colorForLabel(label) {
if (staticLabelColors[label]) return staticLabelColors[label];
const matchedPrefix = Object.keys(modulePrefixColors).find((prefix) => label.startsWith(prefix));
Expand Down Expand Up @@ -558,8 +586,11 @@ jobs:
}

const refinedModuleLabels = refineModuleLabels(detectedModuleLabels);
const compactedModuleState = compactNoisyModuleLabels(refinedModuleLabels);
const selectedModuleLabels = compactedModuleState.moduleLabels;
const forcePathPrefixes = compactedModuleState.forcePathPrefixes;
const modulePrefixesWithLabels = new Set(
[...refinedModuleLabels]
[...selectedModuleLabels]
.map((label) => parseModuleLabel(label)?.prefix)
.filter(Boolean)
);
Expand All @@ -571,9 +602,11 @@ jobs:
});
const currentLabelNames = currentLabels.map((label) => label.name);
const currentPathLabels = currentLabelNames.filter((label) => managedPathLabelSet.has(label));
const candidatePathLabels = new Set([...currentPathLabels, ...forcePathPrefixes]);

const dedupedPathLabels = currentPathLabels.filter((label) => {
const dedupedPathLabels = [...candidatePathLabels].filter((label) => {
if (label === "core") return true;
if (forcePathPrefixes.has(label)) return true;
return !modulePrefixesWithLabels.has(label);
});

Expand Down Expand Up @@ -627,7 +660,7 @@ jobs:
manualRiskOverrideLabel,
...managedPathLabels,
...contributorTierLabels,
...refinedModuleLabels,
...selectedModuleLabels,
]);

for (const label of labelsToEnsure) {
Expand Down Expand Up @@ -663,7 +696,7 @@ jobs:
const manualRiskSelection =
currentLabelNames.find((label) => computedRiskLabels.includes(label)) || riskLabel;

const moduleLabelList = sortModuleLabels([...refinedModuleLabels]);
const moduleLabelList = sortModuleLabels([...selectedModuleLabels]);
const contributorLabelList = contributorTierLabel ? [contributorTierLabel] : [];
const selectedRiskLabels = hasManualRiskOverride
? sortByPriority([manualRiskSelection, manualRiskOverrideLabel], riskPriorityIndex)
Expand Down
2 changes: 2 additions & 0 deletions docs/ci-map.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@ Merge-blocking checks should stay small and deterministic. Optional checks are u
- Additional behavior: label descriptions are auto-managed as hover tooltips to explain each auto-judgment rule
- Additional behavior: provider-related keywords in provider/config/onboard/integration changes are promoted to `provider:*` labels (for example `provider:kimi`, `provider:deepseek`)
- Additional behavior: hierarchical de-duplication keeps only the most specific scope labels (for example `tool:composio` suppresses `tool:core` and `tool`)
- 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: 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
- 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
Expand Down
2 changes: 2 additions & 0 deletions docs/pr-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,10 @@ Maintain these branch protection rules on `main`:

- Contributor opens PR with full `.github/pull_request_template.md`.
- `PR Labeler` applies scope/path labels + size labels + risk labels + module labels (for example `channel:telegram`, `provider:kimi`, `tool:shell`) and contributor tiers by merged PR count (`experienced` >=10, `principal` >=20, `distinguished` >=50), while de-duplicating less-specific scope labels when a more specific module label is present.
- 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.
- 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.

### Step B: Validation
Expand Down
2 changes: 2 additions & 0 deletions src/channels/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1783,3 +1783,5 @@ mod tests {
assert!(calls.load(Ordering::SeqCst) >= 1);
}
}

// color preview seed on main
2 changes: 2 additions & 0 deletions src/providers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -845,3 +845,5 @@ mod tests {
assert_eq!(result, input);
}
}

// color preview seed on main
2 changes: 2 additions & 0 deletions src/tools/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -358,3 +358,5 @@ mod tests {
assert!(!names.contains(&"delegate"));
}
}

// color preview seed on main
Loading