feat: 升级KT工作流能力
This commit is contained in:
parent
d813dc69ab
commit
ad7ee6d1b0
20
README.md
20
README.md
@ -12,15 +12,15 @@
|
||||
- 生成任务收尾包:把状态、验证计划、可选验证执行、全局 review 和历史清理收成一份结果。
|
||||
- 生成页面测试用例:内置“先写用例、可视化证据、事不过三”的测试闭环。
|
||||
- 生成接口测试计划:接口改动后输出真实调用命令和统一返回结构断言。
|
||||
- 生成业务链路测试计划:固化 Admin 登录、博客 CRUD、QQBot 扫码/自动回复、Web/Playground 回跳。
|
||||
- 生成业务链路测试计划:固化 Admin 登录、博客 CRUD、QQBot 扫码/自动回复、更新登录 SSE、FFLogs 命令、系统日志可视化、Web/Playground 回跳。
|
||||
- 生成多仓库提交/推送计划:按仓库分组、建议提交信息、列出提交和推送前检查。
|
||||
- 生成远程只读健康检查和数据库同步安全向导:覆盖飞牛 NAS 服务探测、GTID、备份和行数校验。
|
||||
- 生成专项组件工作流:KtTable、BlogArgon、AdminAuth、QQBot、Knife4jSwagger 的防踩坑清单和验证点。
|
||||
- 生成专项组件工作流:KtTable、BlogArgon、AdminAuth、QQBot、FF14Plugin、NapCatLogin、SystemLog、Knife4jSwagger 的防踩坑清单和验证点。
|
||||
- 生成验证进程清理计划:按项目路径和端口给出 PowerShell 检查命令,不直接杀进程。
|
||||
- 清理历史产物:统一治理 `.kt-workspace` 下的测试/验证产物,按目录最近修改时间只保留最近 3 轮,模板目录永久保留。
|
||||
- 检查 env 策略和变更风险:区分后端真实 env 与前端客户端 `.env*`,提醒锁文件、核心表格组件、部署链路和 Vue TSX 插槽写法等高风险改动。
|
||||
- 全局 CodeReview 只读扫描:汇总全部 KT 子仓库的 Git 状态、敏感文件跟踪、冲突标记、运行时调试输出、疑似凭据字面量、根目录生成产物和当前变更风险;任何文件改动后都要跑一遍。
|
||||
- 生成或写入 `TASKS.md` 改动记录:默认 `dryRun=true`,确认后再落盘。
|
||||
- 全局 CodeReview 只读扫描:汇总全部 KT 子仓库的 Git 状态、敏感文件跟踪、冲突标记、运行时调试输出、疑似凭据字面量、根目录生成产物和当前变更风险;默认只对变更文件做内容扫描,避免历史误报污染上下文;任何文件改动后都要跑一遍。
|
||||
- 生成或写入 `TASKS.md` 最近记录:默认 `dryRun=true`,确认后再落盘。
|
||||
- 生成提交前检查清单:校验 KT commit message 约定。
|
||||
|
||||
## 安装
|
||||
@ -74,15 +74,15 @@ pnpm run admin-login -- --url http://127.0.0.1:5999/#/auth/login
|
||||
| `kt_cleanup_process_plan` | 生成验证进程清理检查命令 |
|
||||
| `kt_env_policy` | 检查 env 文件现状和提交策略 |
|
||||
| `kt_risk_scan` | 扫描当前或传入变更文件的偏差风险 |
|
||||
| `kt_global_code_review` | 对 KT 全部子仓库做只读全局 CodeReview 扫描 |
|
||||
| `kt_global_code_review` | 对 KT 全部子仓库做只读全局 CodeReview 扫描,默认仅深扫变更文件 |
|
||||
| `kt_finish_task` | 生成任务收尾包,可选执行验证和历史清理 |
|
||||
| `kt_commit_plan` | 生成多仓库提交计划、建议 commit message 和检查项 |
|
||||
| `kt_push_plan` | 生成多仓库推送计划和远程异常提醒 |
|
||||
| `kt_business_test_plan` | 生成固化业务链路测试计划 |
|
||||
| `kt_business_test_plan` | 生成固化业务链路测试计划,包含 QQBot SSE、FFLogs 和系统日志 |
|
||||
| `kt_remote_health_check` | 生成或执行远程只读健康检查命令 |
|
||||
| `kt_db_sync_plan` | 生成数据库同步安全向导 |
|
||||
| `kt_component_workflow` | 输出专项组件/链路防踩坑工作流 |
|
||||
| `kt_append_task_record` | 预览或写入 `TASKS.md` 改动记录 |
|
||||
| `kt_append_task_record` | 预览或写入 `TASKS.md` 最近记录 |
|
||||
| `kt_commit_checklist` | 生成提交前检查清单并校验 commit message |
|
||||
|
||||
## 可复用脚本
|
||||
@ -90,7 +90,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 复审报告。 |
|
||||
| `pnpm run global-review` | 对 KT 全部子仓库做只读全局 CodeReview 扫描,默认仅对变更文件做内容深扫,输出 JSON 复审报告。 |
|
||||
|
||||
## 项目别名
|
||||
|
||||
@ -122,10 +122,10 @@ pnpm run admin-login -- --url http://127.0.0.1:5999/#/auth/login
|
||||
- 要收尾时调用 `kt_finish_task`,默认只生成计划和 review;需要执行验证时显式传 `runValidation=true`。
|
||||
- 文件改动完成并验证后调用 `kt_global_code_review`,或运行 `pnpm run global-review`;它只读扫描,不删除文件、不提交代码。
|
||||
- 要提交或推送时先调用 `kt_commit_plan` / `kt_push_plan`,按仓库分组确认范围。
|
||||
- 要测真实业务链路时调用 `kt_business_test_plan`,选择 `admin-login`、`qqbot-auto-reply` 等 flow。
|
||||
- 要测真实业务链路时调用 `kt_business_test_plan`,选择 `admin-login`、`qqbot-auto-reply`、`qqbot-login-sse`、`fflogs-command`、`system-log-visualization` 等 flow。
|
||||
- 远程服务排查先调用 `kt_remote_health_check`,默认只生成只读命令;需要执行时显式传 `execute=true`。
|
||||
- 数据库同步前调用 `kt_db_sync_plan`,先确认源库、目标库、备份库和校验点。
|
||||
- 改 KtTable、BlogArgon、AdminAuth、QQBot、Knife4jSwagger 前调用 `kt_component_workflow`,先看专项禁区。
|
||||
- 改 KtTable、BlogArgon、AdminAuth、QQBot、FF14Plugin、NapCatLogin、SystemLog、Knife4jSwagger 前调用 `kt_component_workflow`,先看专项禁区。
|
||||
- 不确定任务边界时调用 `kt_guardrails`,先拿到“能做什么、不能做什么、怎么验证”。
|
||||
- 验证前调用 `kt_suggest_verification`,避免盲跑全量构建。
|
||||
- 页面测试前调用 `kt_create_page_test_case`,再执行 Playwright/浏览器测试。
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@kt/mcp-kt-workflow",
|
||||
"version": "0.5.0",
|
||||
"version": "0.6.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "Reusable MCP workflow tools for the KT workspace.",
|
||||
|
||||
@ -205,8 +205,9 @@ export function registerTools(server: McpServer): void {
|
||||
server.registerTool(
|
||||
'kt_global_code_review',
|
||||
{
|
||||
description: '对 KT 已知子仓库做只读全局 CodeReview 扫描:Git 状态、tracked env/deploy/secrets、冲突标记、调试输出、疑似凭据字面量和当前变更风险。',
|
||||
description: '对 KT 已知子仓库做只读全局 CodeReview 扫描:Git 状态、tracked env/deploy/secrets、冲突标记、调试输出、疑似凭据字面量和当前变更风险。默认只对变更文件做内容扫描,避免历史误报污染上下文。',
|
||||
inputSchema: {
|
||||
contentScanMode: z.enum(['changed', 'all']).default('changed'),
|
||||
includeContentScan: z.boolean().default(true),
|
||||
includeRootScan: z.boolean().default(true),
|
||||
maxFindingsPerProject: z.number().int().min(1).max(200).default(20),
|
||||
@ -224,6 +225,7 @@ export function registerTools(server: McpServer): void {
|
||||
inputSchema: {
|
||||
changeType: z.enum(taskTypeValues).default('general'),
|
||||
cleanupHistory: z.boolean().default(false),
|
||||
contentScanMode: z.enum(['changed', 'all']).default('changed'),
|
||||
includeRootScan: z.boolean().default(true),
|
||||
keepHistory: z.number().int().min(0).max(50).default(3),
|
||||
maxFindingsPerProject: z.number().int().min(1).max(200).default(20),
|
||||
@ -274,8 +276,11 @@ export function registerTools(server: McpServer): void {
|
||||
.enum([
|
||||
'admin-login',
|
||||
'blog-crud',
|
||||
'fflogs-command',
|
||||
'qqbot-account-scan',
|
||||
'qqbot-auto-reply',
|
||||
'qqbot-login-sse',
|
||||
'system-log-visualization',
|
||||
'web-playground-auth',
|
||||
'all',
|
||||
])
|
||||
@ -319,10 +324,19 @@ export function registerTools(server: McpServer): void {
|
||||
server.registerTool(
|
||||
'kt_component_workflow',
|
||||
{
|
||||
description: '按 KT 专项组件/链路输出防踩坑工作流:KtTable、BlogArgon、AdminAuth、QQBot、Knife4jSwagger。',
|
||||
description: '按 KT 专项组件/链路输出防踩坑工作流:KtTable、BlogArgon、AdminAuth、QQBot、FF14Plugin、NapCatLogin、SystemLog、Knife4jSwagger。',
|
||||
inputSchema: {
|
||||
target: z
|
||||
.enum(['AdminAuth', 'BlogArgon', 'Knife4jSwagger', 'KtTable', 'QQBot'])
|
||||
.enum([
|
||||
'AdminAuth',
|
||||
'BlogArgon',
|
||||
'FF14Plugin',
|
||||
'Knife4jSwagger',
|
||||
'KtTable',
|
||||
'NapCatLogin',
|
||||
'QQBot',
|
||||
'SystemLog',
|
||||
])
|
||||
.default('KtTable'),
|
||||
},
|
||||
title: 'KT Component Workflow',
|
||||
|
||||
@ -38,13 +38,13 @@ export async function runSelfTest(): Promise<void> {
|
||||
projects: ['mcp'],
|
||||
}),
|
||||
businessTestPlan: buildBusinessTestPlan({
|
||||
flow: 'qqbot-auto-reply',
|
||||
flow: 'system-log-visualization',
|
||||
}),
|
||||
commitPlan: await buildCommitPlan({
|
||||
projects: ['mcp'],
|
||||
}),
|
||||
componentWorkflow: buildComponentWorkflow({
|
||||
target: 'Knife4jSwagger',
|
||||
target: 'SystemLog',
|
||||
}),
|
||||
dbSyncPlan: buildDbSyncPlan({}),
|
||||
finishTask: await buildFinishTask({
|
||||
|
||||
@ -127,6 +127,51 @@ export async function scanTaskRisk(input: RiskScanInput): Promise<Record<string,
|
||||
addRisk('medium', file, '后端接口或业务逻辑改动。', '本地启动或复用服务后真实调用对应接口。');
|
||||
}
|
||||
|
||||
if (/src\/common\/decorators\/format-date-time\.decorator\.ts|src\/.*\.(entity|dto)\.ts/.test(normalized)) {
|
||||
addRisk(
|
||||
'medium',
|
||||
file,
|
||||
'后端时间字段或 DTO/Entity 响应结构改动。',
|
||||
'确认数据源经 DTO/Entity 实例返回,FormatDateTime 不做递归响应遍历,时间统一 YYYY-MM-DD HH:mm:ss。',
|
||||
);
|
||||
}
|
||||
|
||||
if (/src\/common\/logger\/|src\/admin\/system-log\/|pino|loki|system-log/i.test(normalized)) {
|
||||
addRisk(
|
||||
'medium',
|
||||
file,
|
||||
'Pino/Loki 或系统日志链路改动。',
|
||||
'验证 API 日志写入、/system/logs 系列接口、Admin 系统日志页面和 Loki ready/query_range。',
|
||||
);
|
||||
}
|
||||
|
||||
if (/src\/qqbot\/plugins\/(ff14Market|fflogs)\//i.test(normalized)) {
|
||||
addRisk(
|
||||
'medium',
|
||||
file,
|
||||
'FF14/FFLogs 命令插件改动。',
|
||||
'确认中文查询和回显走字典映射,命令注册表同步,真实命令输出字段完整。',
|
||||
);
|
||||
}
|
||||
|
||||
if (/src\/qqbot\/(account|napcat|connection)\//i.test(normalized)) {
|
||||
addRisk(
|
||||
'medium',
|
||||
file,
|
||||
'QQBot/NapCat 登录链路改动。',
|
||||
'验证新增账号和更新登录都能生成有效二维码,SSE 进度可见,旧 isLogin 状态不会短路。',
|
||||
);
|
||||
}
|
||||
|
||||
if (/apps\/web-antdv-next\/src\/views\/system\/(dict|log)\//.test(normalized)) {
|
||||
addRisk(
|
||||
'medium',
|
||||
file,
|
||||
'Admin 系统管理页改动。',
|
||||
'验证菜单权限、KtTable 展示、列表筛选和对应 API 响应结构。',
|
||||
);
|
||||
}
|
||||
|
||||
if (/Jenkinsfile|deploy\/|k8s\/|nginx.*\.conf/.test(normalized)) {
|
||||
addRisk('medium', file, '部署链路配置改动。', '只验证配置语法或生成产物,不主动触发远程部署。');
|
||||
}
|
||||
|
||||
@ -66,6 +66,7 @@ interface ReviewFinding {
|
||||
|
||||
interface ReviewProjectResult {
|
||||
changedFiles: string[];
|
||||
contentScannedFiles: string[];
|
||||
credentialHints: ReviewFinding[];
|
||||
debugFindings: ReviewFinding[];
|
||||
findings: ReviewFinding[];
|
||||
@ -109,11 +110,16 @@ function isBenignCredentialValue(value: string): boolean {
|
||||
if (!normalized) return true;
|
||||
if (normalized.startsWith('$') || normalized.startsWith('${')) return true;
|
||||
if (normalized.startsWith('<') || normalized.includes('example')) return true;
|
||||
if (/^\*+$/.test(normalized)) return true;
|
||||
return [
|
||||
'false',
|
||||
'hidden',
|
||||
'masked',
|
||||
'null',
|
||||
'password',
|
||||
'please-replace-me-with-your-own-key',
|
||||
'redacted',
|
||||
'replace-me',
|
||||
'secret',
|
||||
'token',
|
||||
'true',
|
||||
@ -138,6 +144,7 @@ function isCredentialHintAllowed(file: string, line: string): boolean {
|
||||
const normalized = file.replaceAll('\\', '/');
|
||||
const trimmed = line.trimStart();
|
||||
if (trimmed.startsWith('<')) return true;
|
||||
if (/\b(url|uri|dsn)\.password\s*=\s*['"]\*+['"]/.test(trimmed)) return true;
|
||||
return /(^|\/)views\/(demos|examples)\//.test(normalized);
|
||||
}
|
||||
|
||||
@ -274,7 +281,9 @@ function findRootGeneratedArtifacts(): string[] {
|
||||
|
||||
async function reviewProject(
|
||||
projectKey: string,
|
||||
input: Required<Pick<GlobalCodeReviewInput, 'includeContentScan' | 'maxFindingsPerProject'>>,
|
||||
input: Required<
|
||||
Pick<GlobalCodeReviewInput, 'contentScanMode' | 'includeContentScan' | 'maxFindingsPerProject'>
|
||||
>,
|
||||
): Promise<ReviewProjectResult> {
|
||||
const project = resolveProject(projectKey);
|
||||
const repoType = detectRepoType(project.path);
|
||||
@ -284,6 +293,14 @@ async function reviewProject(
|
||||
: { ok: false, stderr: '', stdout: '' };
|
||||
const changedFiles = status.ok ? parseGitStatusFiles(status.stdout) : [];
|
||||
const trackedFiles = repoType === 'git' ? await listGitTrackedFiles(project.path) : [];
|
||||
const trackedFileSet = new Set(trackedFiles);
|
||||
const contentScannedFiles =
|
||||
input.contentScanMode === 'all'
|
||||
? trackedFiles
|
||||
: changedFiles
|
||||
.map((file) => file.replaceAll('\\', '/'))
|
||||
.filter((file) => shouldReadReviewFile(file))
|
||||
.filter((file) => trackedFileSet.has(file) || existsSync(path.join(project.path, file)));
|
||||
const trackedSensitiveFiles = trackedFiles.filter(
|
||||
(file) =>
|
||||
sensitiveTrackedFilePattern.test(file) &&
|
||||
@ -291,7 +308,7 @@ async function reviewProject(
|
||||
);
|
||||
const contentFindings = collectProjectContentFindings(
|
||||
project,
|
||||
trackedFiles,
|
||||
contentScannedFiles,
|
||||
input.includeContentScan,
|
||||
input.maxFindingsPerProject,
|
||||
);
|
||||
@ -308,6 +325,7 @@ async function reviewProject(
|
||||
|
||||
return {
|
||||
changedFiles,
|
||||
contentScannedFiles,
|
||||
credentialHints: contentFindings.credentialHints,
|
||||
debugFindings: contentFindings.debugFindings,
|
||||
findings: [...trackedSensitiveFindings, ...contentFindings.findings],
|
||||
@ -330,6 +348,7 @@ export async function buildGlobalCodeReview(
|
||||
? input.projects!
|
||||
: [...defaultReviewProjects];
|
||||
const includeContentScan = input.includeContentScan ?? true;
|
||||
const contentScanMode = input.contentScanMode ?? 'changed';
|
||||
const maxFindingsPerProject = Math.max(
|
||||
1,
|
||||
Math.min(input.maxFindingsPerProject ?? 20, 200),
|
||||
@ -337,6 +356,7 @@ export async function buildGlobalCodeReview(
|
||||
const projectResults = await Promise.all(
|
||||
projects.map((project) =>
|
||||
reviewProject(project, {
|
||||
contentScanMode,
|
||||
includeContentScan,
|
||||
maxFindingsPerProject,
|
||||
}),
|
||||
@ -377,11 +397,13 @@ export async function buildGlobalCodeReview(
|
||||
notes: [
|
||||
'本工具只读扫描,不会修改文件、删除文件或提交代码。',
|
||||
'P1 优先处理:冲突标记、被跟踪的真实 env/部署凭据文件。',
|
||||
'默认 contentScanMode=changed,只对当前变更文件做 P2/P3 内容扫描,避免历史误报污染上下文;需要全仓库深扫时传 all。',
|
||||
'P2 需要人工确认:疑似凭据字面量、旧部署文件、调试断点。',
|
||||
'P3 为清洁度问题:运行时 console.log 等。',
|
||||
],
|
||||
projects: projectResults.map((item) => ({
|
||||
changedFiles: item.changedFiles,
|
||||
contentScannedFileCount: item.contentScannedFiles.length,
|
||||
credentialHintCount: item.credentialHints.length,
|
||||
debugFindingCount: item.debugFindings.length,
|
||||
findingCount: item.findings.length,
|
||||
@ -394,6 +416,7 @@ export async function buildGlobalCodeReview(
|
||||
summary: {
|
||||
changedProjectCount: projectResults.filter((item) => item.changedFiles.length > 0)
|
||||
.length,
|
||||
contentScanMode,
|
||||
findingCount: allFindings.length,
|
||||
p1: allFindings.filter((item) => item.level === 'P1').length,
|
||||
p2: allFindings.filter((item) => item.level === 'P2').length,
|
||||
|
||||
@ -38,7 +38,7 @@ export async function prepareTask(input: PrepareTaskInput): Promise<Record<strin
|
||||
'按最小范围修改。',
|
||||
'按 verification.commands 和 notes 做轻量验证。',
|
||||
'有文件改动时更新 TASKS.md。',
|
||||
'最终回复或提交前跑全局 review,并处理全部 findings。',
|
||||
'最终回复或提交前跑全局 review;默认只深扫变更文件,真实 findings 先处理。',
|
||||
],
|
||||
pageTestCase,
|
||||
project,
|
||||
@ -66,21 +66,22 @@ export function buildTaskRecord(input: TaskRecordInput): string {
|
||||
const date = input.date || formatDateInShanghai();
|
||||
const scope = (input.scope?.length ?? 0) > 0 ? input.scope!.join('、') : '待补充';
|
||||
const content = input.content || '待补充';
|
||||
const testCase = input.testCase || '本次无页面测试或待补充。';
|
||||
const verification = input.verification || '待补充。';
|
||||
|
||||
return `### ${date}:${input.title}\n\n- 范围:${scope}\n- 内容:${content}\n- 页面测试用例:${testCase}\n- 验证:${verification}\n`;
|
||||
const optionalTestCase = input.testCase ? `- 页面测试用例:${input.testCase}\n` : '';
|
||||
|
||||
return `### ${date}:${input.title}\n\n- 范围:${scope}\n- 关键词:${content}\n${optionalTestCase}- 验证:${verification}\n`;
|
||||
}
|
||||
|
||||
export function appendTaskRecord(input: TaskRecordInput): Record<string, unknown> {
|
||||
const record = buildTaskRecord(input);
|
||||
const tasksPath = resolveInsideRoot('TASKS.md');
|
||||
const current = readFileSync(tasksPath, 'utf8');
|
||||
const marker = '## 改动记录';
|
||||
const marker = current.includes('## 最近记录') ? '## 最近记录' : '## 改动记录';
|
||||
const markerIndex = current.indexOf(marker);
|
||||
|
||||
if (markerIndex === -1) {
|
||||
throw new Error('TASKS.md does not contain ## 改动记录');
|
||||
throw new Error('TASKS.md does not contain ## 最近记录 or ## 改动记录');
|
||||
}
|
||||
|
||||
const insertAt = current.indexOf('\n', markerIndex);
|
||||
|
||||
@ -102,6 +102,17 @@ export function buildBusinessTestPlan(input: BusinessTestPlanInput = {}): Record
|
||||
projects: ['api', 'admin', 'blog'],
|
||||
steps: ['登录 Admin', '新增分类/标签', '新增文章并绑定分类标签', '编辑后查询列表', '清理 KT_TEST_ 数据'],
|
||||
},
|
||||
'fflogs-command': {
|
||||
assertions: [
|
||||
'命令注册表和在线命令表都能看到 FFLogs 查询命令。',
|
||||
'中文任务名可查询,回显使用本地化任务名和字段名。',
|
||||
'最近 10 次 logs 返回颜色、输出评分、治疗评分、dps、adps、rdps、ndps、hps。',
|
||||
'FF14 查价和 FFLogs 映射优先来自字典表,不在插件代码硬编码。',
|
||||
],
|
||||
preflight: ['API 可达', 'FFLogs client id/secret 由环境变量注入', '字典表存在 FFLogs/FF14Market 映射项', 'QQBot 测试账号在线'],
|
||||
projects: ['api', 'admin'],
|
||||
steps: ['确认字典映射', '执行 /logs 角色 服务器 中文高难任务', '查询命令日志', '确认失败场景返回可读中文错误'],
|
||||
},
|
||||
'qqbot-account-scan': {
|
||||
assertions: [
|
||||
'扫码创建返回 qrcode/containerName/webuiPort。',
|
||||
@ -112,6 +123,17 @@ export function buildBusinessTestPlan(input: BusinessTestPlanInput = {}): Record
|
||||
projects: ['api', 'admin'],
|
||||
steps: ['打开账号连接页', '点击扫码新增账号', '轮询二维码状态', '确认账号回填', '测试删除联动容器'],
|
||||
},
|
||||
'qqbot-login-sse': {
|
||||
assertions: [
|
||||
'更新登录接口快速返回 pending session,不被前端 HTTP 超时截断。',
|
||||
'scan/events SSE 能看到重置登录态、生成二维码、等待扫码、成功或错误等步骤。',
|
||||
'账号被其他终端踢下线后,更新登录不复用旧二维码或旧 isLogin 状态。',
|
||||
'手动刷新二维码不超时,且不会无意义重启容器。',
|
||||
],
|
||||
preflight: ['API 可达', 'NapCat 容器可达', '账号处于离线或可重置状态', 'Admin QQBot 账号页面可访问'],
|
||||
projects: ['api', 'admin'],
|
||||
steps: ['触发更新登录', '订阅 scan/events', '观察二维码刷新', '扫码或等待过期', '核对账号状态和容器状态'],
|
||||
},
|
||||
'qqbot-auto-reply': {
|
||||
assertions: [
|
||||
'消息日志能看到 OneBot 收到的私聊/群聊/频道消息。',
|
||||
@ -122,6 +144,17 @@ export function buildBusinessTestPlan(input: BusinessTestPlanInput = {}): Record
|
||||
projects: ['api', 'admin'],
|
||||
steps: ['创建 KT_TEST_ 自动回复规则', '发送私聊测试消息', '查询消息日志', '查询发送日志', '清理测试规则'],
|
||||
},
|
||||
'system-log-visualization': {
|
||||
assertions: [
|
||||
'API pino 日志能写入 Loki 或在 Loki 不可用时不阻塞业务。',
|
||||
'/system/logs、/system/logs/summary、/system/logs/status 返回 Vben 兼容结构。',
|
||||
'Admin 系统日志页面能查询、筛选、刷新,控制台无 error。',
|
||||
'时间字段统一显示为 YYYY-MM-DD HH:mm:ss。',
|
||||
],
|
||||
preflight: ['API 可达', 'Loki /ready 返回 200 或降级状态明确', 'Admin 系统日志菜单可见', 'K8s Secret/Jenkins env 已注入 LOKI_*'],
|
||||
projects: ['api', 'admin'],
|
||||
steps: ['触发一条 API 日志', '查询 Loki query_range', '打开 Admin #/system/logs', '执行筛选刷新', '核对 summary/status'],
|
||||
},
|
||||
'web-playground-auth': {
|
||||
assertions: [
|
||||
'无 token 时不主动 refresh,不产生登录循环。',
|
||||
|
||||
@ -95,6 +95,7 @@ export async function buildFinishTask(input: FinishTaskInput = {}): Promise<Reco
|
||||
}
|
||||
|
||||
const review = await buildGlobalCodeReview({
|
||||
contentScanMode: input.contentScanMode ?? 'changed',
|
||||
includeContentScan: true,
|
||||
includeRootScan: input.includeRootScan ?? true,
|
||||
maxFindingsPerProject: input.maxFindingsPerProject ?? 20,
|
||||
@ -116,7 +117,7 @@ export async function buildFinishTask(input: FinishTaskInput = {}): Promise<Reco
|
||||
review,
|
||||
snapshots,
|
||||
stopConditions: [
|
||||
'review.findings 非空时先修复,再复跑 kt_global_code_review。',
|
||||
'review.findings 存在真实 P1/P2 时先修复,再复跑 kt_global_code_review。',
|
||||
'validationRuns 中任一 result.ok=false 时先处理失败命令。',
|
||||
'发现真实 env、生产 Secret 或非本次范围文件时停止提交。',
|
||||
],
|
||||
@ -139,7 +140,7 @@ export async function buildCommitPlan(input: CommitPlanInput = {}): Promise<Reco
|
||||
'每个独立 Git 仓库单独提交。',
|
||||
'提交前先跑对应轻量验证和 kt_global_code_review。',
|
||||
'后端真实 .env.development/.env.production 不提交;前端 env 可提交但只能包含客户端公开配置。',
|
||||
'根目录 AGENTS/SKILLS/TASKS 不属于子仓库,最终回复里单独说明。',
|
||||
'KT 根目录是本地治理仓库;如没有 remote,只本地提交并在最终回复说明。',
|
||||
],
|
||||
includePush: input.includePush ?? false,
|
||||
projects: changedSnapshots.map((snapshot) => {
|
||||
@ -313,10 +314,28 @@ export function buildComponentWorkflow(input: ComponentWorkflowInput = {}): Reco
|
||||
verification: ['横向滚动性能', '纵向滚动表头对齐', '列宽/行高 resize', 'summary 固定底部', '宽屏无无意义横向滚动条'],
|
||||
},
|
||||
QQBot: {
|
||||
antiPatterns: ['把 NapCat token 写进仓库', '删除账号不清理专属容器', '白名单/黑名单只做前端过滤', '远程服务不可达时伪装成业务成功'],
|
||||
antiPatterns: ['把 NapCat token 写进仓库', '删除账号不清理专属容器', '白名单/黑名单只做前端过滤', '远程服务不可达时伪装成业务成功', '更新登录时用旧 isLogin 状态短路'],
|
||||
files: ['Node/kt-template-online-api/src/qqbot', 'Vue/kt-template-admin/apps/web-antdv-next/src/views/qqbot', 'ci/fnos-k8s/deploy-qqbot-runtime.ps1'],
|
||||
guardrails: ['OneBot 反向 WS token 校验', '权限名单底层先走 QQ号过滤', '群/频道精确 QQ 过滤由开关控制', '扫码取消/过期/失败清理未绑定容器'],
|
||||
verification: ['账号在线', '扫码新增/更新登录', '自动回复规则命中', '权限名单三类过滤', '消息日志和发送日志闭环'],
|
||||
guardrails: ['OneBot 反向 WS token 校验', '权限名单底层先走 QQ号过滤', '群/频道精确 QQ 过滤由开关控制', '扫码取消/过期/失败清理未绑定容器', '更新登录走 SSE 进度,后台重置登录态后再生成二维码'],
|
||||
verification: ['账号在线', '扫码新增/更新登录', 'scan/events 每步进度', '自动回复规则命中', '权限名单三类过滤', '消息日志和发送日志闭环'],
|
||||
},
|
||||
FF14Plugin: {
|
||||
antiPatterns: ['把 boss/服务器/职业映射硬编码到插件里', 'FFLogs 英文任务名直接回显给中文用户', '外部 API 失败时吞掉错误上下文', '字典缺失时返回空白结果'],
|
||||
files: ['Node/kt-template-online-api/src/qqbot/plugins/ff14Market', 'Node/kt-template-online-api/src/qqbot/plugins/fflogs', 'Node/kt-template-online-api/src/admin/dict', 'Vue/kt-template-admin/apps/web-antdv-next/src/views/system/dict'],
|
||||
guardrails: ['FF14 查价和 FFLogs 映射优先走字典表维护', '中文任务名查询和回显都要经过字典', '命令注册表和在线命令列表同步验证', '保留最近 logs 的颜色/评分/dps/adps/rdps/ndps/hps 字段语义'],
|
||||
verification: ['/logs 角色 服务器 高难任务中文名', '/logs 角色 服务器 最近10次', 'FF14 查价中文物品名', '字典管理页面可维护映射'],
|
||||
},
|
||||
NapCatLogin: {
|
||||
antiPatterns: ['更新登录复用已过期二维码 URL', '前端等待长接口导致超时', '手动刷新每次重启容器', '后端拿旧 login state 判断成功'],
|
||||
files: ['Node/kt-template-online-api/src/qqbot/account', 'Node/kt-template-online-api/src/qqbot/napcat', 'Vue/kt-template-admin/apps/web-antdv-next/src/views/qqbot'],
|
||||
guardrails: ['创建账号和更新登录共用同一套二维码生成事实源', '更新登录先 reset runtime login state', 'SSE 返回 step/status/qrcode/error', '容器重启只在必要时触发,优先直接通信 NapCat API'],
|
||||
verification: ['账号被踢下线后点击更新登录', 'scan/events 能看到重置/生成二维码/等待扫码/成功或失败', '手动刷新不超时', '过期二维码不再复用'],
|
||||
},
|
||||
SystemLog: {
|
||||
antiPatterns: ['把真实 Loki 凭据写入仓库', '只改 Dockerfile 不校验线上 env/Secret', '日志时间格式前后端不一致', 'global-review 把历史脱敏占位符当作真实凭据输出'],
|
||||
files: ['Node/kt-template-online-api/src/common/logger', 'Node/kt-template-online-api/src/admin/system-log', 'Vue/kt-template-admin/apps/web-antdv-next/src/views/system/log', 'mcp/ktWorkflow/src/tools/review.ts'],
|
||||
guardrails: ['pino + Loki env 走 Jenkins 私有 env 或 K8s Secret', '后端时间字段经 DTO/Entity 装饰器统一 YYYY-MM-DD HH:mm:ss', '系统日志页面走 /system/logs 系列接口', 'review 默认只对变更文件做 P2/P3 内容扫描'],
|
||||
verification: ['API typecheck/lint/test', 'Loki /ready 和 query_range', 'Admin #/system/logs 无 console error', 'global-review findings 不含已知脱敏占位符误报'],
|
||||
},
|
||||
} satisfies Record<string, Record<string, unknown>>;
|
||||
|
||||
|
||||
15
src/types.ts
15
src/types.ts
@ -128,6 +128,7 @@ export interface RiskScanInput {
|
||||
}
|
||||
|
||||
export interface GlobalCodeReviewInput {
|
||||
contentScanMode?: 'all' | 'changed';
|
||||
includeContentScan?: boolean;
|
||||
includeRootScan?: boolean;
|
||||
maxFindingsPerProject?: number;
|
||||
@ -137,6 +138,7 @@ export interface GlobalCodeReviewInput {
|
||||
export interface FinishTaskInput {
|
||||
changeType?: TaskType;
|
||||
cleanupHistory?: boolean;
|
||||
contentScanMode?: 'all' | 'changed';
|
||||
includeRootScan?: boolean;
|
||||
keepHistory?: number;
|
||||
maxFindingsPerProject?: number;
|
||||
@ -159,8 +161,11 @@ export interface PushPlanInput {
|
||||
export type BusinessFlow =
|
||||
| 'admin-login'
|
||||
| 'blog-crud'
|
||||
| 'fflogs-command'
|
||||
| 'qqbot-account-scan'
|
||||
| 'qqbot-auto-reply'
|
||||
| 'qqbot-login-sse'
|
||||
| 'system-log-visualization'
|
||||
| 'web-playground-auth';
|
||||
|
||||
export interface BusinessTestPlanInput {
|
||||
@ -184,7 +189,15 @@ export interface DbSyncPlanInput {
|
||||
}
|
||||
|
||||
export interface ComponentWorkflowInput {
|
||||
target?: 'AdminAuth' | 'BlogArgon' | 'Knife4jSwagger' | 'KtTable' | 'QQBot';
|
||||
target?:
|
||||
| 'AdminAuth'
|
||||
| 'BlogArgon'
|
||||
| 'FF14Plugin'
|
||||
| 'Knife4jSwagger'
|
||||
| 'KtTable'
|
||||
| 'NapCatLogin'
|
||||
| 'QQBot'
|
||||
| 'SystemLog';
|
||||
}
|
||||
|
||||
export interface PrepareTaskInput extends GuardrailsInput {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user