refactor: 收口KT工作流护栏

This commit is contained in:
sunlei 2026-06-05 16:21:25 +08:00
parent 300a34d33e
commit 2372b682a5
10 changed files with 122 additions and 6 deletions

22
src/core/cli.ts Normal file
View File

@ -0,0 +1,22 @@
import type { GlobalCodeReviewInput } from '../types.js';
function parseContentScanMode(argv: string[]): 'all' | 'changed' {
if (argv.includes('--content-scan-all') || argv.includes('--contentScanMode=all')) {
return 'all';
}
const modeIndex = argv.findIndex(
(item) => item === '--contentScanMode' || item === '--content-scan-mode',
);
return modeIndex >= 0 && argv[modeIndex + 1] === 'all' ? 'all' : 'changed';
}
export function parseGlobalReviewCliArgs(
argv: string[],
): Pick<GlobalCodeReviewInput, 'contentScanMode' | 'includeContentScan' | 'includeRootScan'> {
return {
contentScanMode: parseContentScanMode(argv),
includeContentScan: !argv.includes('--no-content-scan'),
includeRootScan: !argv.includes('--no-root-scan'),
};
}

View File

@ -34,6 +34,7 @@ export const taskTypeValues = [
'general',
'mcp',
'page',
'refactor',
'style',
] as const satisfies readonly TaskType[];

View File

