fix: 固化TASKS记录治理检查

This commit is contained in:
sunlei 2026-06-10 11:04:26 +08:00
parent 5151a5eae0
commit 64da1ebc03
3 changed files with 101 additions and 4 deletions

View File

@ -23,7 +23,7 @@
- 生成验证进程清理计划:按项目路径和端口给出 PowerShell 检查命令,不直接杀进程。
- 清理历史产物:统一治理 `.kt-workspace` 下的测试/验证产物,按目录最近修改时间只保留最近 3 轮模板目录永久保留CLI 默认 dry-run真实清理必须显式传 `--execute`
- 检查 env 策略和变更风险:区分后端真实 env 与前端客户端 `.env*`,提醒锁文件、核心表格组件、部署链路和 Vue TSX 插槽写法等高风险改动。
- 全局 CodeReview 只读扫描:汇总全部 KT 子仓库的 Git 状态、敏感文件跟踪、冲突标记、运行时调试输出、疑似凭据字面量、根目录生成产物和当前变更风险;默认只对变更文件做内容扫描,并放过短中文显示标签,避免历史误报污染上下文;任何文件改动后都要跑一遍。
- 全局 CodeReview 只读扫描:汇总全部 KT 子仓库的 Git 状态、敏感文件跟踪、冲突标记、运行时调试输出、疑似凭据字面量、根目录生成产物、`TASKS.md` 最近记录字段结构和当前变更风险;默认只对变更文件做内容扫描,并放过短中文显示标签,避免历史误报污染上下文;任何文件改动后都要跑一遍。
- 生成或写入 `TASKS.md` 最近记录:默认 `dryRun=true`,确认后再落盘。
- 生成提交前检查清单:校验 KT commit message 约定。
@ -106,7 +106,7 @@ pnpm run admin-login -- --url http://127.0.0.1:5999/#/auth/login
| 脚本 | 用途 |
| --- | --- |
| `pnpm run admin-login` | 使用可见 Edge 打开 Admin 登录页,填写账号密码,拖动滑块,保存登录态和截图。默认账号来自初始化数据 `admin/123456`,生产或个人账号用 `KT_ADMIN_USERNAME` / `KT_ADMIN_PASSWORD` 或 CLI 参数覆盖。 |
| `pnpm run global-review` | 对 KT 全部子仓库做只读全局 CodeReview 扫描,默认仅对变更文件做内容深扫,输出 JSON 复审报告;确认误报时优先升级 `src/tools/review.ts`,不要把误报沉积到上下文。 |
| `pnpm run global-review` | 对 KT 全部子仓库做只读全局 CodeReview 扫描,默认仅对变更文件做内容深扫,输出 JSON 复审报告,并校验 `TASKS.md` 最近记录只保留范围、关键词、验证字段;确认误报时优先升级 `src/tools/review.ts`,不要把误报沉积到上下文。 |
| `pnpm run obsidian-context` | 输出 Obsidian 索引上下文;可传 `--module Admin`、`--query QQBot`、`--max-documents 10`。 |
| `pnpm run obsidian-validate` | 校验 KT Obsidian vault 结构和链接;默认 warning 不让脚本失败,需要严格模式时传 `--fail-on-warnings`。 |
| `pnpm run obsidian-sync` | 审计 Obsidian 工作流入口是否连通,并联动执行 validate。 |

View File

