feat(ticket-management)!: 标题改单括号 / —— 【域/闸门】合并为一个括号(废止两括号写法);splitTitle 按 / 拆 parts,断言 4 改为「/ 后闸门 ∈ 词表 且 = 首行闸门=;可开工不得带闸门」;SKILL.md §2/§7 同步
This commit is contained in:
@@ -21,9 +21,9 @@ description: 用 Gitea 工单库做「人读 + 机读」双面管理 —— 标
|
|||||||
|
|
||||||
## 2 标题规范
|
## 2 标题规范
|
||||||
|
|
||||||
- **主前缀(必填,7 值)**:`【订单】` `【库存】` `【结算】` `【商品】` `【报表】` `【平台】` `【其他】`
|
- **前缀(必填,单括号、`/` 分隔)**:`【订单】` `【库存】` `【结算】` `【商品】` `【报表】` `【平台】` `【其他】`;带闸门时**合并成一个括号**:`【域/闸门】`(例 `【报表/等口径】`)—— **不要写两个括号**(`【报表】【等口径】` 已废止)。
|
||||||
- 词表**可按项目替换**,但**改动须登记**;新域先用 `【其他】`,**同域攒够 3 条再立**新前缀。
|
- 词表**可按项目替换**,但**改动须登记**;新域先用 `【其他】`,**同域攒够 3 条再立**新前缀。
|
||||||
- **第二段(可选)**:`【等口径】` `【等取证】` `【等部署】` —— **仅当「不能开工」**。
|
- **闸门(可选)**:`/等口径` `/等取证` `/等部署` —— **仅当「不能开工」**;可开工 ⇒ 括号里只有域。
|
||||||
- **准入三问**(新增任何段都要过):① 列表页会按它筛 / 排吗?② 能否从正文推出(能推就别重复占位)?③ 生命周期里会变几次(反复变 = 每次都要改标题)?
|
- **准入三问**(新增任何段都要过):① 列表页会按它筛 / 排吗?② 能否从正文推出(能推就别重复占位)?③ 生命周期里会变几次(反复变 = 每次都要改标题)?
|
||||||
- 按此三问:**问题域、闸门**通过;**优先级**(churn 大)· **端** · **环境** · **版本** · **客户 / 店铺**(敏感)· **批次**(用 milestone)全部不通过。
|
- 按此三问:**问题域、闸门**通过;**优先级**(churn 大)· **端** · **环境** · **版本** · **客户 / 店铺**(敏感)· **批次**(用 milestone)全部不通过。
|
||||||
|
|
||||||
@@ -99,6 +99,6 @@ node bin/ticket.mjs rename 142 --repo xbc/xbc --domain=平台 [--gate=等口径]
|
|||||||
1. 标题以 `【…】` 开头,且内文 ∈ 问题域词表;
|
1. 标题以 `【…】` 开头,且内文 ∈ 问题域词表;
|
||||||
2. 正文第一行存在且含 `域=`;
|
2. 正文第一行存在且含 `域=`;
|
||||||
3. **标题主域 = 首行 `域=` 的 L1**;
|
3. **标题主域 = 首行 `域=` 的 L1**;
|
||||||
4. 标题若有第二段 ⇒ ∈ 闸门词表 **且** 等于首行 `闸门=`;
|
4. 标题 `/` 后的闸门 ∈ 词表 **且** 等于首行 `闸门=`;**可开工时标题不得带 `/闸门`**(多写即 FAIL);
|
||||||
5. 人读部分(第一个 `<!--` 之前)**不含**工作区路径与内部分析编号 —— 判据用**具体前缀**(`codes/` · `分析/数据/` · `分析/D-`…`分析/F-` · `分析/_tmp` · `分析/_archive` · `86 §` · `80 · V` · 盘符路径);⚠ **不要用裸 `分析/`**(会误伤中文词组,如「进货分析/商品维度」);
|
5. 人读部分(第一个 `<!--` 之前)**不含**工作区路径与内部分析编号 —— 判据用**具体前缀**(`codes/` · `分析/数据/` · `分析/D-`…`分析/F-` · `分析/_tmp` · `分析/_archive` · `86 §` · `80 · V` · 盘符路径);⚠ **不要用裸 `分析/`**(会误伤中文词组,如「进货分析/商品维度」);
|
||||||
6. 正文若含 `<!--` ⇒ 该注释块必须含 `ref-owner:`。
|
6. 正文若含 `<!--` ⇒ 该注释块必须含 `ref-owner:`。
|
||||||
@@ -40,7 +40,8 @@ function mkApi(opt) {
|
|||||||
function splitTitle(title) {
|
function splitTitle(title) {
|
||||||
const brackets = []; let s = String(title || "").trim();
|
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(); }
|
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 firstLine(body) { return String(body || "").split(NL)[0] || ""; }
|
||||||
function fieldOf(line, key) {
|
function fieldOf(line, key) {
|
||||||
@@ -53,10 +54,10 @@ function checksOf(title, body) {
|
|||||||
const sp = splitTitle(title), L = firstLine(body);
|
const sp = splitTitle(title), L = firstLine(body);
|
||||||
const dom = fieldOf(L, "域"), gate = fieldOf(L, "闸门"), hp = humanPart(body);
|
const dom = fieldOf(L, "域"), gate = fieldOf(L, "闸门"), hp = humanPart(body);
|
||||||
return [
|
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],
|
["2 首行含 域=", L.indexOf("域=") >= 0],
|
||||||
["3 标题主域 = 首行 域 的 L1", sp.brackets.length > 0 && dom.split("/")[0] === sp.brackets[0]],
|
["3 标题主域 = 首行 域 的 L1", sp.parts.length > 0 && dom.split("/")[0] === sp.parts[0]],
|
||||||
["4 第二段 = 闸门=", sp.brackets.length < 2 ? true : (GATES.indexOf(sp.brackets[1]) >= 0 && sp.brackets[1] === gate)],
|
["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; })],
|
["5 人读部分无本地 / 外部引用", !BAD.some(function (b) { return hp.indexOf(b) >= 0; })],
|
||||||
["6 有引用块 ⇒ 有 ref-owner", body.indexOf("<!--") < 0 ? true : body.indexOf("ref-owner:") >= 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;
|
const title = opt.title;
|
||||||
if (!title) { console.log("用法:create --repo=o/r --title=... --body-file=... [--label=<id>] [--dry-run] [--force]"); return 1; }
|
if (!title) { console.log("用法:create --repo=o/r --title=... --body-file=... [--label=<id>] [--dry-run] [--force]"); return 1; }
|
||||||
const sp = splitTitle(title);
|
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);
|
const dup = await findTitle(api, opt.repo, title);
|
||||||
if (dup.length) { console.log("拒绝:已存在同名单 #" + dup[0].number + " —— 先查重是硬规则"); return 1; }
|
if (dup.length) { console.log("拒绝:已存在同名单 #" + dup[0].number + " —— 先查重是硬规则"); return 1; }
|
||||||
const bodyFile = opt["body-file"];
|
const bodyFile = opt["body-file"];
|
||||||
@@ -129,12 +130,12 @@ async function cmdRename(api, opt, n) {
|
|||||||
const sp = splitTitle(r.j.title);
|
const sp = splitTitle(r.j.title);
|
||||||
const dom = opt.domain;
|
const dom = opt.domain;
|
||||||
if (!dom || DOMAINS.indexOf(dom) < 0) { console.log("用法:rename <n> --domain=<词表值> [--gate=等口径|等取证|等部署] [--dry-run]"); return 1; }
|
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 (opt.gate && opt.gate !== "可开工") {
|
||||||
if (GATES.indexOf(opt.gate) < 0) { console.log("闸门不在词表:" + opt.gate); return 1; }
|
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; }
|
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 });
|
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)));
|
console.log("status=" + p.s + " → " + (p.j && p.j.title ? p.j.title : p.t.slice(0, 160)));
|
||||||
|
|||||||
Reference in New Issue
Block a user