From 366a7a1606c91ff1ef405efb8cee8d9549b56be2 Mon Sep 17 00:00:00 2001 From: sunlei Date: Fri, 3 Jul 2026 13:53:20 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E5=A2=9E=E5=BC=BATASKS=E7=B4=A2?= =?UTF-8?q?=E5=BC=95=E6=B2=BB=E7=90=86=E9=97=A8=E7=A6=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/selfTest.ts | 45 +++++++++++++++ src/tools/review.ts | 133 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 177 insertions(+), 1 deletion(-) diff --git a/src/selfTest.ts b/src/selfTest.ts index 30375de..43f6b71 100644 --- a/src/selfTest.ts +++ b/src/selfTest.ts @@ -1149,6 +1149,51 @@ export async function runSelfTest(): Promise { ) { throw new Error("TASKS recent record limit self-check failed"); } + const taskHardLimitFindings = findTaskRecordGovernanceFindings([ + "# TASKS.md", + "", + "## 当前上下文", + "", + "## 维护规则", + "", + "## 最近记录", + "### 2026-07-03:记录一", + "- 范围:root", + `- 关键词:${"TASKS 治理".repeat(60)}`, + " - 子弹列表塞长细节", + "补充验证:不允许用补充段绕过三字段结构。", + "- 验证:global-review", + "## 历史索引", + "", + "### Root Governance / Workflow", + `- 2026-07-03:${"历史索引也不能塞长流水。".repeat(20)}`, + ...Array.from({ length: 700 }, () => "填充总体大小上限"), + ]); + for (const category of [ + "tasks-file-size-limit", + "tasks-record-field-too-long", + "tasks-record-freeform-line", + "tasks-history-item-too-long", + ]) { + if (!taskHardLimitFindings.some((item) => item.category === category)) { + throw new Error(`TASKS hard limit self-check failed: ${category}`); + } + } + const taskSectionFindings = findTaskRecordGovernanceFindings([ + "# TASKS.md", + "", + "## 当前上下文", + "## 最近记录", + "## 维护规则", + "## 历史索引", + ]); + if ( + !taskSectionFindings.some( + (item) => item.category === "tasks-section-order", + ) + ) { + throw new Error("TASKS section order self-check failed"); + } const napcatImageFindings = findNapcatImageGovernanceFindings( ["QQBOT_NAPCAT_IMAGE=mlikiowa/napcat-docker:latest"], diff --git a/src/tools/review.ts b/src/tools/review.ts index c3ce3b6..67759b7 100644 --- a/src/tools/review.ts +++ b/src/tools/review.ts @@ -68,6 +68,15 @@ const napcatImageAssignmentPattern = /^\s*QQBOT_NAPCAT_IMAGE\s*=\s*([^#\s]+)\s*(?:#.*)?$/; const taskRecordAllowedFields = new Set(["范围", "关键词", "验证"]); const taskRecordRequiredFields = ["范围", "关键词", "验证"] as const; +const taskRequiredSecondLevelSections = [ + "当前上下文", + "维护规则", + "最近记录", + "历史索引", +] as const; +const taskFileMaxBytes = 14 * 1024; +const taskHistoryItemMaxChars = 180; +const taskRecordFieldMaxChars = 360; const taskRecentRecordLimit = 3; interface ReviewFinding { @@ -102,7 +111,13 @@ export function findTaskRecordGovernanceFindings( lines: string[], ): ReviewFinding[] { const findings: ReviewFinding[] = []; + findings.push(...findTaskFileShapeFindings(lines)); + const recentStart = lines.findIndex((line) => line.trim() === "## 最近记录"); + const historyStart = lines.findIndex((line) => line.trim() === "## 历史索引"); + if (historyStart >= 0) { + findings.push(...findTaskHistoryIndexFindings(lines, historyStart)); + } if (recentStart < 0) return findings; const nextSectionOffset = lines @@ -164,13 +179,38 @@ export function findTaskRecordGovernanceFindings( } if (!currentRecord) continue; + if (!line) continue; const fieldMatch = line.match(/^-\s*([^::]+)[::]/); - if (!fieldMatch) continue; + if (!fieldMatch) { + findings.push({ + category: "tasks-record-freeform-line", + file: "TASKS.md", + line: index + 1, + level: "P2", + message: `TASKS 最近记录「${currentRecord.heading}」包含非三字段正文。`, + project: "root", + suggestion: + "最近记录只允许三条 bullet:范围、关键词、验证;补充段、子列表、表格、代码块全部迁入 docs 或历史索引。", + }); + continue; + } const field = fieldMatch[1].trim(); if (taskRecordAllowedFields.has(field)) { currentRecord.fields.add(field); + if (line.length > taskRecordFieldMaxChars) { + findings.push({ + category: "tasks-record-field-too-long", + file: "TASKS.md", + line: index + 1, + level: "P2", + message: `TASKS 最近记录字段「${field}」过长,当前 ${line.length} 字,限制 ${taskRecordFieldMaxChars} 字。`, + project: "root", + suggestion: + "把长证据、长复现和长命令移入 docs 或历史索引短关键词,最近记录只保留摘要。", + }); + } continue; } @@ -190,6 +230,97 @@ export function findTaskRecordGovernanceFindings( return findings; } +/** + * Checks the whole task index shape before section-specific recent-record checks. + * @param lines - Current `TASKS.md` content split by line. + * @returns Review findings for total size or required second-level section drift. + */ +function findTaskFileShapeFindings(lines: string[]): ReviewFinding[] { + const findings: ReviewFinding[] = []; + const byteLength = Buffer.byteLength(lines.join("\n"), "utf8"); + if (byteLength > taskFileMaxBytes) { + findings.push({ + category: "tasks-file-size-limit", + file: "TASKS.md", + level: "P2", + message: `TASKS.md 只能作为压缩索引,当前 ${byteLength} bytes,限制 ${taskFileMaxBytes} bytes。`, + project: "root", + suggestion: + "删除流水账、长验证、截图清单和完整命令;必要细节放入 docs,TASKS 只保留索引。", + }); + } + + const sectionHeadings = lines + .map((line, index) => ({ line, lineNumber: index + 1 })) + .filter((item) => /^##\s+/.test(item.line.trim())); + const actualSections = sectionHeadings.map((item) => + item.line.trim().replace(/^##\s+/, ""), + ); + const expectedSections = [...taskRequiredSecondLevelSections]; + const hasExactSectionOrder = + actualSections.length === expectedSections.length && + expectedSections.every((section, index) => actualSections[index] === section); + if (!hasExactSectionOrder) { + findings.push({ + category: "tasks-section-order", + file: "TASKS.md", + line: sectionHeadings[0]?.lineNumber ?? 1, + level: "P2", + message: `TASKS.md 二级章节必须固定为 ${expectedSections.join(" -> ")}。`, + project: "root", + suggestion: + "不要新增临时章节、补充章节或任务分组;把额外内容压缩进最近记录三字段或历史索引分类 bullet。", + }); + } + + return findings; +} + +/** + * Checks the compact history index so old work cannot become another long-form log sink. + * @param lines - Current `TASKS.md` content split by line. + * @param historyStart - Zero-based line index of `## 历史索引`. + * @returns Review findings for long or free-form history index content. + */ +function findTaskHistoryIndexFindings( + lines: string[], + historyStart: number, +): ReviewFinding[] { + const findings: ReviewFinding[] = []; + for (let index = historyStart + 1; index < lines.length; index += 1) { + const line = lines[index].trim(); + if (!line) continue; + if (line.startsWith("### ")) continue; + + if (!line.startsWith("- ")) { + findings.push({ + category: "tasks-history-freeform-line", + file: "TASKS.md", + line: index + 1, + level: "P2", + message: "TASKS 历史索引只能使用分类标题和单行关键词 bullet。", + project: "root", + suggestion: + "删除段落、代码块、表格和补充说明;历史索引只保留可检索关键词。", + }); + continue; + } + + if (line.length <= taskHistoryItemMaxChars) continue; + findings.push({ + category: "tasks-history-item-too-long", + file: "TASKS.md", + line: index + 1, + level: "P2", + message: `TASKS 历史索引条目过长,当前 ${line.length} 字,限制 ${taskHistoryItemMaxChars} 字。`, + project: "root", + suggestion: + "把旧任务压缩成日期、主题和关键词;长背景移入 docs 或删除。", + }); + } + return findings; +} + export function findNapcatImageGovernanceFindings( lines: string[], context: {