mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-31 02:06:21 +00: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 <qwen-coder@alibabacloud.com>
* 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 <qwen-coder@alibabacloud.com>
* 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 <qwen-coder@alibabacloud.com>
* 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 <qwen-coder@alibabacloud.com>
* 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 <qwen-coder@alibabacloud.com>
* 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 <qwen-coder@alibabacloud.com>
* 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 <qwen-coder@alibabacloud.com>
* 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 <qwen-coder@alibabacloud.com>
* 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 <qwen-coder@alibabacloud.com>
* 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 <qwen-coder@alibabacloud.com>
* 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 <qwen-coder@alibabacloud.com>
* 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 <qwen-coder@alibabacloud.com>
---------
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder @alibabacloud.com>
89 lines
2.7 KiB
JSON
89 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"]
|
|
}
|
|
]
|
|
}
|