refactor: 收口KT工作流护栏
This commit is contained in:
parent
300a34d33e
commit
2372b682a5
22
src/core/cli.ts
Normal file
22
src/core/cli.ts
Normal 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'),
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -34,6 +34,7 @@ export const taskTypeValues = [
|
|||||||
'general',
|
'general',
|
||||||
'mcp',
|
'mcp',
|
||||||
'page',
|
'page',
|
||||||
|
'refactor',
|
||||||
'style',
|
'style',
|
||||||
] as const satisfies readonly TaskType[];
|
] as const satisfies readonly TaskType[];
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { mkdirSync, writeFileSync } from 'node:fs';
|
import { mkdirSync, writeFileSync } from 'node:fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
|
||||||
|
import { parseGlobalReviewCliArgs } from './core/cli.js';
|
||||||
import { registeredToolNames } from './core/constants.js';
|
import { registeredToolNames } from './core/constants.js';
|
||||||
import { resolveInsideRoot } from './core/workspace.js';
|
import { resolveInsideRoot } from './core/workspace.js';
|
||||||
import { buildGuardrails } from './tools/guardrails.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');
|
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 = {
|
const data = {
|
||||||
context: readWorkflowContext({ taskRecordCount: 2 }),
|
context: readWorkflowContext({ taskRecordCount: 2 }),
|
||||||
guardrails: buildGuardrails({
|
guardrails: buildGuardrails({
|
||||||
@ -45,7 +71,9 @@ export async function runSelfTest(): Promise<void> {
|
|||||||
maxFindingsPerProject: 10,
|
maxFindingsPerProject: 10,
|
||||||
projects: ['mcp'],
|
projects: ['mcp'],
|
||||||
}),
|
}),
|
||||||
|
reviewCliParser,
|
||||||
reviewClassifier,
|
reviewClassifier,
|
||||||
|
refactorGuardrails,
|
||||||
businessTestPlan: buildBusinessTestPlan({
|
businessTestPlan: buildBusinessTestPlan({
|
||||||
flow: 'system-log-visualization',
|
flow: 'system-log-visualization',
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||||
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
||||||
|
|
||||||
|
import { parseGlobalReviewCliArgs } from './core/cli.js';
|
||||||
import { cleanupHistoryArtifacts, parseCliCleanupArgs } from './tools/cleanup.js';
|
import { cleanupHistoryArtifacts, parseCliCleanupArgs } from './tools/cleanup.js';
|
||||||
import { buildGlobalCodeReview } from './tools/review.js';
|
import { buildGlobalCodeReview } from './tools/review.js';
|
||||||
import { registerTools } from './registerTools.js';
|
import { registerTools } from './registerTools.js';
|
||||||
@ -12,10 +13,7 @@ if (process.argv.includes('--cleanup-history')) {
|
|||||||
} else if (process.argv.includes('--global-review')) {
|
} else if (process.argv.includes('--global-review')) {
|
||||||
console.log(
|
console.log(
|
||||||
JSON.stringify(
|
JSON.stringify(
|
||||||
await buildGlobalCodeReview({
|
await buildGlobalCodeReview(parseGlobalReviewCliArgs(process.argv)),
|
||||||
includeContentScan: !process.argv.includes('--no-content-scan'),
|
|
||||||
includeRootScan: !process.argv.includes('--no-root-scan'),
|
|
||||||
}),
|
|
||||||
null,
|
null,
|
||||||
2,
|
2,
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,20 +1,24 @@
|
|||||||
import type { GuardrailSet, GuardrailsInput, GuardrailsResult } from '../types.js';
|
import type { GuardrailSet, GuardrailsInput, GuardrailsResult } from '../types.js';
|
||||||
import { resolveProject } from '../core/workspace.js';
|
import { resolveProject } from '../core/workspace.js';
|
||||||
|
import { buildKarpathyGuardrails } from './karpathy.js';
|
||||||
import { createPageTestCase } from './testing.js';
|
import { createPageTestCase } from './testing.js';
|
||||||
import { buildVerificationPlan } from './verification.js';
|
import { buildVerificationPlan } from './verification.js';
|
||||||
export function buildGuardrails(input: GuardrailsInput): GuardrailsResult {
|
export function buildGuardrails(input: GuardrailsInput): GuardrailsResult {
|
||||||
const project = resolveProject(input.project);
|
const project = resolveProject(input.project);
|
||||||
const taskType = input.taskType || input.changeType || 'general';
|
const taskType = input.taskType || input.changeType || 'general';
|
||||||
const paths = input.paths || [];
|
const paths = input.paths || [];
|
||||||
|
const karpathy = buildKarpathyGuardrails(taskType);
|
||||||
const guardrails: GuardrailSet = {
|
const guardrails: GuardrailSet = {
|
||||||
beforeEdit: [
|
beforeEdit: [
|
||||||
'先读最近的 AGENTS.md,再读 TASKS.md 和 SKILLS.md。',
|
'先读最近的 AGENTS.md,再读 TASKS.md 和 SKILLS.md。',
|
||||||
|
...karpathy.beforeEdit,
|
||||||
'先检查目标仓库状态,识别用户已有改动,不覆盖、不回滚无关文件。',
|
'先检查目标仓库状态,识别用户已有改动,不覆盖、不回滚无关文件。',
|
||||||
'先快速扫现有代码风格和目录结构,再决定改动方式。',
|
'先快速扫现有代码风格和目录结构,再决定改动方式。',
|
||||||
'搜索优先用 rg,路径和接口名尽量保持可 grep。',
|
'搜索优先用 rg,路径和接口名尽量保持可 grep。',
|
||||||
],
|
],
|
||||||
duringEdit: [
|
duringEdit: [
|
||||||
'改动范围收敛到本次需求相关文件。',
|
'改动范围收敛到本次需求相关文件。',
|
||||||
|
...karpathy.duringEdit,
|
||||||
'复杂度高的地方加简短注释,避免空泛解释。',
|
'复杂度高的地方加简短注释,避免空泛解释。',
|
||||||
'后端真实环境配置不写入提交内容;前端 env 只允许客户端公开变量。',
|
'后端真实环境配置不写入提交内容;前端 env 只允许客户端公开变量。',
|
||||||
'前端实现遵循现有 antdv-next/Vben 约定,不引入无关抽象。',
|
'前端实现遵循现有 antdv-next/Vben 约定,不引入无关抽象。',
|
||||||
@ -24,6 +28,7 @@ export function buildGuardrails(input: GuardrailsInput): GuardrailsResult {
|
|||||||
'不要在用户未要求时自动 push。',
|
'不要在用户未要求时自动 push。',
|
||||||
'不要把真实密钥、数据库密码、生产 env 写进可提交文件。',
|
'不要把真实密钥、数据库密码、生产 env 写进可提交文件。',
|
||||||
'不要为了验证长期保留 Node/Vite/浏览器进程。',
|
'不要为了验证长期保留 Node/Vite/浏览器进程。',
|
||||||
|
...karpathy.doNot,
|
||||||
],
|
],
|
||||||
verification: buildVerificationPlan({
|
verification: buildVerificationPlan({
|
||||||
changeType: taskType,
|
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('MCP 变更至少跑 self-test 和真实 SDK Client smoke test。');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
guardrails.verification.notes.push(...karpathy.verificationNotes);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
guardrails,
|
guardrails,
|
||||||
paths,
|
paths,
|
||||||
|
|||||||
48
src/tools/karpathy.ts
Normal file
48
src/tools/karpathy.ts
Normal 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;
|
||||||
|
}
|
||||||
@ -6,6 +6,7 @@ import { detectRepoType } from '../core/project.js';
|
|||||||
import { formatDateInShanghai, getRecentTaskRecords, getTaskContextIndex, listWorkspaceSkills, readText, resolveInsideRoot, resolveProject, workspaceRoot } from '../core/workspace.js';
|
import { formatDateInShanghai, getRecentTaskRecords, getTaskContextIndex, listWorkspaceSkills, readText, resolveInsideRoot, resolveProject, workspaceRoot } from '../core/workspace.js';
|
||||||
import { buildGuardrails } from './guardrails.js';
|
import { buildGuardrails } from './guardrails.js';
|
||||||
import { inspectProject } from './inspect.js';
|
import { inspectProject } from './inspect.js';
|
||||||
|
import { karpathyCommitChecklistItem, karpathyPrepareTaskStep } from './karpathy.js';
|
||||||
import { scanTaskRisk } from './envRisk.js';
|
import { scanTaskRisk } from './envRisk.js';
|
||||||
import { createPageTestCase } from './testing.js';
|
import { createPageTestCase } from './testing.js';
|
||||||
export async function prepareTask(input: PrepareTaskInput): Promise<Record<string, unknown>> {
|
export async function prepareTask(input: PrepareTaskInput): Promise<Record<string, unknown>> {
|
||||||
@ -35,6 +36,7 @@ export async function prepareTask(input: PrepareTaskInput): Promise<Record<strin
|
|||||||
nextSteps: [
|
nextSteps: [
|
||||||
'阅读 workPacket.readFirst 中的上下文文件。',
|
'阅读 workPacket.readFirst 中的上下文文件。',
|
||||||
'按 guardrails.beforeEdit 做开工前检查。',
|
'按 guardrails.beforeEdit 做开工前检查。',
|
||||||
|
karpathyPrepareTaskStep,
|
||||||
'按最小范围修改。',
|
'按最小范围修改。',
|
||||||
'按 verification.commands 和 notes 做轻量验证。',
|
'按 verification.commands 和 notes 做轻量验证。',
|
||||||
'有文件改动时更新 TASKS.md。',
|
'有文件改动时更新 TASKS.md。',
|
||||||
@ -113,6 +115,7 @@ export async function createCommitChecklist(input: CommitChecklistInput): Promis
|
|||||||
return {
|
return {
|
||||||
checks: [
|
checks: [
|
||||||
'确认只包含本次任务相关文件。',
|
'确认只包含本次任务相关文件。',
|
||||||
|
karpathyCommitChecklistItem,
|
||||||
'先运行对应轻量验证命令。',
|
'先运行对应轻量验证命令。',
|
||||||
'提交前已跑全局 review,且 findings 已修复;确认误报时优先修 ktWorkflow 过滤规则,避免污染后续上下文。',
|
'提交前已跑全局 review,且 findings 已修复;确认误报时优先修 ktWorkflow 过滤规则,避免污染后续上下文。',
|
||||||
'每个独立 Git 仓库单独 commit。',
|
'每个独立 Git 仓库单独 commit。',
|
||||||
|
|||||||
@ -29,14 +29,14 @@ export function buildVerificationPlan(input: VerificationPlanInput): Verificatio
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
typecheck &&
|
typecheck &&
|
||||||
['api', 'frontend', 'general', 'mcp', 'page', 'style'].includes(changeType)
|
['api', 'frontend', 'general', 'mcp', 'page', 'refactor', 'style'].includes(changeType)
|
||||||
) {
|
) {
|
||||||
commands.push(typecheck);
|
commands.push(typecheck);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (project.alias === 'api') {
|
if (project.alias === 'api') {
|
||||||
if (scripts.lint) commands.push(`${packageManager} run lint`);
|
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`);
|
commands.push(`${packageManager} test -- --passWithNoTests`);
|
||||||
}
|
}
|
||||||
if (['api', 'backend'].includes(changeType)) {
|
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');
|
if (repoType === 'git') commands.push('git diff --check');
|
||||||
commands.push(getGlobalReviewCommand());
|
commands.push(getGlobalReviewCommand());
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { tryExecFile, tryPowerShell } from '../core/exec.js';
|
|||||||
import { detectRepoType, parseGitStatusFiles } from '../core/project.js';
|
import { detectRepoType, parseGitStatusFiles } from '../core/project.js';
|
||||||
import { formatDateInShanghai, getGlobalReviewCommand, resolveProject, toPosix, workspaceRoot } from '../core/workspace.js';
|
import { formatDateInShanghai, getGlobalReviewCommand, resolveProject, toPosix, workspaceRoot } from '../core/workspace.js';
|
||||||
import { cleanupHistoryArtifacts } from './cleanup.js';
|
import { cleanupHistoryArtifacts } from './cleanup.js';
|
||||||
|
import { karpathyChangedFileChecklistItem, karpathyFinishNote, karpathyStopCondition } from './karpathy.js';
|
||||||
import { buildGlobalCodeReview, defaultReviewProjects } from './review.js';
|
import { buildGlobalCodeReview, defaultReviewProjects } from './review.js';
|
||||||
import { buildVerificationPlan } from './verification.js';
|
import { buildVerificationPlan } from './verification.js';
|
||||||
interface RepoSnapshot {
|
interface RepoSnapshot {
|
||||||
@ -113,6 +114,7 @@ export async function buildFinishTask(input: FinishTaskInput = {}): Promise<Reco
|
|||||||
'默认只生成收尾结果;runValidation=true 时才执行验证命令。',
|
'默认只生成收尾结果;runValidation=true 时才执行验证命令。',
|
||||||
'cleanupHistory=true 时才真实清理历史产物,否则仅预览。',
|
'cleanupHistory=true 时才真实清理历史产物,否则仅预览。',
|
||||||
'提交和推送仍需用户明确要求后执行。',
|
'提交和推送仍需用户明确要求后执行。',
|
||||||
|
karpathyFinishNote,
|
||||||
],
|
],
|
||||||
review,
|
review,
|
||||||
snapshots,
|
snapshots,
|
||||||
@ -120,6 +122,7 @@ export async function buildFinishTask(input: FinishTaskInput = {}): Promise<Reco
|
|||||||
'review.findings 存在 P1/P2 时先判定真实风险或工具误报;真实风险修代码,工具误报修 ktWorkflow,再复跑 kt_global_code_review。',
|
'review.findings 存在 P1/P2 时先判定真实风险或工具误报;真实风险修代码,工具误报修 ktWorkflow,再复跑 kt_global_code_review。',
|
||||||
'validationRuns 中任一 result.ok=false 时先处理失败命令。',
|
'validationRuns 中任一 result.ok=false 时先处理失败命令。',
|
||||||
'发现真实 env、生产 Secret 或非本次范围文件时停止提交。',
|
'发现真实 env、生产 Secret 或非本次范围文件时停止提交。',
|
||||||
|
karpathyStopCondition,
|
||||||
],
|
],
|
||||||
summary: {
|
summary: {
|
||||||
changedProjectCount: changedSnapshots.length,
|
changedProjectCount: changedSnapshots.length,
|
||||||
@ -138,6 +141,7 @@ export async function buildCommitPlan(input: CommitPlanInput = {}): Promise<Reco
|
|||||||
return {
|
return {
|
||||||
checks: [
|
checks: [
|
||||||
'每个独立 Git 仓库单独提交。',
|
'每个独立 Git 仓库单独提交。',
|
||||||
|
karpathyChangedFileChecklistItem,
|
||||||
'提交前先跑对应轻量验证和 kt_global_code_review。',
|
'提交前先跑对应轻量验证和 kt_global_code_review。',
|
||||||
'kt_global_code_review findings 必须为 0;确认误报时先修 ktWorkflow 规则,不把误报当作长期上下文。',
|
'kt_global_code_review findings 必须为 0;确认误报时先修 ktWorkflow 规则,不把误报当作长期上下文。',
|
||||||
'后端真实 .env.development/.env.production 不提交;前端 env 可提交但只能包含客户端公开配置。',
|
'后端真实 .env.development/.env.production 不提交;前端 env 可提交但只能包含客户端公开配置。',
|
||||||
|
|||||||
@ -22,6 +22,7 @@ export type TaskType =
|
|||||||
| 'general'
|
| 'general'
|
||||||
| 'mcp'
|
| 'mcp'
|
||||||
| 'page'
|
| 'page'
|
||||||
|
| 'refactor'
|
||||||
| 'style';
|
| 'style';
|
||||||
|
|
||||||
export interface ResolvedProject {
|
export interface ResolvedProject {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user