@ -1,6 +1,7 @@
import { mkdirSync, writeFileSync } from 'node:fs';
import path from 'node:path';
import { parseGlobalReviewCliArgs } from './core/cli.js';
import { registeredToolNames } from './core/constants.js';
import { resolveInsideRoot } from './core/workspace.js';
import { buildGuardrails } from './tools/guardrails.js';
@ -20,6 +21,31 @@ export async function runSelfTest(): Promise<void> {
throw new Error('review credential classifier self-check failed');
}
const reviewCliParser = {
dashedAll: parseGlobalReviewCliArgs(['node', 'server', '--global-review', '--content-scan-all']).contentScanMode,
keyValueAll: parseGlobalReviewCliArgs(['node', 'server', '--global-review', '--contentScanMode=all']).contentScanMode,
splitAll: parseGlobalReviewCliArgs(['node', 'server', '--global-review', '--content-scan-mode', 'all']).contentScanMode,
};
if (
reviewCliParser.dashedAll !== 'all' ||
reviewCliParser.keyValueAll !== 'all' ||
reviewCliParser.splitAll !== 'all'
) {
throw new Error('global-review CLI content scan mode parser self-check failed');
}
const refactorGuardrails = buildGuardrails({
project: 'mcp',
taskType: 'refactor',
userRequest: '重构 KT 工作流护栏',
});
const refactorGuardrailOk =
refactorGuardrails.guardrails.beforeEdit.some((item) => item.includes('重构前写清楚')) &&
refactorGuardrails.guardrails.verification.notes.some((item) => item.includes('重构验证必须覆盖原行为'));
if (!refactorGuardrailOk) {
throw new Error('refactor guardrail self-check failed');
}
const data = {
context: readWorkflowContext({ taskRecordCount: 2 }),
guardrails: buildGuardrails({
@ -45,7 +71,9 @@ export async function runSelfTest(): Promise<void> {
maxFindingsPerProject: 10,
projects: ['mcp'],
}),
reviewCliParser,
reviewClassifier,
refactorGuardrails,
businessTestPlan: buildBusinessTestPlan({
flow: 'system-log-visualization',
}),

View File

@ -2,6 +2,7 @@
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { parseGlobalReviewCliArgs } from './core/cli.js';
import { cleanupHistoryArtifacts, parseCliCleanupArgs } from './tools/cleanup.js';
import { buildGlobalCodeReview } from './tools/review.js';
import { registerTools } from './registerTools.js';
@ -12,10 +13,7 @@ if (process.argv.includes('--cleanup-history')) {
} else if (process.argv.includes('--global-review')) {
console.log(
JSON.stringify(
await buildGlobalCodeReview({
includeContentScan: !process.argv.includes('--no-content-scan'),
includeRootScan: !process.argv.includes('--no-root-scan'),
}),
await buildGlobalCodeReview(parseGlobalReviewCliArgs(process.argv)),
null,
2,
),

View File

@ -1,20 +1,24 @@
import type { GuardrailSet, GuardrailsInput, GuardrailsResult } from '../types.js';
import { resolveProject } from '../core/workspace.js';
import { buildKarpathyGuardrails } from './karpathy.js';
import { createPageTestCase } from './testing.js';
import { buildVerificationPlan } from './verification.js';
export function buildGuardrails(input: GuardrailsInput): GuardrailsResult {
const project = resolveProject(input.project);
const taskType = input.taskType || input.changeType || 'general';
const paths = input.paths || [];
const karpathy = buildKarpathyGuardrails(taskType);
const guardrails: GuardrailSet = {
beforeEdit: [
'先读最近的 AGENTS.md再读 TASKS.md 和 SKILLS.md。',
...karpathy.beforeEdit,
'先检查目标仓库状态,识别用户已有改动,不覆盖、不回滚无关文件。',
'先快速扫现有代码风格和目录结构,再决定改动方式。',
'搜索优先用 rg路径和接口名尽量保持可 grep。',
],
duringEdit: [
'改动范围收敛到本次需求相关文件。',
...karpathy.duringEdit,
'复杂度高的地方加简短注释,避免空泛解释。',
'后端真实环境配置不写入提交内容;前端 env 只允许客户端公开变量。',
'前端实现遵循现有 antdv-next/Vben 约定,不引入无关抽象。',
@ -24,6 +28,7 @@ export function buildGuardrails(input: GuardrailsInput): GuardrailsResult {
'不要在用户未要求时自动 push。',
'不要把真实密钥、数据库密码、生产 env 写进可提交文件。',
'不要为了验证长期保留 Node/Vite/浏览器进程。',
...karpathy.doNot,
],
verification: buildVerificationPlan({
changeType: taskType,
@ -65,6 +70,8 @@ export function buildGuardrails(input: GuardrailsInput): GuardrailsResult {
guardrails.verification.notes.push('MCP 变更至少跑 self-test 和真实 SDK Client smoke test。');
}
guardrails.verification.notes.push(...karpathy.verificationNotes);
return {
guardrails,
paths,

48
src/tools/karpathy.ts Normal file
View File

@ -0,0 +1,48 @@
import type { TaskType } from '../types.js';
export interface KarpathyGuardrailItems {
beforeEdit: string[];
doNot: string[];
duringEdit: string[];
verificationNotes: string[];
}
export const karpathyPrepareTaskStep =
'先写清假设、范围边界和可验证成功标准;需求过宽时先审计并收敛目标。';
export const karpathyCommitChecklistItem =
'确认改动没有引入无验收标准的整体重构或顺手格式化。';
export const karpathyChangedFileChecklistItem =
'提交前确认每个 changed file 都属于本次请求、必要验证修复或 KT 记录维护。';
export const karpathyFinishNote =
'按 karpathy-guidelines 审计:没有明确假设、范围和成功证据的整体优化不应生成提交。';
export const karpathyStopCondition =
'发现全仓格式化、整文件重写或无验收标准重构时停止提交。';
export function buildKarpathyGuardrails(taskType: TaskType): KarpathyGuardrailItems {
const items: KarpathyGuardrailItems = {
beforeEdit: [
'非平凡编码/评审/重构任务先套用 karpathy-guidelines明确假设、取最小可行改动、定义可验证成功标准。',
'如果需求是笼统优化或整体重构,先把它收敛成具体行为、风险和验收证据;没有明确目标时只做审计不制造 diff。',
],
doNot: [
'不要为了“整体优化”做全仓格式化、整文件重写或无验收标准的重构。',
],
duringEdit: [
'每一行改动都应能追溯到用户请求、必要验证修复或 KT 记录维护。',
'不为单次使用新增抽象,不引入未被请求的灵活性或配置项。',
],
verificationNotes: [],
};
if (taskType === 'refactor') {
items.beforeEdit.push('重构前写清楚要保留的行为、要降低的复杂度和不触碰的边界。');
items.duringEdit.push('只合并真实重复、删除自己造成的孤儿代码,避免顺手重构邻近模块。');
items.verificationNotes.push('重构验证必须覆盖原行为;没有行为证据时不要声称重构完成。');
}
return items;
}

View File

@ -6,6 +6,7 @@ import { detectRepoType } from '../core/project.js';
import { formatDateInShanghai, getRecentTaskRecords, getTaskContextIndex, listWorkspaceSkills, readText, resolveInsideRoot, resolveProject, workspaceRoot } from '../core/workspace.js';
import { buildGuardrails } from './guardrails.js';
import { inspectProject } from './inspect.js';
import { karpathyCommitChecklistItem, karpathyPrepareTaskStep } from './karpathy.js';
import { scanTaskRisk } from './envRisk.js';
import { createPageTestCase } from './testing.js';
export async function prepareTask(input: PrepareTaskInput): Promise<Record<string, unknown>> {
@ -35,6 +36,7 @@ export async function prepareTask(input: PrepareTaskInput): Promise<Record<strin
nextSteps: [
'阅读 workPacket.readFirst 中的上下文文件。',
'按 guardrails.beforeEdit 做开工前检查。',
karpathyPrepareTaskStep,
'按最小范围修改。',
'按 verification.commands 和 notes 做轻量验证。',
'有文件改动时更新 TASKS.md。',
@ -113,6 +115,7 @@ export async function createCommitChecklist(input: CommitChecklistInput): Promis
return {
checks: [
'确认只包含本次任务相关文件。',
karpathyCommitChecklistItem,
'先运行对应轻量验证命令。',
'提交前已跑全局 review且 findings 已修复;确认误报时优先修 ktWorkflow 过滤规则,避免污染后续上下文。',
'每个独立 Git 仓库单独 commit。',

View File

@ -29,14 +29,14 @@ export function buildVerificationPlan(input: VerificationPlanInput): Verificatio
if (
typecheck &&
['api', 'frontend', 'general', 'mcp', 'page', 'style'].includes(changeType)
['api', 'frontend', 'general', 'mcp', 'page', 'refactor', 'style'].includes(changeType)
) {
commands.push(typecheck);
}
if (project.alias === 'api') {
if (scripts.lint) commands.push(`${packageManager} run lint`);
if (scripts.test && ['api', 'backend', 'general'].includes(changeType)) {
if (scripts.test && ['api', 'backend', 'general', 'refactor'].includes(changeType)) {
commands.push(`${packageManager} test -- --passWithNoTests`);
}
if (['api', 'backend'].includes(changeType)) {
@ -76,6 +76,10 @@ export function buildVerificationPlan(input: VerificationPlanInput): Verificatio
);
}
if (changeType === 'refactor') {
notes.push('重构验证要证明原行为保持不变,并说明复杂度、重复或职责边界改善点;没有明确收益时不要改。');
}
if (repoType === 'git') commands.push('git diff --check');
commands.push(getGlobalReviewCommand());

View File

@ -3,6 +3,7 @@ import { tryExecFile, tryPowerShell } from '../core/exec.js';
import { detectRepoType, parseGitStatusFiles } from '../core/project.js';
import { formatDateInShanghai, getGlobalReviewCommand, resolveProject, toPosix, workspaceRoot } from '../core/workspace.js';
import { cleanupHistoryArtifacts } from './cleanup.js';
import { karpathyChangedFileChecklistItem, karpathyFinishNote, karpathyStopCondition } from './karpathy.js';
import { buildGlobalCodeReview, defaultReviewProjects } from './review.js';
import { buildVerificationPlan } from './verification.js';
interface RepoSnapshot {
@ -113,6 +114,7 @@ export async function buildFinishTask(input: FinishTaskInput = {}): Promise<Reco
'默认只生成收尾结果runValidation=true 时才执行验证命令。',
'cleanupHistory=true 时才真实清理历史产物,否则仅预览。',
'提交和推送仍需用户明确要求后执行。',
karpathyFinishNote,
],
review,
snapshots,
@ -120,6 +122,7 @@ export async function buildFinishTask(input: FinishTaskInput = {}): Promise<Reco
'review.findings 存在 P1/P2 时先判定真实风险或工具误报;真实风险修代码,工具误报修 ktWorkflow再复跑 kt_global_code_review。',
'validationRuns 中任一 result.ok=false 时先处理失败命令。',
'发现真实 env、生产 Secret 或非本次范围文件时停止提交。',
karpathyStopCondition,
],
summary: {
changedProjectCount: changedSnapshots.length,
@ -138,6 +141,7 @@ export async function buildCommitPlan(input: CommitPlanInput = {}): Promise<Reco
return {
checks: [
'每个独立 Git 仓库单独提交。',
karpathyChangedFileChecklistItem,
'提交前先跑对应轻量验证和 kt_global_code_review。',
'kt_global_code_review findings 必须为 0确认误报时先修 ktWorkflow 规则,不把误报当作长期上下文。',
'后端真实 .env.development/.env.production 不提交;前端 env 可提交但只能包含客户端公开配置。',

View File

@ -22,6 +22,7 @@ export type TaskType =
| 'general'
| 'mcp'
| 'page'
| 'refactor'
| 'style';
export interface ResolvedProject {