@ -12,7 +12,7 @@ import { cleanupHistoryArtifacts, parseCliCleanupArgs } from './tools/cleanup.js
import { buildWorkflowLoopAudit } from './tools/loop.js';
import { inspectProject } from './tools/inspect.js';
import { prepareTask, readWorkflowContext } from './tools/task.js';
import { buildGlobalCodeReview, isBenignCredentialReviewValue } from './tools/review.js';
import { buildGlobalCodeReview, findTaskRecordGovernanceFindings, isBenignCredentialReviewValue } from './tools/review.js';
import { buildBusinessTestPlan } from './tools/testing.js';
import { buildVerificationPlan } from './tools/verification.js';
import { buildWorkstreamCloseout } from './tools/closeout.js';
@ -26,6 +26,19 @@ export async function runSelfTest(): Promise<void> {
throw new Error('review credential classifier self-check failed');
}
const taskRecordFindings = findTaskRecordGovernanceFindings([
'## 最近记录',
'### 2026-06-10长记录污染',
'- 范围root',
'- 关键词TASKS 治理',
'- 页面测试用例:配置类任务,无页面测试。',
'- 验证global-review',
'## 历史索引',
]);
if (!taskRecordFindings.some((item) => item.category === 'tasks-record-extra-field')) {
throw new Error('TASKS record governance self-check failed');
}
const reviewCliParser = {
dashedAll: parseGlobalReviewCliArgs(['node', 'server', '--global-review', '--content-scan-all']).contentScanMode,
keyValueAll: parseGlobalReviewCliArgs(['node', 'server', '--global-review', '--contentScanMode=all']).contentScanMode,

View File

@ -54,6 +54,8 @@ const frontendEnvProjectAliases = new Set<ProjectAlias>([
const credentialAssignmentPattern =
/\b(password|passwd|secret|token|webuiToken|accessToken|privateKey)\b\s*[:=]\s*(["'])([^"']+)\2/i;
const taskRecordAllowedFields = new Set(['范围', '关键词', '验证']);
const taskRecordRequiredFields = ['范围', '关键词', '验证'] as const;
interface ReviewFinding {
category: string;
@ -78,6 +80,79 @@ interface ReviewProjectResult {
trackedSensitiveFiles: string[];
}
export function findTaskRecordGovernanceFindings(lines: string[]): ReviewFinding[] {
const findings: ReviewFinding[] = [];
const recentStart = lines.findIndex((line) => line.trim() === '## 最近记录');
if (recentStart < 0) return findings;
const nextSectionOffset = lines
.slice(recentStart + 1)
.findIndex((line) => /^##\s+/.test(line.trim()));
const recentEnd = nextSectionOffset < 0 ? lines.length : recentStart + 1 + nextSectionOffset;
let currentRecord:
| {
fields: Set<string>;
heading: string;
line: number;
}
| null = null;
const pushMissingFieldFindings = () => {
if (!currentRecord) return;
for (const field of taskRecordRequiredFields) {
if (currentRecord.fields.has(field)) continue;
findings.push({
category: 'tasks-record-missing-field',
file: 'TASKS.md',
line: currentRecord.line,
level: 'P2',
message: `TASKS 最近记录缺少「${field}」字段。`,
project: 'root',
suggestion: '最近记录只保留范围、关键词、验证三项,详细过程迁入 docs 或历史索引。',
});
}
};
for (let index = recentStart + 1; index < recentEnd; index += 1) {
const line = lines[index].trim();
if (line.startsWith('### ')) {
pushMissingFieldFindings();
currentRecord = {
fields: new Set<string>(),
heading: line.replace(/^###\s+/, ''),
line: index + 1,
};
continue;
}
if (!currentRecord) continue;
const fieldMatch = line.match(/^-\s*([^:]+)[:]/);
if (!fieldMatch) continue;
const field = fieldMatch[1].trim();
if (taskRecordAllowedFields.has(field)) {
currentRecord.fields.add(field);
continue;
}
findings.push({
category: 'tasks-record-extra-field',
file: 'TASKS.md',
line: index + 1,
level: 'P2',
message: `TASKS 最近记录「${currentRecord.heading}」包含额外字段「${field}」。`,
project: 'root',
suggestion: '把额外说明迁入 docsTASKS 最近记录只保留范围、关键词、验证。',
});
}
pushMissingFieldFindings();
return findings;
}
async function listGitTrackedFiles(projectPath: string): Promise<string[]> {
const tracked = await tryExecFile('git', ['ls-files'], projectPath);
if (!tracked.ok) return [];
@ -385,6 +460,10 @@ export async function buildGlobalCodeReview(
: findWorkspaceFilesByName(new Set(['deploy.js', 'deploy.json']), 50);
const rootGeneratedArtifacts =
input.includeRootScan === false ? [] : findRootGeneratedArtifacts();
const taskRecordFindings =
input.includeRootScan === false
? []
: findTaskRecordGovernanceFindings(readProjectFileLines(workspaceRoot, 'TASKS.md'));
const findings = projectResults.flatMap((item) => [
...item.findings,
...item.debugFindings,
@ -406,7 +485,12 @@ export async function buildGlobalCodeReview(
project: 'root',
suggestion: '统一迁入 .kt-workspace 对应子目录,并更新生成脚本或测试流程。',
}));
const allFindings = [...findings, ...rootFindings, ...rootGeneratedFindings];
const allFindings = [
...findings,
...rootFindings,
...rootGeneratedFindings,
...taskRecordFindings,
];
return {
findings: allFindings,