feat(ticket-management)!: 标题改单括号 / —— 【域/闸门】合并为一个括号(废止两括号写法);splitTitle 按 / 拆 parts,断言 4 改为「/ 后闸门 ∈ 词表 且 = 首行闸门=;可开工不得带闸门」;SKILL.md §2/§7 同步

This commit is contained in:
2026-09-22 16:17:44 +08:00
parent ae6c8d8c2a
commit 9e47a23642
2 changed files with 12 additions and 11 deletions
+9 -8
View File
@@ -40,7 +40,8 @@ function mkApi(opt) {
function splitTitle(title) {
const brackets = []; let s = String(title || "").trim();
while (s.startsWith("【")) { const e = s.indexOf("】"); if (e < 0) break; brackets.push(s.slice(1, e)); s = s.slice(e + 1).trim(); }
return { brackets: brackets, rest: s };
const parts = brackets.length ? brackets[0].split("/").map(function (x) { return x.trim(); }) : [];
return { brackets: brackets, parts: parts, rest: s };
}
function firstLine(body) { return String(body || "").split(NL)[0] || ""; }
function fieldOf(line, key) {
@@ -53,10 +54,10 @@ function checksOf(title, body) {
const sp = splitTitle(title), L = firstLine(body);
const dom = fieldOf(L, "域"), gate = fieldOf(L, "闸门"), hp = humanPart(body);
return [
["1 标题主前缀 ∈ 词表", sp.brackets.length > 0 && DOMAINS.indexOf(sp.brackets[0]) >= 0],
["1 标题主前缀 ∈ 词表", sp.parts.length > 0 && DOMAINS.indexOf(sp.parts[0]) >= 0],
["2 首行含 域=", L.indexOf("域=") >= 0],
["3 标题主域 = 首行 域 的 L1", sp.brackets.length > 0 && dom.split("/")[0] === sp.brackets[0]],
["4 第二段 = 闸门=", sp.brackets.length < 2 ? true : (GATES.indexOf(sp.brackets[1]) >= 0 && sp.brackets[1] === gate)],
["3 标题主域 = 首行 域 的 L1", sp.parts.length > 0 && dom.split("/")[0] === sp.parts[0]],
["4 标题闸门 = 首行 闸门=", sp.parts.length < 2 ? (gate === "可开工") : (GATES.indexOf(sp.parts[1]) >= 0 && sp.parts[1] === gate)],
["5 人读部分无本地 / 外部引用", !BAD.some(function (b) { return hp.indexOf(b) >= 0; })],
["6 有引用块 ⇒ 有 ref-owner", body.indexOf("<!--") < 0 ? true : body.indexOf("ref-owner:") >= 0]
];
@@ -103,7 +104,7 @@ async function cmdCreate(api, opt) {
const title = opt.title;
if (!title) { console.log("用法:create --repo=o/r --title=... --body-file=... [--label=<id>] [--dry-run] [--force]"); return 1; }
const sp = splitTitle(title);
if (!sp.brackets.length || DOMAINS.indexOf(sp.brackets[0]) < 0) { console.log("拒绝:标题主前缀不合规 —— " + title); return 1; }
if (!sp.parts.length || DOMAINS.indexOf(sp.parts[0]) < 0) { console.log("拒绝:标题主前缀不合规 —— " + title); return 1; }
const dup = await findTitle(api, opt.repo, title);
if (dup.length) { console.log("拒绝:已存在同名单 #" + dup[0].number + " —— 先查重是硬规则"); return 1; }
const bodyFile = opt["body-file"];
@@ -129,12 +130,12 @@ async function cmdRename(api, opt, n) {
const sp = splitTitle(r.j.title);
const dom = opt.domain;
if (!dom || DOMAINS.indexOf(dom) < 0) { console.log("用法:rename <n> --domain=<词表值> [--gate=等口径|等取证|等部署] [--dry-run]"); return 1; }
let title = "【" + dom + "】";
let inner = dom;
if (opt.gate && opt.gate !== "可开工") {
if (GATES.indexOf(opt.gate) < 0) { console.log("闸门不在词表:" + opt.gate); return 1; }
title += "【" + opt.gate + "】";
inner = dom + "/" + opt.gate;
}
title += sp.rest;
const title = "【" + inner + "】" + sp.rest;
if (opt["dry-run"]) { console.log("[dry-run] " + r.j.title + " → " + title); return 0; }
const p = await api("/repos/" + opt.repo + "/issues/" + n, "PATCH", { title: title });
console.log("status=" + p.s + " → " + (p.j && p.j.title ? p.j.title : p.t.slice(0, 160)));