feat(ticket-management)!: 「任务全貌」四段 → 五段,新增必备段「经验教训」

用户 2026-09-24 指出漏了一个要点:**经验教训**也应是**重点结构**的一部分。

【改动】
- SKILL §8.1:正文骨架加第 ⑤ 段 `## 经验教训`(**必备**,排在 `效果对比` 之后)——
  必须是**可复用结论**(对象 + 动作 / 判据);**尚未定性写「暂无(原因)」**,
  **不许留空壳,也不许写「加强测试」这类套话**。
- §8.2 闸门 B:**四问 → 五问**(新增「它给出『下次怎么避免 / 怎么做』的可复用结论吗?」)。
- §8.3 整洁性自检:加「经验教训须是可复用结论、非套话;未定性须显式写『暂无(原因)』」。
- §3.2 骨架 / §8.0 定全貌 / §8.1 标题 / §5 assess 表 / §7 分工注 / §8.3 有效性 —— 措辞全部同步。
- `bin/ticket.mjs`:`SECTIONS` 加第 5 段(词表 `经验教训` / `教训` / `复盘`);`assess` 表头改「五段」;
  **必备段缺失提示按段区分**(教训 ⇒ 指「暂无(原因)」;提案 ⇒ 指「结论即提案 / 无需处置」);
  `selftest` 20 → **21**。
- `templates/issue.md` 加 `## 经验教训`;README 技能行改「五段任务全貌」。
- (顺带更正)README 里「本机已按此接好 ticket-management」**与实测不符**(本机无该 junction)⇒
  去掉机器特有断言 —— 可复用文档不写本地状态。

【验证 · E1 · 本机】
- `node --check` 通过;`selftest` **21/21**;`grep 四段|四问` ⇒ **0 残留**。
- `assess 158`(真跑)⇒ 五段中**恰好第 ⑤ 段未识别**(正是用户指出的缺口,`#158` 待补经验教训);
  顺序 OK · 空壳 无。
