chore: 增强TASKS索引治理门禁
This commit is contained in:
parent
71d9ac7e10
commit
366a7a1606
@ -1149,6 +1149,51 @@ export async function runSelfTest(): Promise<void> {
|
||||
) {
|
||||
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"],
|
||||
|
||||
@ -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: {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user