mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-11-07 06:19:43 -10:00
* fix(triage): assign one accountable maintainer when deferring a PR The defer path resolves the maintainer deterministically from the existing owner map (same area/labels/load/rotation logic as issue assignment) and assigns the PR to them, instead of an ambiguous $QWEN_MAINTAINER_HANDLE / most-recent-reviewer chain. Replaces the reviewer-request routing prototype entirely. * feat(triage): assign one area owner when a PR opens * ci: update workflow size baseline * feat(triage): route PRs to the module owner, with the area pool as fallback * test(triage): pin the assign-pr-owner bootstrap guard shape and ordering * test(triage): pin the assign-pr-owner sparse-checkout membership * fix(triage): reject never-matching paths entries in the owner map * test(triage): pin the re-throw of non-permission assign failures * fix(triage): reject empty paths lists in the owner map * test(triage): pin that a label-only area never path-matches * test(triage): pin the assign-pr-owner PR_NUMBER and DRY_RUN wiring * fix(triage): resolve the defer fallback to the newest human reviewer * docs(triage): make the defer resolver env chain and jq filter robust The heredoc read process.env.REPO, but no triage lane exports REPO — the agent step exports REPOSITORY and Actions always provides GITHUB_REPOSITORY, so repo resolved to undefined, the heredoc threw, 2>/dev/null swallowed the evidence, and the owner-map rung silently never fired. Resolve the repo as REPO -> REPOSITORY -> GITHUB_REPOSITORY and the number as PR_NUMBER -> ISSUE_NUMBER, and pass the session's shell variables through on the node invocation since an unexported variable never reaches the child. The latestReviews fallback jq filter also aborted outright when any review had a null author (a deleted account): piping null into endswith() errors during array construction, discarding every reviewer. Select non-null authors before the bot-suffix filter. * test(triage): pin the privileged trigger and job-level env absence The suite never asserted triggers.pull_request_target and its types or the concurrency shape, although the sibling assign-issue-owner.test.mjs pins both: rewriting the trigger to pull_request kept the suite green while the write token becomes read-only on fork PRs and the permission-tolerance catch silently disables routing for every one of them. Pin the trigger types, the concurrency group, and cancel-in-progress. Also pin the absence of job-level env, mirroring the sibling suite: a GH_TOKEN at jobs.assign.env would leak the pull-requests:write token into every step, including checkout, while every existing assertion stayed green. Verified both pins catch their mutants. * fix(triage): re-check PR coverage immediately before assigning The write ran on the snapshot taken at the start of the run, while up to ~30 sequential API calls (permission and open-issue load per candidate) sit between that snapshot and `gh pr edit --add-assignee`. A mapped owner landing on the PR in that window — a human assigning themselves, or a concurrent workflow run on a synchronize burst — got a second assignee stacked on top, the exact failure the coverage check exists to prevent. Re-fetch state, assignees, and latest reviews immediately before the write and re-run both skipPrReason and alreadyCovered, mirroring the pre-write re-fetch assign-issue-owner.mjs already performs. The stub now serves a distinct PR state to the second `pr view` so both race outcomes (covered mid-run, closed mid-run) are pinned; removing the re-fetch makes both new tests fail. Co-authored-by: Qwen-Coder <[email protected]> * docs(triage): reuse the exported open-issue counter in the defer resolver The heredoc asserted "Same load metric as issue assignment" while re-implementing the metric inline (gh issue list ... --limit 100 --json number --jq length). assign-issue-owner.mjs already exports that exact counter as openIssueCount; import it instead so the defer resolver and issue assignment cannot drift when the metric changes. Smoke-ran the edited heredoc from the repo root against a stubbed gh: it still resolves an eligible owner and drives the counter once per candidate. Co-authored-by: Qwen-Coder <[email protected]> * test(triage): pin the sparse checkout of the statically imported sibling The suite pinned sparse-checkout membership for issue-owners.json and assign-pr-owner.mjs, but not for assign-issue-owner.mjs, which the entry script statically imports (loadPolicy, openIssueCount, pickOwner). The bootstrap guard only checks for assign-pr-owner.mjs, so dropping the sibling entry would pass the guard and then fail node with a missing module. Dropping the YAML line makes the new pin fail; restoring it goes green. Co-authored-by: Qwen-Coder <[email protected]> * test(triage): pin the denied and failing push-access paths No test exercised the push-access check beyond the stub answering `write` for every collaborator lookup: neither a denied owner nor a failing lookup was covered, and the terminal "no eligible owner" exit was unreachable by any test. A regression that bypassed canWrite entirely kept the whole suite green. Extend the gh stub with a per-login denial and a global permission mode ('read' answers a non-write permission, 'error' fails the lookup), and pin three outcomes: a denied module owner falls back to the coarser area without ever being assigned, and when no owner passes the check the run ends in the terminal skip with a clean exit for both the denied route and the canWrite catch branch. Making canWrite always-true fails both new tests. Co-authored-by: Qwen-Coder <[email protected]> * fix(triage): decode changed filenames as base64 before area routing Changed filenames on a fork PR are attacker-controlled, and git accepts newlines in path components. changedFiles() re-split the files endpoint's rendered jq text on newlines, so a file named "x<LF>packages/core/poc" arrived as two entries — "x" and a phantom "packages/core/poc" — letting a fork author forge area routing (longest-prefix ranking) and pick which module owner gets assigned. Pull each filename with `.[].filename | @base64` and decode per line, so one filename always stays one entry. The guard suite's stub now answers the files call with base64-encoded names (keeping the legacy text rendering for the old filter), and a new regression test pins the exact forged probe: the newline-carrying name arrives as one entry that matches no area prefix, while a legit core file in the same PR still routes to core. Co-authored-by: Qwen-Coder <[email protected]> * fix(triage): skip PRs whose author account was deleted `gh pr view --json author` exports `"author": null` for deleted accounts; guard both author dereferences so every later trigger skips gracefully (exit 0) instead of throwing and running the assignment check red. Co-authored-by: Qwen-Coder <[email protected]> * test(triage): route a probe file through every mapped area prefix The routing tests only pinned core-skills and core-goals; a typo in any of the six other module prefixes would silently reroute those PRs to the generic fallback with the suite green. Probe every mapped prefix — fixed literals, so a typo'd prefix fails instead of shifting the probe with it. Co-authored-by: Qwen-Coder <[email protected]> * fix(triage): guard the defer heredoc against a deleted PR author The assign-on-defer heredoc dereferenced pr.author.login unguarded; with "author": null the eligibility filter threw and 2>/dev/null silently bypassed deterministic owner resolution. Null-safe exclusion keeps the resolver running, matching the jq fallback below it. Co-authored-by: Qwen-Coder <[email protected]> * fix(triage): keep PR owner routing current * fix(triage): keep the defer resolver heredoc backtick-free The triage lane's permissions.deny blocks any run_shell_command whose text contains a backtick, so the template literal in canWrite() and the backticked "author": null comment made the whole resolver command EXECUTION_DENIED before approval. Use string concatenation, de-backtick the comment, and pin the invariant with a witness test that turns red if a backtick is reintroduced into the resolver block. Co-authored-by: Qwen-Coder <[email protected]> * fix(triage): treat the agent-assignee GraphQL refusal as a graceful skip gh pr edit --add-assignee goes through replaceActorsForAssignable, which GitHub refuses for GitHub App installation tokens when the PR already carries a coding-agent assignee ("Assigning agents is not supported with GitHub App installation tokens"). The graceful-skip filter did not match that refusal, so main() rethrew and the assignment check ran red on the contributor's own PR. Recognize the refusal and skip like the other token-limit cases; the PR already has an accountable actor. Co-authored-by: Qwen-Coder <[email protected]> * fix(triage): tolerate a transient issue-list failure in the load fan-out The load lookup fans out one gh issue list per eligible owner (up to 15 for the core pool) with no guard; one transient failure — secondary rate limit, 5xx, issues disabled — threw straight up and ran the assignment check red, while the sibling canWrite() lookup deliberately warns and continues. Wrap the lookup in the same pattern: retry once, then warn and degrade to a zero load so the rotation still lands an owner — the load metric is a tie-break heuristic, not a gate on assigning. Co-authored-by: Qwen-Coder <[email protected]> * fix(triage): don't count dismissed reviews as owner coverage A dismissed review is a removed review in GitHub's model. Filtering it out of alreadyCovered() keeps a PR whose only mapped-owner involvement is a dismissed review eligible for assignment, instead of skipping the gate and staying ownerless on every later trigger. Co-authored-by: Qwen-Coder <[email protected]> --------- Co-authored-by: Qwen-Coder <[email protected]> Co-authored-by: Qwen-Coder <qwen-coder @alibabacloud.com>
90 lines
2.7 KiB
JSON
90 lines
2.7 KiB
JSON
{
|
|
"$comment": "Label-driven issue assignment map; areas are keyed on the existing issue label taxonomy. Assignment is a pure function of an issue's labels — no model output is involved. Owners need push access but do NOT need a CODEOWNERS entry; every candidate is re-checked against the collaborator API at write time, so adding a login here cannot grant access to someone who lacks it. Areas match in file order, first match wins. An area's optional paths list routes PR assignment (assign-pr-owner.mjs) by changed-file prefix, longest prefix wins; the core area's packages/core/ entry is the fallback for paths no module claims. Issue assignment ignores paths; module areas sit after core so label-based issue matching still resolves core first. See docs/design/2026-08-07-issue-auto-assignment.md.",
|
|
"requireLabels": ["need-discussion"],
|
|
"skipLabels": [
|
|
"welcome-pr",
|
|
"feature/need-help",
|
|
"good first issue",
|
|
"help wanted",
|
|
"autofix/approved",
|
|
"autofix/in-progress"
|
|
],
|
|
"areas": [
|
|
{
|
|
"name": "core",
|
|
"labels": ["category/core", "scope/core"],
|
|
"paths": ["packages/core/"],
|
|
"owners": [
|
|
"wenshao",
|
|
"yiliang114",
|
|
"LaZzyMan",
|
|
"doudouOUC",
|
|
"pomelo-nwu",
|
|
"DennisYu07",
|
|
"jifeng",
|
|
"chiga0",
|
|
"qqqys",
|
|
"ytahdn",
|
|
"BenGuanRan",
|
|
"DragonnZhang",
|
|
"callmeYe",
|
|
"zjunothing",
|
|
"ZijianZhang989"
|
|
]
|
|
},
|
|
{
|
|
"name": "core-skills",
|
|
"labels": ["scope/core"],
|
|
"paths": ["packages/core/src/skills/"],
|
|
"owners": ["wenshao"]
|
|
},
|
|
{
|
|
"name": "core-memory",
|
|
"labels": ["scope/core"],
|
|
"paths": ["packages/core/src/memory/"],
|
|
"owners": ["wenshao"]
|
|
},
|
|
{
|
|
"name": "core-goals",
|
|
"labels": ["scope/core"],
|
|
"paths": ["packages/core/src/goals/"],
|
|
"owners": ["qqqys"]
|
|
},
|
|
{
|
|
"name": "core-telemetry",
|
|
"labels": ["scope/core"],
|
|
"paths": ["packages/core/src/telemetry/"],
|
|
"owners": ["zjunothing"]
|
|
},
|
|
{
|
|
"name": "core-extension",
|
|
"labels": ["scope/core"],
|
|
"paths": ["packages/core/src/extension/"],
|
|
"owners": ["callmeYe"]
|
|
},
|
|
{
|
|
"name": "core-agents",
|
|
"labels": ["scope/core"],
|
|
"paths": ["packages/core/src/agents/"],
|
|
"owners": ["qqqys"]
|
|
},
|
|
{
|
|
"name": "core-config",
|
|
"labels": ["scope/core"],
|
|
"paths": ["packages/core/src/config/"],
|
|
"owners": ["qqqys"]
|
|
},
|
|
{
|
|
"name": "core-runtime",
|
|
"labels": ["scope/core"],
|
|
"paths": [
|
|
"packages/core/src/core/",
|
|
"packages/core/src/services/",
|
|
"packages/core/src/tools/",
|
|
"packages/core/src/utils/"
|
|
],
|
|
"owners": ["yiliang114"]
|
|
}
|
|
]
|
|
}
|