Skip to content

Commit 05ecb40

Browse files
committed
feat(issues): add orthogonal area labels for triage
Map template Area fields and light heuristics to additive labels (provider, account-pool, catalog, gui, cli, proxy, platform, streaming, tools, install, service) beside existing kind/process labels, with open issue backfill via workflow_dispatch.
1 parent 6d54e5f commit 05ecb40

7 files changed

Lines changed: 498 additions & 5 deletions

File tree

.github/ISSUE_TEMPLATE/bug_report.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,12 @@ body:
3535
- Dashboard
3636
- Provider adapter
3737
- Authentication and account pool
38+
- Catalog / models
39+
- Streaming
40+
- Tools / MCP / web search
3841
- Installation or packaging
3942
- Service lifecycle
43+
- Platform (Windows / macOS / Linux)
4044
- Documentation
4145
- Other
4246
validations:

.github/ISSUE_TEMPLATE/feature_request.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,12 @@ body:
1919
- Dashboard
2020
- Provider adapters
2121
- Authentication and account pool
22+
- Catalog / models
23+
- Streaming
24+
- Tools / MCP / web search
2225
- Installation or packaging
2326
- Service lifecycle
27+
- Platform (Windows / macOS / Linux)
2428
- Documentation
2529
- Multiple areas
2630
- Other

.github/ISSUE_TEMPLATE/provider_compatibility.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ name: Provider or API compatibility
22
description: Report an incompatible provider, endpoint, request shape, response shape, or client integration.
33
labels:
44
- provider-compatibility
5+
- provider
56
body:
67
- type: markdown
78
attributes:

.github/scripts/issue-quality.cjs

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,125 @@ const KIND_TO_LABEL = {
250250
"provider-compatibility": "provider-compatibility",
251251
};
252252

