Skip to content

Commit 9d1ef56

Browse files
fix(scripts): refuse PR-only labels on a new issue in issue-create (#31460)
<!-- claude-projects-attribution: {"github_login":"zhuangjianguo"} --> _Requested by **Steedos** · [project thread](https://claude.ai/code/project/chan_018ETKaNwMZCNRYD7D8Q9Ubb?thread=cmsg_018ETKaNwMZCNRYD7D8Q9UbbNKXM3PyX5W136u8jKBiA5Q)_ Fixes #31359 Clause-②: no Metadata-protocol: none ## 维护者速读 - 改了什么:建卡脚本 `issue-create.mjs` 在发出任何写入之前,先用 `label-write.mjs` 导出的同一条种类规则检查 `--label`;给新 issue 挂只属于 PR 的 `pm:awaiting-merge`(任意大小写)会被拒绝,干跑同样拒绝。 - 为什么改:种类规则此前只守在中继的命令行入口,建卡脚本直接调用 `sendFleetWrite` 绕了过去,PR 专用标签仍能落到新卡上。 - 风险与回滚:只动一个仓内工具脚本,不发布任何包;回滚即还原该文件这一处调用与一条自测。 - 先看:`scripts/pm/issue-create.mjs` 的 `planFrom` 里 `refuseKindLabels` 那三行,其次是干跑电池新增的那条自测。 - 两分钟验证:`node scripts/pm/issue-create.mjs --self-test`,预期 65 条全过;再带 `--dry-run --label PM:AWAITING-MERGE` 跑一次,预期退出码 2 并引出规则原文。 - 需要你做的:无,席位落地。 ## What changes - `scripts/pm/issue-create.mjs` imports `refuseKindLabels`, `KIND_ISSUE` and `KIND_RULE_AWAITING_MERGE_PR_ONLY` from `scripts/pm/label-write.mjs` (no rule retyped; no import cycle, the file already imported `classifyHttp` from there). - `planFrom` asks `refuseKindLabels({ kind: KIND_ISSUE, add: labels, ref: 'the new card' })` right after the existing `rewriteRetiredLaneLabels` call. A refusal joins the plan's errors, so `main` prints the rule and exits 2 (usage) before any transport is chosen: dry run, direct POST and relay alike. - Header gains a short "The kind rule" section; the dry-run battery pin moves 3 to 4. - `close-cards.mjs` untouched, as the card says. ## Tests - New self-test case (dry-run battery): `--dry-run --label PM:AWAITING-MERGE` exits 2, stderr carries the quoted rule, nothing is printed as sent, nothing is paced. Self-test 65/65 at the final head (was 64/64 on main). - Ablation through `scripts/ablation-replace.mjs --delete` of the `if (wrongKind) errors.push(wrongKind);` line: anchor 1 to 0 on disk, self-test exit 1 with exactly that case red (got exit 0, rule not quoted, plan printed as sent); restored, blob hash equal to HEAD, `git diff HEAD` empty. - Manual: the uppercase label prints the rule plus the case-insensitive note and exits 2; control `--label pm:queue` dry run exits 0. ## Acceptance notes - None. ## Verification - `dispatch-gates --tier`: no path-derived mandate, 18 changed lines. - `dispatch-gates --commands`: 32 derived families, all exit 0 at 7372117; `--ran` reconciles 32 run, 0 NOT-MEASURED. The merge of main (deac7f4) brought no file under `scripts/`; the same 32 were rerun at the merge head 17ed0b1, all exit 0, `--ran` 32 run, 0 NOT-MEASURED. --- _Generated by [Claude Code](https://claude.ai/code/session_01QsR2jT5126E7RcpXRu5M3L)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 669a8aa commit 9d1ef56

1 file changed

Lines changed: 16 additions & 2 deletions

File tree

‎scripts/pm/issue-create.mjs‎

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,12 @@
7272
* (`fleet-write/dispatch.mjs`) over the one alias map. ⛔ A rewrite, never a
7373
* refusal: no card is held back for its label.
7474
*
75+
* ## The kind rule
76+
*
77+
* A `--label` the kind rule forbids on an issue (`pm:awaiting-merge`, any case)
78+
* is refused as usage (exit 2), on a dry run too, with the rule quoted —
79+
* `refuseKindLabels` (`label-write.mjs`), the one rule every label door asks.
80+
*
7581
* ## Read-back
7682
*
7783
* After a 201 the card is fetched again and its title compared. A mismatch is
@@ -104,7 +110,7 @@ import { isEntrypoint } from '../invoked-as.mjs';
104110
import { EXIT_PREREQUISITE_NOT_MET, PROXY_FLAG, SEAT_SIGNATURE_SHAPE, proxyRearmPlan, resolveSweepRepo, signatureRefusal } from './check-half-states.mjs';
105111
import { EXIT_UNCONFIRMED, exitForResult, matchRunAnnotations, packRequest, parseRelayAnnotation, relayAnnotationMessage, resolveRoute, retiredLaneRewriteLine, rewriteRetiredLaneLabels, sendFleetWrite, unconfirmedText } from './fleet-write/dispatch.mjs';
106112
import { refusalText as relayRefusalText } from './fleet-write/validate.mjs';
107-
import { classifyHttp } from './label-write.mjs';
113+
import { KIND_ISSUE, KIND_RULE_AWAITING_MERGE_PR_ONLY, classifyHttp, refuseKindLabels } from './label-write.mjs';
108114
import { EXIT_WRITE_PACE_REFUSED, isWriteMethod, noteResponse, paceWrite, releaseWriteLease } from './write-pace.mjs';
109115

110116
const SELF_PATH = fileURLToPath(import.meta.url);
@@ -225,6 +231,9 @@ export function planFrom(opts, { read = (p) => readFileSync(p, 'utf8'), env = pr
225231
// A retired lane alias is written as its lane (header): a rewrite, never a refusal.
226232
const lanes = rewriteRetiredLaneLabels(dedupe(opts.labels));
227233
const labels = lanes.labels;
234+
// The kind rule (label-write.mjs), asked of a new card's labels before any write, dry run included.
235+
const wrongKind = refuseKindLabels({ kind: KIND_ISSUE, add: labels, ref: 'the new card' });
236+
if (wrongKind) errors.push(wrongKind);
228237
const assignees = dedupe(opts.assignees);
229238
const payload = { title: title ?? '', body: body ?? '' };
230239
if (labels.length) payload.labels = labels;
@@ -446,7 +455,7 @@ const SELF_TEST_BATTERIES = Object.freeze({
446455
'the payload: title trimmed to one line, labels and assignees de-duplicated, nothing invented': 5,
447456
"the signature: a card body opens with the seat's signature line, or the plan is refused before any request": 8,
448457
'the transport: a fake platform, and what each answer does to the exit code': 9,
449-
'dry-run: no request leaves, and the plan is printed': 3,
458+
'dry-run: no request leaves, and the plan is printed': 4,
450459
'the wiring: both halves around the one POST, on the write verb only': 4,
451460
'the relay transport: ONE dispatch carrying the create, the card found by title since the dispatch, and an accepted dispatch with no run UNCONFIRMED under auto too — never the direct POST': 9,
452461
'the retired lane aliases: a domain:engine / services / cli / rest --label is sent as domain:server on either transport, the dry run and the read-back say so, a live domain:* label passes untouched, and nothing is refused': 8,
@@ -653,6 +662,11 @@ export async function selfTest() {
653662
t('--dry-run exits 0 and prints the request it did not send', [spawned.status, spawned.stdout.includes('NOTHING was sent') && spawned.stdout.includes('POST /repos/o/r/issues')], [0, true], spawned.stderr.slice(-300));
654663
t('…and the throttle recorded nothing: a dry run makes no write', existsSync(join(dir, 'dry-pace.jsonl')), false);
655664
t('a refused plan is usage, printed, and nothing is sent', spawnSync(process.execPath, [SELF_PATH, '--dry-run', '--repo', 'o/r', '--title', 'x'], { encoding: 'utf8', env: { ...process.env, HTTPS_PROXY: '', https_proxy: '' } }).status, EXIT_USAGE);
665+
const kindCli = spawnSync(process.execPath, [SELF_PATH, '--dry-run', '--repo', 'o/r', '--title', 'Dry', '--body-file', join(dir, 'b.md'), '--label', 'PM:AWAITING-MERGE'], {
666+
encoding: 'utf8',
667+
env: { ...process.env, OS_PM_WRITE_PACE_FILE: join(dir, 'kind-pace.jsonl'), GITHUB_TOKEN: TOKEN_FIXTURE, GH_TOKEN: '', HTTPS_PROXY: '', https_proxy: '' },
668+
});
669+
t('⛔ a PR-only label (PM:AWAITING-MERGE, any case) is refused on a dry run too — usage, the rule quoted, nothing printed as sent, nothing paced', [kindCli.status, kindCli.stderr.includes(KIND_RULE_AWAITING_MERGE_PR_ONLY), kindCli.stdout.includes('NOTHING was sent'), existsSync(join(dir, 'kind-pace.jsonl'))], [EXIT_USAGE, true, false, false], kindCli.stderr.slice(-300));
656670
}
657671

658672
// ── the relay transport ─────────────────────────────────────────────────

0 commit comments

Comments
 (0)