- 合成「五段齐」正文 ⇒ 五段全 ✓ · 顺序 OK · 空壳 无。
This commit is contained in:
2026-09-24 10:50:33 +08:00
parent feeafbd70d
commit a70eaae125
4 changed files with 43 additions and 27 deletions
+16 -9
View File
@@ -3,7 +3,7 @@
// 子命令:labels | milestones | list | verify <n...> | assess <n|--body-file=f> | create
// update <n> | comments <n> | comment-add <n> | comment-del <id> | rename <n>
// 令牌:GITEA_TOKEN 环境变量,或 --token-file=<含 key=value 的文件>(不落盘、不回显)
// 纪律真源:同目录 ../SKILL.md(§7 verify 七断言 · §8 结构纪律 / assess 四段体检)
// 纪律真源:同目录 ../SKILL.md(§7 verify 七断言 · §8 结构纪律 / assess 五段体检)
import fs from "node:fs";
const DOMAINS = ["订单", "库存", "结算", "商品", "报表", "平台", "其他"];
@@ -12,14 +12,15 @@ const NL = String.fromCharCode(10);
const BACKSLASH = String.fromCharCode(92);
const BAD = ["codes/", "分析/数据/", "分析/D-", "分析/E-", "分析/A-", "分析/B-", "分析/C-", "分析/F-", "分析/_tmp", "分析/_archive", "分析/README", "86 §", "80 · V", "/home/", "C:" + BACKSLASH];
// §8.1「任务全貌」四段:正文骨架词表(数组顺序 = 规范顺序)
// §8.1「任务全貌」五段:正文骨架词表(数组顺序 = 规范顺序)
const SECTIONS = [
{ key: "问题背景", words: ["现象", "根因", "影响面", "问题背景", "背景", "生产实测"], need: "必备" },
{ key: "处置提案", words: ["处置提案", "方案", "机制", "提案", "路线", "结论"], need: "必备" },
{ key: "处置结果", words: ["处置结果", "执行回执", "当前状态", "回执", "结果"], need: "按事实" },
{ key: "效果对比", words: ["效果对比", "收益验证", "收益", "对照", "效果", "基线"], need: "按事实" }
{ key: "效果对比", words: ["效果对比", "收益验证", "收益", "对照", "效果", "基线"], need: "按事实" },
{ key: "经验教训", words: ["经验教训", "教训", "复盘"], need: "必备" }
];
// 四段之外「已识别的小节」:不计入四段,但也不该被报成「未归类」
// 五段之外「已识别的小节」:不计入五段,但也不该被报成「未归类」
const OPTIONAL_SECTIONS = ["验收判据", "反证条件", "遗留", "取证记录"];
// 取值型选项:`--repo=o/r` 与 `--repo o/r` 两种写法都收(等价;布尔开关不吃后一个参数)
@@ -131,17 +132,21 @@ function structureOf(body) {
}
function printStructure(body) {
const st = structureOf(body);
console.log("四段(§8.1 · 顺序 " + SECTIONS.map(function (s) { return s.key; }).join(" → ") + "):");
console.log("五段(§8.1 · 顺序 " + SECTIONS.map(function (s) { return s.key; }).join(" → ") + "):");
for (let i = 0; i < SECTIONS.length; i++) {
const own = st.hits.filter(function (x) { return x.idx === i; }).map(function (x) { return "## " + x.text; });
const tail = own.length ? " " + own.join(" · ")
: (SECTIONS[i].need === "必备" ? " ← 未识别到(必备段:确认是否用了同义小标题;若「结论即提案」或结论为「无需处置」,请显式写一段声明)" : " ← 未识别到(未发生 / 未测就不写,不要空壳)");
: (SECTIONS[i].need === "必备"
? " ← 未识别到(必备段:" + (SECTIONS[i].key === "经验教训"
? "若尚未定性,写「暂无(原因)」;不许留空壳,也不许写「加强测试」这类套话"
: "确认是否用了同义小标题;若「结论即提案」或结论为「无需处置」,请显式写一段声明") + ")"
: " ← 未识别到(未发生 / 未测就不写,不要空壳)");
console.log(" " + (own.length ? "[✓]" : "[ ]") + " " + SECTIONS[i].key + "(" + SECTIONS[i].need + ")" + tail);
}
console.log(" 顺序:" + (st.ordered ? "OK" : "⚠ 主叙事未按 背景→提案→结果→效果 排列 ⇒ 调整(确为「更正 / 补充」后置小节时可保留,由人判断)"));
if (st.unclassified.length) console.log(" 未归类 ## 小节(人工判断归属):" + st.unclassified.map(function (t) { return "## " + t; }).join(" · "));
console.log(" 空壳段:" + (st.empty.length ? "✗ " + st.empty.join(" · ") + "(删掉或补齐)" : "无"));
console.log(" 其他已识别小节(不计入四段):" + (st.optional.length ? st.optional.join(" · ") : "无"));
console.log(" 其他已识别小节(不计入五段):" + (st.optional.length ? st.optional.join(" · ") : "无"));
}
// 「必须可执行物」的启发式判据(§8.1 第 2 条):含代码围栏或 SQL 关键字 —— 只作提示,仍需人核
const FENCE = String.fromCharCode(96, 96, 96);
@@ -173,7 +178,7 @@ function cmdSelftest() {
t("标题带闸门但首行 闸门=可开工 ⇒ 断言 4 失败", b2[3][1] === false);
const b3 = checksOf("【报表】x", "域=报表-统计-模式分列" + NL + NL + "<!-- refs:");
t("有引用块无 ref-owner ⇒ 断言 6 失败", b3[5][1] === false);
// —— §8 结构评估:四段齐 / 缺必备段 / 空壳 / 乱序 ——
// —— §8 结构评估:五段齐 / 缺必备段 / 空壳 / 乱序 ——
const sOk = structureOf("域=平台-性能-索引" + NL + NL + "## 现象" + NL + "坏。" + NL + NL + "## 方案" + NL + "修。");
t("背景 + 提案齐 ⇒ 顺序 OK", sOk.hits.length === 2 && sOk.ordered === true && sOk.empty.length === 0);
const sMiss = structureOf("域=平台-性能-索引" + NL + NL + "## 现象" + NL + "坏。");
@@ -194,6 +199,8 @@ function cmdSelftest() {
t("可执行物启发式:代码围栏", isExecutable(FENCE + "bash" + NL + "echo hi" + NL + FENCE) === true);
t("可执行物启发式:SQL 关键字", isExecutable("跑这条 SELECT 即可") === true);
t("可执行物启发式:过程叙述判非可执行", isExecutable("今天讨论了一下,暂时没有结论") === false);
const sLesson = structureOf("域=平台-性能-索引" + NL + NL + "## 现象" + NL + "x" + NL + NL + "## 经验教训" + NL + "y");
t("「经验教训」可被识别为第 5 段", sLesson.hits.some(function (x) { return x.idx === 4; }) === true);
console.log("自检 " + pass + "/" + (pass + fail));
return fail === 0 ? 0 : 1;
}
@@ -398,7 +405,7 @@ const USAGE = [
" labels | milestones 读标签 / 里程碑",
" list [--state=open|closed|all] 列工单",
" verify <n...> 七项断言(有效性:标题/首行/闸门/自包含/引用块/标签)",
" assess <n> | assess --body-file=<文件> 结构评估(§8.0 落点表底稿:四段现状/缺段/空壳/评论清单)",
" assess <n> | assess --body-file=<文件> 结构评估(§8.0 落点表底稿:五段现状/缺段/空壳/评论清单)",
" create --title=… --body-file=… --label=<id>[,<id>] [--dry-run] [--force]",
" update <n> --body-file=… [--title=…] [--dry-run] [--force]",
" comments <n> [--dump=<文件>] 评论清单 / 删前全量备份",