253+
/**
254+
* Orthogonal product-area labels (additive beside kind/process labels).
255+
* Colors/descriptions are used when the workflow ensures labels exist.
256+
*/
257+
const AREA_LABELS = {
258+
provider: {
259+
color: "1D76DB",
260+
description: "Provider adapters, OpenAI-compat presets, upstream API quirks",
261+
},
262+
"account-pool": {
263+
color: "5319E7",
264+
description: "OAuth, credentials, Codex pool, quota, failover, plans",
265+
},
266+
catalog: {
267+
color: "006B75",
268+
description: "Model catalog, slugs, visibility, routed entries",
269+
},
270+
gui: {
271+
color: "D93F0B",
272+
description: "Dashboard, tray, settings UI",
273+
},
274+
cli: {
275+
color: "FBCA04",
276+
description: "CLI, config inject, packaging flags",
277+
},
278+
proxy: {
279+
color: "0E8A16",
280+
description: "HTTP proxy, routing, reverse-proxy / management auth",
281+
},
282+
platform: {
283+
color: "BFDADC",
284+
description: "OS/service/tray/ACL (Windows-heavy, not Windows-only)",
285+
},
286+
streaming: {
287+
color: "C5DEF5",
288+
description: "SSE, WebSocket, terminal stream frames",
289+
},
290+
tools: {
291+
color: "F9D0C4",
292+
description: "tool_calls, MCP, web-search / sidecar tools",
293+
},
294+
install: {
295+
color: "EDEDED",
296+
description: "Installation or packaging",
297+
},
298+
service: {
299+
color: "EDEDED",
300+
description: "Service lifecycle (WinSW/launchd/scheduler)",
301+
},
302+
};
303+
304+
/** Canonical Area dropdown text → area label(s). Keys are lowercased. */
305+
const AREA_FIELD_TO_LABELS = {
306+
cli: ["cli"],
307+
"proxy and routing": ["proxy"],
308+
dashboard: ["gui"],
309+
"provider adapter": ["provider"],
310+
"provider adapters": ["provider"],
311+
"authentication and account pool": ["account-pool"],
312+
"catalog / models": ["catalog"],
313+
streaming: ["streaming"],
314+
"tools / mcp / web search": ["tools"],
315+
"installation or packaging": ["install"],
316+
"service lifecycle": ["service"],
317+
"service lifecycle (config injection)": ["service"],
318+
"platform (windows / macos / linux)": ["platform"],
319+
documentation: ["documentation"],
320+
// No dedicated label; heuristics still run in detectAreaLabels.
321+
"multiple areas": [],
322+
other: [],
323+
};
324+
325+
const AREA_HEURISTICS = [
326+
{
327+
label: "account-pool",
328+
re: /\b(oauth|reauth|needsreauth|quota|account pool|codex.?auth|auto[- ]?switch|failover|refresh token|plan_type|chatgpt[- ]account|reset credit)\b/i,
329+
},
330+
{
331+
label: "catalog",
332+
re: /\b(catalog|model list|model visibility|virtual model|routed (catalog|entries|slug)|model slug)\b/i,
333+
},
334+
{
335+
label: "gui",
336+
re: /\b(dashboard|\bgui\b|tray|sidebar|settings tab)\b/i,
337+
},
338+
{
339+
label: "cli",
340+
re: /\b(ocx\b|config\.toml|config inject)\b/i,
341+
},
342+
{
343+
label: "proxy",
344+
re: /\b(reverse[- ]proxy|management api|admin[- ]token|\/api\/\*|bind(s)? the (old )?port|proxy running)\b/i,
345+
},
346+
{
347+
label: "platform",
348+
re: /\b(windows|macos|mac os|win32|darwin|wsl|winsw|launchd|systemd|icacls|\bacl\b)\b/i,
349+
},
350+
{
351+
label: "streaming",
352+
re: /\b(sse|websocket|\bws\b|stream(ing)?\b.{0,40}\b(truncat|terminal)|terminal (sse )?frame|without a terminal)\b/i,
353+
},
354+
{
355+
label: "tools",
356+
re: /\b(tool_calls?|tool[- ]calls?|\bmcp\b|web[- ]search|tool[- ]recall)\b/i,
357+
},
358+
{
359+
label: "install",
360+
re: /\b(npm (global )?install|packaging|release asset|npx ocx)\b/i,
361+
},
362+
{
363+
label: "service",
364+
re: /\b(ocx service|winsw|scheduler backend|launchd service)\b/i,
365+
},
366+
{
367+
label: "provider",
368+
re: /\b(provider adapter|openai[- ]compatible|provider[- ]compat|adapter quirk|upstream api|built[- ]in provider|provider preset)\b/i,
369+
},
370+
];
371+
253372
/**
254373
* Map a detected issue kind to its triage label. Returns null when unknown.
255374
*/
@@ -258,6 +377,70 @@ function labelForKind(kind) {
258377
return KIND_TO_LABEL[kind] || null;
259378
}
260379

380+
/**
381+
* Map a template Area dropdown value to orthogonal area label names.
382+
* Returns [] for Other / Multiple areas / unknown / empty.
383+
*
384+
* @param {unknown} areaText
385+
* @returns {string[]}
386+
*/
387+
function mapAreaFieldToLabels(areaText) {
388+
if (typeof areaText !== "string") return [];
389+
const key = areaText.replace(/\s+/g, " ").trim().toLowerCase();
390+
if (!key) return [];
391+
return AREA_FIELD_TO_LABELS[key] ? [...AREA_FIELD_TO_LABELS[key]] : [];
392+
}
393+
394+
/**
395+
* Conservative title/body heuristics for orthogonal area labels.
396+
*
397+
* @param {string} title
398+
* @param {string} body
399+
* @returns {string[]}
400+
*/
401+
function heuristicAreaLabels(title, body) {
402+
const text = `${title || ""}\n${body || ""}`;
403+
const out = [];
404+
for (const { label, re } of AREA_HEURISTICS) {
405+
if (re.test(text)) out.push(label);
406+
}
407+
return out;
408+
}
409+
410+
/**
411+
* Detect additive product-area labels from Area field, form defaults, and
412+
* title/body heuristics. Never invents per-provider labels.
413+
*
414+
* @param {{ title?: string, body?: string, labels?: string[] }} issue
415+
* @returns {string[]}
416+
*/
417+
function detectAreaLabels(issue) {
418+
const title = typeof issue?.title === "string" ? issue.title : "";
419+
const body = typeof issue?.body === "string" ? issue.body : "";
420+
const labels = Array.isArray(issue?.labels) ? issue.labels : [];
421+
422+
const areaSection = extractSection(body, "Area");
423+
const fromArea = mapAreaFieldToLabels(areaSection);
424+
const fromHeur = heuristicAreaLabels(title, body);
425+
const fromForm = [];
426+
if (labels.includes("provider-compatibility")) fromForm.push("provider");
427+
// Provider-compat form uses this heading instead of Area.
428+
if (extractSection(body, "Provider or upstream service") !== null) {
429+
fromForm.push("provider");
430+
}
431+
432+
const seen = new Set();
433+
const out = [];
434+
for (const label of [...fromArea, ...fromForm, ...fromHeur]) {
435+
if (!label || seen.has(label)) continue;
436+
// `documentation` is a kind label and also an Area mapping target.
437+
if (label !== "documentation" && !AREA_LABELS[label]) continue;
438+
seen.add(label);
439+
out.push(label);
440+
}
441+
return out;
442+
}
443+
261444
function countHeadings(body, headings) {
262445
let n = 0;
263446
for (const h of headings) {
@@ -952,6 +1135,11 @@ module.exports = {
9521135
hasConcreteDetail,
9531136
labelForKind,
9541137
KIND_TO_LABEL,
1138+
AREA_LABELS,
1139+
AREA_FIELD_TO_LABELS,
1140+
mapAreaFieldToLabels,
1141+
heuristicAreaLabels,
1142+
detectAreaLabels,
9551143
hasSubstantialStructuredContent,
9561144
rejectsWorkflowDispatchPullRequest,
9571145
rejectsWorkflowDispatchNonDefaultBranch,

.github/scripts/issue-quality.test.cjs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ const {
1313
shouldReopen,
1414
shouldEnforceClosure,
1515
labelForKind,
16+
AREA_LABELS,
17+
mapAreaFieldToLabels,
18+
detectAreaLabels,
1619
isPlaceholderOnlyValue,
1720
isPlaceholder,
1821
isRawPlaceholder,
@@ -1650,3 +1653,108 @@ describe("rejectsWorkflowDispatchNonDefaultBranch", () => {
16501653
);
16511654
});
16521655
});
1656+
1657+
// ---------------------------------------------------------------------------
1658+
// Orthogonal area labels
1659+
// ---------------------------------------------------------------------------
1660+
1661+
describe("mapAreaFieldToLabels", () => {
1662+
it("maps canonical Area dropdown values", () => {
1663+
assert.deepEqual(mapAreaFieldToLabels("CLI"), ["cli"]);
1664+
assert.deepEqual(mapAreaFieldToLabels("Proxy and routing"), ["proxy"]);
1665+
assert.deepEqual(mapAreaFieldToLabels("Dashboard"), ["gui"]);
1666+
assert.deepEqual(mapAreaFieldToLabels("Provider adapter"), ["provider"]);
1667+
assert.deepEqual(mapAreaFieldToLabels("Provider adapters"), ["provider"]);
1668+
assert.deepEqual(mapAreaFieldToLabels("Authentication and account pool"), ["account-pool"]);
1669+
assert.deepEqual(mapAreaFieldToLabels("Catalog / models"), ["catalog"]);
1670+
assert.deepEqual(mapAreaFieldToLabels("Streaming"), ["streaming"]);
1671+
assert.deepEqual(mapAreaFieldToLabels("Tools / MCP / web search"), ["tools"]);
1672+
assert.deepEqual(mapAreaFieldToLabels("Installation or packaging"), ["install"]);
1673+
assert.deepEqual(mapAreaFieldToLabels("Service lifecycle"), ["service"]);
1674+
assert.deepEqual(mapAreaFieldToLabels("Platform (Windows / macOS / Linux)"), ["platform"]);
1675+
assert.deepEqual(mapAreaFieldToLabels("Documentation"), ["documentation"]);
1676+
});
1677+
1678+
it("maps legacy Service lifecycle wording and ignores Other / Multiple areas", () => {
1679+
assert.deepEqual(mapAreaFieldToLabels("Service lifecycle (config injection)"), ["service"]);
1680+
assert.deepEqual(mapAreaFieldToLabels("Other"), []);
1681+
assert.deepEqual(mapAreaFieldToLabels("Multiple areas"), []);
1682+
assert.deepEqual(mapAreaFieldToLabels(""), []);
1683+
assert.deepEqual(mapAreaFieldToLabels(null), []);
1684+
});
1685+
1686+
it("exposes metadata for every non-documentation area label", () => {
1687+
for (const name of Object.keys(AREA_LABELS)) {
1688+
assert.ok(AREA_LABELS[name].color, name);
1689+
assert.ok(AREA_LABELS[name].description, name);
1690+
}
1691+
});
1692+
});
1693+
1694+
describe("detectAreaLabels", () => {
1695+
it("applies Area mapping plus orthogonal heuristics", () => {
1696+
const labels = detectAreaLabels({
1697+
title: "Pool failover stalls on SSE without terminal frame",
1698+
body: [
1699+
"### Area",
1700+
"Authentication and account pool",
1701+
"### Summary",
1702+
"Account pool failover waits forever when the upstream SSE stream ends without a terminal frame.",
1703+
].join("\n"),
1704+
labels: ["bug"],
1705+
});
1706+
assert.ok(labels.includes("account-pool"));
1707+
assert.ok(labels.includes("streaming"));
1708+
});
1709+
1710+
it("adds provider for provider-compatibility form and label", () => {
1711+
const fromLabel = detectAreaLabels({
1712+
title: "AgentRouter Anthropic streams can end without terminal SSE frames",
1713+
body: "### Summary\nStream ends early.",
1714+
labels: ["provider-compatibility"],
1715+
});
1716+
assert.ok(fromLabel.includes("provider"));
1717+
assert.ok(fromLabel.includes("streaming"));
1718+
1719+
const fromHeading = detectAreaLabels({
1720+
title: "Custom relay rejects tool_calls",
1721+
body: [
1722+
"### Provider or upstream service",
1723+
"Volcengine Ark",
1724+
"### Current behaviour",
1725+
"tool_calls with empty content return 400.",
1726+
].join("\n"),
1727+
labels: [],
1728+
});
1729+
assert.ok(fromHeading.includes("provider"));
1730+
assert.ok(fromHeading.includes("tools"));
1731+
});
1732+
1733+
it("runs heuristics for Multiple areas / Other without inventing per-provider labels", () => {
1734+
const labels = detectAreaLabels({
1735+
title: "Dashboard ACL hardening blocks management API on Windows",
1736+
body: [
1737+
"### Area",
1738+
"Multiple areas",
1739+
"### Summary",
1740+
"OpenCodex dashboard shows no data when ACL/icacls hardening cannot be verified.",
1741+
].join("\n"),
1742+
labels: ["bug"],
1743+
});
1744+
assert.ok(labels.includes("gui"));
1745+
assert.ok(labels.includes("platform"));
1746+
assert.equal(labels.includes("kiro"), false);
1747+
assert.equal(labels.includes("gemini"), false);
1748+
assert.equal(labels.includes("windows"), false);
1749+
});
1750+
1751+
it("maps Documentation Area to documentation and does not invent docs", () => {
1752+
const labels = detectAreaLabels({
1753+
title: "Codex Auth UI/docs conflate usage-based switching",
1754+
body: ["### Area", "Documentation", "### Summary", "Docs misdefine new session."].join("\n"),
1755+
labels: [],
1756+
});
1757+
assert.ok(labels.includes("documentation"));
1758+
assert.equal(labels.includes("docs"), false);
1759+
});
1760+
});

0 commit comments

Comments
 (0)