diff --git a/README.md b/README.md index 43e9fe5..feedd3b 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,9 @@ - 检查子项目环境:Git/SVN、包管理器、Node 版本、env 文件、Git 状态。 - 生成防偏差工作包:把开工前检查、禁止项、风险扫描、验证计划汇总成一份可执行清单。 - 生成验证建议:按后端、前端、样式、页面、部署、数据库、MCP 等变更类型给出轻量验证命令和迁移核验要点。 -- 固化改动后 review:验证计划和提交清单都会提醒执行 `kt_global_code_review` / `pnpm run global-review`。 +- 固化改动后 review:验证计划和提交清单都会提醒执行 `kt_global_code_review` / `pnpm run global-review`;大方向收尾必须同时提供 Superpowers code review 证据。 - 生成任务收尾包:把状态、验证计划、可选验证执行、全局 review 和历史清理收成一份结果。 -- 生成大方向完结闭环审计:确认开发目标、测试证据、问题记录、稳定解法和 ktWorkflow 升级是否齐备。 +- 生成大方向完结闭环审计:确认开发目标、测试证据、KT 全局 review、Superpowers code review、问题记录、稳定解法和 ktWorkflow 升级是否齐备。 - 生成页面测试用例:内置“先写用例、可视化证据、事不过三”的测试闭环。 - 生成接口测试计划:接口改动后输出真实调用命令和统一返回结构断言。 - 生成业务链路测试计划:固化 Admin 登录、博客 CRUD、QQBot 扫码/自动回复、更新登录 SSE、FFLogs 命令、系统日志可视化、Web/Playground 回跳。 @@ -37,7 +37,7 @@ pnpm run self-test pnpm run obsidian-context -- --module ktWorkflow pnpm run obsidian-validate pnpm run obsidian-sync -pnpm run workstream-closeout -- --title "发布闭环" --verification "Jenkins SUCCESS" --doc-sync "无需文档更新" --cleanup "cleanup-history dry-run deleted=0" --cleanup-final-deleted 0 --review "global-review findings=0" --problem "无新卡点" --solution "无新增稳定解法" +pnpm run workstream-closeout -- --title "发布闭环" --verification "Jenkins SUCCESS" --doc-sync "无需文档更新" --cleanup "cleanup-history dry-run deleted=0" --cleanup-final-deleted 0 --review "global-review findings=0" --superpowers-review "Superpowers reviewer completed; no Critical/Important findings" --problem "无新卡点" --solution "无新增稳定解法" pnpm run cleanup-history -- --dry-run pnpm run cleanup-history -- --execute pnpm run admin-login -- --url http://127.0.0.1:5999/#/auth/login @@ -85,8 +85,8 @@ pnpm run admin-login -- --url http://127.0.0.1:5999/#/auth/login | `kt_risk_scan` | 扫描当前或传入变更文件的偏差风险 | | `kt_global_code_review` | 对 KT 全部子仓库做只读全局 CodeReview 扫描,默认仅深扫变更文件 | | `kt_finish_task` | 生成任务收尾包,可选执行验证和历史清理 | -| `kt_workstream_closeout` | 生成大方向完结闭环审计,检查测试证据、文档同步、历史清理、全局 review、问题记录、稳定解法和 ktWorkflow 升级 | -| `kt_workflow_loop_audit` | 审计上下文锚定、测试证据、文档同步、历史清理最终 `deleted=0`、问题固化、ktWorkflow 升级和全局 review | +| `kt_workstream_closeout` | 生成大方向完结闭环审计,检查测试证据、文档同步、历史清理、KT 全局 review、Superpowers code review、问题记录、稳定解法和 ktWorkflow 升级 | +| `kt_workflow_loop_audit` | 审计上下文锚定、测试证据、文档同步、历史清理最终 `deleted=0`、问题固化、ktWorkflow 升级、KT 全局 review 和 Superpowers code review | | `kt_change_doc_sync` | 根据改动路径生成 README/API/AGENTS/docs/Obsidian/skill/ktWorkflow 文档同步清单 | | `kt_commit_plan` | 生成多仓库提交计划、建议 commit message 和检查项 | | `kt_push_plan` | 生成多仓库推送计划和远程异常提醒 | @@ -111,7 +111,7 @@ pnpm run admin-login -- --url http://127.0.0.1:5999/#/auth/login | `pnpm run obsidian-validate` | 校验 KT Obsidian vault 结构和链接;默认 warning 不让脚本失败,需要严格模式时传 `--fail-on-warnings`。 | | `pnpm run obsidian-sync` | 审计 Obsidian 工作流入口是否连通,并联动执行 validate。 | | `pnpm run cleanup-history` | 预览 `.kt-workspace` 测试/验证历史产物清理;真实清理用 `pnpm run cleanup-history -- --execute`,只保留最近 3 轮并保留模板目录。 | -| `pnpm run workstream-closeout` | 生成大方向完结闭环审计;识别测试证据、文档同步、历史清理、全局 review、问题记录、稳定解法和 ktWorkflow 升级缺口。 | +| `pnpm run workstream-closeout` | 生成大方向完结闭环审计;识别测试证据、文档同步、历史清理、KT 全局 review、Superpowers code review、问题记录、稳定解法和 ktWorkflow 升级缺口。 | ## 项目别名 @@ -143,10 +143,10 @@ pnpm run admin-login -- --url http://127.0.0.1:5999/#/auth/login - 需要按知识图谱定位上下文时先调用 `kt_obsidian_context`,例如 `module=Admin` 或 `query=BangDream`;Obsidian 只做文档索引,返回的 `ruleAnchors` 用来读取相关规则文档,`codeAnchors` 必须继续搭配 `kt_inspect_project`、`kt_risk_scan`、`kt_suggest_verification` 锚定真实代码上下文。 - 多项目联动时先调用 `kt_inspect_all_projects`。 - 要收尾时调用 `kt_finish_task`,默认只生成计划和 review;需要执行验证时显式传 `runValidation=true`。 -- 大方向结束前调用 `kt_workstream_closeout` 或 `pnpm run workstream-closeout`;如果识别到测试流、卡点、误报、清理、部署观测或命令模板,要先升级对应 ktWorkflow 规则再报告完成。 +- 大方向结束前调用 `kt_workstream_closeout` 或 `pnpm run workstream-closeout`;必须分别传入 `--review` 的 KT 全局审查证据和 `--superpowers-review` 的 Superpowers code review 证据;如果识别到测试流、卡点、误报、清理、部署观测或命令模板,要先升级对应 ktWorkflow 规则再报告完成。 - 文件改动完成并验证后调用 `kt_global_code_review`,或运行 `pnpm run global-review`;它只读扫描,不删除文件、不提交代码。`findings` 先判定真实风险或工具误报,真实风险修业务代码,误报修 `mcp/ktWorkflow/src/tools/review.ts` 并复跑。 - 代码或配置改动后调用 `kt_change_doc_sync`,把需要同步的 README/API/AGENTS/docs/Obsidian/skill/ktWorkflow 入口补齐;无需同步时把原因写进收尾证据。 -- 报告非平凡任务完成前调用 `kt_workflow_loop_audit`,确认测试证据、文档同步、历史清理最终 `deleted=0`、问题固化、ktWorkflow 升级和全局 review 都过门;清理证据必须显式传入,不能依赖缺省值。 +- 报告非平凡任务完成前调用 `kt_workflow_loop_audit`,确认测试证据、文档同步、历史清理最终 `deleted=0`、问题固化、ktWorkflow 升级、KT 全局 review 和 Superpowers code review 都过门;清理证据必须显式传入,不能依赖缺省值。 - 要提交或推送时先调用 `kt_commit_plan` / `kt_push_plan`,按仓库分组确认范围。 - 要测真实业务链路时调用 `kt_business_test_plan`,选择 `admin-login`、`qqbot-auto-reply`、`qqbot-login-sse`、`fflogs-command`、`system-log-visualization` 等 flow。 - 遇到同一命令或同一远程步骤重复卡住时调用 `kt_blocker_resolution`;第二次仍失败时先写入 `TASKS.md` 或补成脚本,再继续。 diff --git a/src/core/cli.ts b/src/core/cli.ts index 5a11197..9f505cd 100644 --- a/src/core/cli.ts +++ b/src/core/cli.ts @@ -79,6 +79,11 @@ export function parseWorkstreamCloseoutCliArgs(argv: string[]): WorkstreamCloseo reviewEvidence: readOptionList(argv, ['--review', '--review-evidence']), reusablePatterns: patterns, stableSolutions: readOptionList(argv, ['--solution', '--stable-solution']), + superpowersReviewEvidence: readOptionList(argv, [ + '--superpowers-review', + '--superpowers-review-evidence', + '--sp-review', + ]), title: readOption(argv, ['--title']), upgradeNotes: readOption(argv, ['--upgrade-notes']), verificationEvidence: readOptionList(argv, ['--verification', '--verification-evidence']), diff --git a/src/registerTools.ts b/src/registerTools.ts index 7e1a483..aa9e2a9 100644 --- a/src/registerTools.ts +++ b/src/registerTools.ts @@ -246,7 +246,7 @@ export function registerTools(server: McpServer): void { server.registerTool( 'kt_workstream_closeout', { - description: '生成大方向完结闭环审计:开发、测试证据、问题记录、稳定解法和 ktWorkflow 升级是否齐备。', + description: '生成大方向完结闭环审计:开发、测试证据、KT 全局 review、Superpowers code review、问题记录、稳定解法和 ktWorkflow 升级是否齐备。', inputSchema: { changedProjects: z.array(z.string()).default([]), cleanupEvidence: z.array(z.string()).default([]), @@ -257,6 +257,7 @@ export function registerTools(server: McpServer): void { reviewEvidence: z.array(z.string()).default([]), reusablePatterns: z.array(z.enum(workstreamReusablePatternValues)).default(['none']), stableSolutions: z.array(z.string()).default([]), + superpowersReviewEvidence: z.array(z.string()).default([]), title: z.string().optional(), upgradeNotes: z.string().optional(), verificationEvidence: z.array(z.string()).default([]), @@ -269,7 +270,7 @@ export function registerTools(server: McpServer): void { server.registerTool( 'kt_workflow_loop_audit', { - description: '审计 KT 自动化闭环:上下文锚定、测试用例、验证证据、历史清理、问题固化、ktWorkflow 升级和全局 review 是否齐备。', + description: '审计 KT 自动化闭环:上下文锚定、测试用例、验证证据、历史清理、问题固化、ktWorkflow 升级、KT 全局 review 和 Superpowers code review 是否齐备。', inputSchema: { changedProjects: z.array(z.string()).default([]), cleanupExecuted: z.boolean().default(false), @@ -285,6 +286,7 @@ export function registerTools(server: McpServer): void { reviewEvidence: z.array(z.string()).default([]), stableSolutions: z.array(z.string()).default([]), stage: z.enum(['failure', 'finish', 'start', 'test']).default('finish'), + superpowersReviewEvidence: z.array(z.string()).default([]), taskTitle: z.string().optional(), testCases: z.array(z.string()).default([]), verificationEvidence: z.array(z.string()).default([]), diff --git a/src/selfTest.ts b/src/selfTest.ts index d04b31e..48604fe 100644 --- a/src/selfTest.ts +++ b/src/selfTest.ts @@ -1,7 +1,10 @@ import { mkdirSync, writeFileSync } from "node:fs"; import path from "node:path"; -import { parseGlobalReviewCliArgs } from "./core/cli.js"; +import { + parseGlobalReviewCliArgs, + parseWorkstreamCloseoutCliArgs, +} from "./core/cli.js"; import { registeredToolNames } from "./core/constants.js"; import { buildBlockerResolution } from "./tools/blocker.js"; import { buildChangeDocSync } from "./tools/docSync.js"; @@ -135,6 +138,23 @@ export async function runSelfTest(): Promise { ); } + const closeoutCliParser = parseWorkstreamCloseoutCliArgs([ + "node", + "server", + "--workstream-closeout", + "--review", + "global-review findings=0", + "--superpowers-review", + "Superpowers reviewer completed", + ]); + if ( + closeoutCliParser.reviewEvidence?.[0] !== "global-review findings=0" || + closeoutCliParser.superpowersReviewEvidence?.[0] !== + "Superpowers reviewer completed" + ) { + throw new Error("workstream closeout review CLI parser self-check failed"); + } + const refactorGuardrails = buildGuardrails({ project: "mcp", taskType: "refactor", @@ -197,6 +217,7 @@ export async function runSelfTest(): Promise { stableSolutions: [ "收尾时固定观测 build、commit、镜像、Deployment、Pod 和 smoke。", ], + superpowersReviewEvidence: ["Superpowers reviewer completed; Important findings handled。"], title: "发布闭环", verificationEvidence: ["Jenkins #132 SUCCESS,K8s Pod Running。"], }); @@ -212,6 +233,7 @@ export async function runSelfTest(): Promise { reviewEvidence: ["global-review findings=0。"], reusablePatterns: ["none"], stableSolutions: ["无新增稳定解法。"], + superpowersReviewEvidence: ["Superpowers reviewer completed; no Critical/Important findings。"], title: "文档收尾", verificationEvidence: ["typecheck/self-test/global-review 通过。"], }); @@ -226,6 +248,7 @@ export async function runSelfTest(): Promise { reviewEvidence: ["global-review findings=0。"], reusablePatterns: ["none"], stableSolutions: ["无新增稳定解法。"], + superpowersReviewEvidence: ["Superpowers reviewer completed。"], title: "无效清理证据", verificationEvidence: ["typecheck 通过。"], }); @@ -233,6 +256,20 @@ export async function runSelfTest(): Promise { throw new Error("workstream closeout cleanup evidence self-check failed"); } + const closeoutMissingSuperpowersReview = buildWorkstreamCloseout({ + cleanupFinalDeleted: 0, + docSyncEvidence: ["无需文档更新,已记录原因。"], + problemRecords: ["无新卡点。"], + reviewEvidence: ["global-review findings=0。"], + reusablePatterns: ["none"], + stableSolutions: ["无新增稳定解法。"], + title: "缺 Superpowers 审查证据", + verificationEvidence: ["typecheck 通过。"], + }); + if ((closeoutMissingSuperpowersReview.canReportComplete as boolean) !== false) { + throw new Error("workstream closeout Superpowers review gate self-check failed"); + } + const obsidianContext = readObsidianContext({ maxDocuments: 6, module: "ktWorkflow", @@ -319,6 +356,7 @@ export async function runSelfTest(): Promise { stableSolutions: [ "cleanup-history 默认 dry-run,显式 --execute 后复验 deleted=0。", ], + superpowersReviewEvidence: ["Superpowers reviewer completed; no Critical/Important findings"], taskTitle: "自动化闭环审计", testCases: ["cleanup-history dry-run/execute 回归测试"], verificationEvidence: ["typecheck/self-test/cleanup dry-run 通过"], @@ -338,6 +376,7 @@ export async function runSelfTest(): Promise { const loopAuditMissingCleanup = buildWorkflowLoopAudit({ docSyncEvidence: ["kt_change_doc_sync requiredDocs 已处理。"], reviewEvidence: ["global-review findings=0。"], + superpowersReviewEvidence: ["Superpowers reviewer completed。"], taskTitle: "缺清理证据闭环", testCases: ["self-test"], verificationEvidence: ["typecheck 通过。"], diff --git a/src/tools/closeout.ts b/src/tools/closeout.ts index 06ca875..53bf460 100644 --- a/src/tools/closeout.ts +++ b/src/tools/closeout.ts @@ -82,6 +82,7 @@ export function buildWorkstreamCloseout(input: WorkstreamCloseoutInput = {}): Re const problemRecords = normalizeList(input.problemRecords); const reviewEvidence = normalizeList(input.reviewEvidence); const stableSolutions = normalizeList(input.stableSolutions); + const superpowersReviewEvidence = normalizeList(input.superpowersReviewEvidence); const patterns = normalizePatterns(input.reusablePatterns); const upgradeNeeded = patterns.some((pattern) => pattern !== 'none'); const ktWorkflowUpdated = input.ktWorkflowUpdated === true; @@ -90,7 +91,8 @@ export function buildWorkstreamCloseout(input: WorkstreamCloseoutInput = {}): Re verificationEvidence.length > 0 ? '' : '补充测试或 smoke 验证证据;只有计划不算闭环完成。', docSyncEvidence.length > 0 ? '' : '补充文档同步证据;无需更新时也要显式写明原因。', cleanupOk ? '' : '补充历史产物清理证据;至少包含 cleanup-history dry-run deleted=0 或结构化 cleanupFinalDeleted=0。', - reviewEvidence.length > 0 ? '' : '补充全局 review 证据。', + reviewEvidence.length > 0 ? '' : '补充 KT 全局 review 证据。', + superpowersReviewEvidence.length > 0 ? '' : '补充 Superpowers code review 证据。', problemRecords.length > 0 ? '' : '补充问题记录;如无新卡点,显式写“无新卡点”。', stableSolutions.length > 0 ? '' : '补充稳定解法;如无新解法,显式写“无新增稳定解法”。', upgradeNeeded && !ktWorkflowUpdated @@ -122,7 +124,8 @@ export function buildWorkstreamCloseout(input: WorkstreamCloseoutInput = {}): Re ? undefined : '补充 cleanup-history dry-run deleted=0 证据或传入 cleanupFinalDeleted=0。', }, - buildPhase('global review', reviewEvidence, '补充 kt_global_code_review 或 global-review 证据。'), + buildPhase('KT global review', reviewEvidence, '补充 kt_global_code_review 或 global-review 证据。'), + buildPhase('Superpowers code review', superpowersReviewEvidence, '补充 Superpowers reviewer 创建/完成/关闭和处理结论证据。'), buildPhase('problem record', problemRecords, '记录问题点;没有问题也要显式写明。'), buildPhase('stable solution', stableSolutions, '记录稳定解法;没有新增解法也要显式写明。'), { @@ -142,6 +145,7 @@ export function buildWorkstreamCloseout(input: WorkstreamCloseoutInput = {}): Re 'pnpm run typecheck', 'pnpm run self-test', 'pnpm run global-review', + 'pnpm run workstream-closeout -- --superpowers-review "Superpowers reviewer completed; no Critical/Important findings"', 'git diff --check', ], missingItems, diff --git a/src/tools/loop.ts b/src/tools/loop.ts index 58c75de..0d2f251 100644 --- a/src/tools/loop.ts +++ b/src/tools/loop.ts @@ -44,6 +44,7 @@ export function buildWorkflowLoopAudit(input: WorkflowLoopAuditInput = {}): Reco const problemRecords = normalizeList(input.problemRecords); const stableSolutions = normalizeList(input.stableSolutions); const reviewEvidence = normalizeList(input.reviewEvidence); + const superpowersReviewEvidence = normalizeList(input.superpowersReviewEvidence); const cleanupEvidence = normalizeList(input.cleanupEvidence); const cleanupPreviewDeleted = normalizeCleanupDeletedCount( input.cleanupPreviewDeleted, @@ -86,7 +87,8 @@ export function buildWorkflowLoopAudit(input: WorkflowLoopAuditInput = {}): Reco '执行 kt_cleanup_history dryRun=false 或 pnpm run cleanup-history -- --execute,并复跑 dry-run 确认 deleted=0。', ), buildRequiredGate('documentation sync evidence', docSyncEvidence, '运行 kt_change_doc_sync 并同步 README/API/AGENTS/docs/Obsidian/skills/ktWorkflow 中需要更新的入口;无需更新时写明原因。'), - buildRequiredGate('global review evidence', reviewEvidence, '文件改动后跑 kt_global_code_review 或 pnpm run global-review。'), + buildRequiredGate('KT global review evidence', reviewEvidence, '文件改动后跑 kt_global_code_review 或 pnpm run global-review。'), + buildRequiredGate('Superpowers code review evidence', superpowersReviewEvidence, '非平凡改动后按 Superpowers requesting-code-review 创建 reviewer,并记录完成/处理/关闭证据。'), ]; if (needsSolidification) { @@ -123,7 +125,7 @@ export function buildWorkflowLoopAudit(input: WorkflowLoopAuditInput = {}): Reco 'pnpm --dir mcp/ktWorkflow run cleanup-history -- --dry-run', 'pnpm --dir mcp/ktWorkflow run cleanup-history -- --execute', 'pnpm --dir mcp/ktWorkflow run global-review', - 'pnpm --dir mcp/ktWorkflow run workstream-closeout -- --title "<目标>" --verification "<证据>" --problem "<问题记录>" --solution "<稳定解法>"', + 'pnpm --dir mcp/ktWorkflow run workstream-closeout -- --title "<目标>" --verification "<证据>" --review "" --superpowers-review "" --problem "<问题记录>" --solution "<稳定解法>"', ], gates, loops: { @@ -164,6 +166,7 @@ export function buildWorkflowLoopAudit(input: WorkflowLoopAuditInput = {}): Reco reusablePattern, reviewEvidenceCount: reviewEvidence.length, stableSolutionCount: stableSolutions.length, + superpowersReviewEvidenceCount: superpowersReviewEvidence.length, testCaseCount: testCases.length, verificationEvidenceCount: verificationEvidence.length, }, diff --git a/src/tools/workflow.ts b/src/tools/workflow.ts index a7ce103..eb9887e 100644 --- a/src/tools/workflow.ts +++ b/src/tools/workflow.ts @@ -1,12 +1,31 @@ -import type { CommitPlanInput, ComponentWorkflowInput, DbSyncPlanInput, ExecResult, FinishTaskInput, PushPlanInput, RemoteHealthCheckInput, RepoType, ResolvedProject } from '../types.js'; -import { tryExecFile, tryPowerShell } from '../core/exec.js'; -import { detectRepoType, parseGitStatusFiles } from '../core/project.js'; -import { 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'; -import { buildWorkstreamCloseout } from './closeout.js'; +import type { + CommitPlanInput, + ComponentWorkflowInput, + DbSyncPlanInput, + ExecResult, + FinishTaskInput, + PushPlanInput, + RemoteHealthCheckInput, + RepoType, + ResolvedProject, +} from "../types.js"; +import { tryExecFile, tryPowerShell } from "../core/exec.js"; +import { detectRepoType, parseGitStatusFiles } from "../core/project.js"; +import { + 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"; +import { buildWorkstreamCloseout } from "./closeout.js"; interface RepoSnapshot { branch: string; changedFiles: string[]; @@ -17,83 +36,100 @@ interface RepoSnapshot { } function normalizeProjectKeys(projects?: string[]): string[] { - const selected = (projects?.length ?? 0) > 0 ? projects! : [...defaultReviewProjects]; - return Array.from(new Set(selected.filter((project) => project !== 'root'))); + const selected = + (projects?.length ?? 0) > 0 ? projects! : [...defaultReviewProjects]; + return Array.from(new Set(selected.filter((project) => project !== "root"))); } function formatShanghaiTimestamp(date = new Date()): string { - const parts = new Intl.DateTimeFormat('en-CA', { - day: '2-digit', - hour: '2-digit', + const parts = new Intl.DateTimeFormat("en-CA", { + day: "2-digit", + hour: "2-digit", hour12: false, - minute: '2-digit', - month: '2-digit', - second: '2-digit', - timeZone: 'Asia/Shanghai', - year: 'numeric', + minute: "2-digit", + month: "2-digit", + second: "2-digit", + timeZone: "Asia/Shanghai", + year: "numeric", }).formatToParts(date); const map = Object.fromEntries(parts.map((part) => [part.type, part.value])); return `${map.year}${map.month}${map.day}_${map.hour}${map.minute}${map.second}`; } function normalizeDbIdentifier(value: string): string { - return value.replace(/[^a-zA-Z0-9_]/g, '_').replace(/_+/g, '_').slice(0, 64); + return value + .replace(/[^a-zA-Z0-9_]/g, "_") + .replace(/_+/g, "_") + .slice(0, 64); } async function collectRepoSnapshot(projectKey: string): Promise { const project = resolveProject(projectKey); const repoType = detectRepoType(project.path); - const status = repoType === 'git' - ? await tryExecFile('git', ['status', '--short'], project.path) - : null; - const branch = repoType === 'git' - ? await tryExecFile('git', ['branch', '--show-current'], project.path) - : null; - const remote = repoType === 'git' - ? await tryExecFile('git', ['remote', '-v'], project.path) - : null; + const status = + repoType === "git" + ? await tryExecFile("git", ["status", "--short"], project.path) + : null; + const branch = + repoType === "git" + ? await tryExecFile("git", ["branch", "--show-current"], project.path) + : null; + const remote = + repoType === "git" + ? await tryExecFile("git", ["remote", "-v"], project.path) + : null; return { - branch: branch?.ok ? branch.stdout : '', + branch: branch?.ok ? branch.stdout : "", changedFiles: status?.ok ? parseGitStatusFiles(status.stdout) : [], project, - remote: remote?.ok ? remote.stdout : '', + remote: remote?.ok ? remote.stdout : "", repoType, - status: status?.ok ? status.stdout : status?.stderr || '', + status: status?.ok ? status.stdout : status?.stderr || "", }; } function suggestCommitMessage(snapshot: RepoSnapshot): string { - const files = snapshot.changedFiles.join('\n'); + const files = snapshot.changedFiles.join("\n"); - if (snapshot.project.alias === 'mcp') { - return 'feat(workflow): 扩展KT工作流能力'; + if (snapshot.project.alias === "mcp") { + return "feat(workflow): 扩展KT工作流能力"; } - if (snapshot.project.alias === 'api' && /qqbot/i.test(files)) { - return 'fix(qqbot): 修复QQBot运行链路'; + if (snapshot.project.alias === "api" && /qqbot/i.test(files)) { + return "fix(qqbot): 修复QQBot运行链路"; } - if (snapshot.project.alias === 'admin' && /\.env|authentication/.test(files)) { - return 'fix(admin): 收敛环境配置和登录页'; + if ( + snapshot.project.alias === "admin" && + /\.env|authentication/.test(files) + ) { + return "fix(admin): 收敛环境配置和登录页"; } - if (snapshot.project.alias === 'web' && /deploy\.json|\.gitignore/.test(files)) { - return 'chore(web): 清理旧部署配置'; + if ( + snapshot.project.alias === "web" && + /deploy\.json|\.gitignore/.test(files) + ) { + return "chore(web): 清理旧部署配置"; } - if (snapshot.project.alias === 'blog') { - return 'fix(blog): 优化博客前台体验'; + if (snapshot.project.alias === "blog") { + return "fix(blog): 优化博客前台体验"; } - if (snapshot.project.alias === 'knife4j') { - return 'feat(knife4j): 升级接口文档插件能力'; + if (snapshot.project.alias === "knife4j") { + return "feat(knife4j): 升级接口文档插件能力"; } - return `chore(${snapshot.project.alias || 'repo'}): 更新项目改动`; + return `chore(${snapshot.project.alias || "repo"}): 更新项目改动`; } -export async function buildFinishTask(input: FinishTaskInput = {}): Promise> { +export async function buildFinishTask( + input: FinishTaskInput = {}, +): Promise> { const projectKeys = normalizeProjectKeys(input.projects); const snapshots = await Promise.all(projectKeys.map(collectRepoSnapshot)); - const changedSnapshots = snapshots.filter((snapshot) => snapshot.changedFiles.length > 0); + const changedSnapshots = snapshots.filter( + (snapshot) => snapshot.changedFiles.length > 0, + ); const verificationPlans = changedSnapshots.map((snapshot) => buildVerificationPlan({ - changeType: input.changeType || 'general', + changeType: input.changeType || "general", project: snapshot.project.alias || snapshot.project.relativePath, }), ); @@ -105,7 +141,9 @@ export async function buildFinishTask(input: FinishTaskInput = {}): Promise !item.includes('global-review'))) { + for (const command of plan.commands.filter( + (item) => !item.includes("global-review"), + )) { validationRuns.push({ command, project: plan.project, @@ -116,7 +154,7 @@ export async function buildFinishTask(input: FinishTaskInput = {}): Promise snapshot.project.relativePath), + changedProjects: changedSnapshots.map( + (snapshot) => snapshot.project.relativePath, + ), problemRecords: [], - reusablePatterns: ['none'], + reusablePatterns: ["none"], stableSolutions: [], - title: 'kt_finish_task 收尾审计', + title: "kt_finish_task 收尾审计", verificationEvidence: validationRuns .filter((item) => item.result.ok) .map((item) => `${item.project.relativePath}: ${item.command}`), }), - mode: input.runValidation ? 'validation_executed' : 'plan_only', + mode: input.runValidation ? "validation_executed" : "plan_only", notes: [ - '默认只生成收尾结果;runValidation=true 时才执行验证命令。', - 'cleanupHistory=true 时才真实清理历史产物,否则仅预览;预览中 deleted>0 说明测试/验证历史还未满足最近 3 轮规则。', - '提交和推送仍需用户明确要求后执行。', + "默认只生成收尾结果;runValidation=true 时才执行验证命令。", + "cleanupHistory=true 时才真实清理历史产物,否则仅预览;预览中 deleted>0 说明测试/验证历史还未满足最近 3 轮规则。", + "提交和推送仍需用户明确要求后执行。", karpathyFinishNote, ], review, snapshots, stopConditions: [ - 'review.findings 存在 P1/P2 时先判定真实风险或工具误报;真实风险修代码,工具误报修 ktWorkflow,再复跑 kt_global_code_review。', - 'validationRuns 中任一 result.ok=false 时先处理失败命令。', - 'cleanup.dryRun=true 且 cleanup.deleted 非空时,先执行 kt_cleanup_history dryRun=false 或 pnpm run cleanup-history -- --execute,再报告测试/验证历史已合规。', - '发现真实 env、生产 Secret 或非本次范围文件时停止提交。', + "review.findings 存在 P1/P2 时先判定真实风险或工具误报;真实风险修代码,工具误报修 ktWorkflow,再复跑 kt_global_code_review。", + "validationRuns 中任一 result.ok=false 时先处理失败命令。", + "cleanup.dryRun=true 且 cleanup.deleted 非空时,先执行 kt_cleanup_history dryRun=false 或 pnpm run cleanup-history -- --execute,再报告测试/验证历史已合规。", + "发现真实 env、生产 Secret 或非本次范围文件时停止提交。", karpathyStopCondition, ], summary: { changedProjectCount: changedSnapshots.length, - changedProjects: changedSnapshots.map((snapshot) => snapshot.project.relativePath), + changedProjects: changedSnapshots.map( + (snapshot) => snapshot.project.relativePath, + ), cleanupPendingCount, validationRunCount: validationRuns.length, }, @@ -168,18 +210,24 @@ export async function buildFinishTask(input: FinishTaskInput = {}): Promise> { - const snapshots = await Promise.all(normalizeProjectKeys(input.projects).map(collectRepoSnapshot)); - const changedSnapshots = snapshots.filter((snapshot) => snapshot.changedFiles.length > 0); +export async function buildCommitPlan( + input: CommitPlanInput = {}, +): Promise> { + const snapshots = await Promise.all( + normalizeProjectKeys(input.projects).map(collectRepoSnapshot), + ); + const changedSnapshots = snapshots.filter( + (snapshot) => snapshot.changedFiles.length > 0, + ); return { checks: [ - '每个独立 Git 仓库单独提交。', + "每个独立 Git 仓库单独提交。", karpathyChangedFileChecklistItem, - '提交前先跑对应轻量验证和 kt_global_code_review。', - 'kt_global_code_review findings 必须为 0;确认误报时先修 ktWorkflow 规则,不把误报当作长期上下文。', - '后端真实 .env.development/.env.production 不提交;前端 env 可提交但只能包含客户端公开配置。', - 'KT 根目录是本地治理仓库;如没有 remote,只本地提交并在最终回复说明。', + "提交前先跑对应轻量验证和 kt_global_code_review。", + "kt_global_code_review findings 必须为 0;确认误报时先修 ktWorkflow 规则,不把误报当作长期上下文。", + "后端真实 .env.development/.env.production 不提交;前端 env 可提交但只能包含客户端公开配置。", + "KT 根目录是本地治理仓库;如没有 remote,只本地提交并在最终回复说明。", ], includePush: input.includePush ?? false, projects: changedSnapshots.map((snapshot) => { @@ -188,10 +236,12 @@ export async function buildCommitPlan(input: CommitPlanInput = {}): Promise> { - const snapshots = await Promise.all(normalizeProjectKeys(input.projects).map(collectRepoSnapshot)); - const remote = input.remote || 'origin'; +export async function buildPushPlan( + input: PushPlanInput = {}, +): Promise> { + const snapshots = await Promise.all( + normalizeProjectKeys(input.projects).map(collectRepoSnapshot), + ); + const remote = input.remote || "origin"; return { checks: [ - '推送前工作区应干净;如仍有改动,先提交或说明为什么暂不提交。', - '推送前复跑 kt_global_code_review,findings 必须为 0;如出现误报先升级 ktWorkflow 规则。', - '如果 Jenkins/Gitea host key、mirror、hooks 异常,先修远程仓库状态再重试。', + "推送前工作区应干净;如仍有改动,先提交或说明为什么暂不提交。", + "推送前复跑 kt_global_code_review,findings 必须为 0;如出现误报先升级 ktWorkflow 规则。", + "如果 Jenkins/Gitea host key、mirror、hooks 异常,先修远程仓库状态再重试。", ], projects: snapshots - .filter((snapshot) => snapshot.repoType === 'git') + .filter((snapshot) => snapshot.repoType === "git") .map((snapshot) => { - const branch = input.branch || snapshot.branch || 'main'; + const branch = input.branch || snapshot.branch || "main"; return { branch, commands: [ - 'git status --short', + "git status --short", `git log --oneline ${remote}/${branch}..${branch}`, `git push ${remote} ${branch}`, ], @@ -243,21 +297,43 @@ function buildRemoteHealthCommands(input: RemoteHealthCheckInput = {}): Array<{ command: string; service: string; }> { - const host = input.sshTarget || input.host || process.env.KT_NAS_SSH_TARGET || 'nas.kwitsukasa.top'; + const host = + input.sshTarget || + input.host || + process.env.KT_NAS_SSH_TARGET || + "nas.kwitsukasa.top"; const port = input.port ?? Number(process.env.KT_NAS_SSH_PORT || 2202); - const ssh = (remoteCommand: string) => `ssh -p ${port} ${host} "${remoteCommand}"`; + const ssh = (remoteCommand: string) => + `ssh -p ${port} ${host} "${remoteCommand}"`; const allCommands: Record = { docker: ssh("docker ps --format '{{.Names}} {{.Status}} {{.Ports}}'"), - gitea: ssh("docker ps --format '{{.Names}} {{.Status}}' | grep -i gitea || true"), - jenkins: ssh("docker ps --format '{{.Names}} {{.Status}}' | grep -i jenkins || true"), - k3d: ssh('kubectl get pods -A -o wide 2>/dev/null || k3d cluster list 2>/dev/null || true'), - minio: ssh("docker ps --format '{{.Names}} {{.Status}} {{.Ports}}' | grep -i minio || true"), - mosquitto: ssh("docker ps --format '{{.Names}} {{.Status}} {{.Ports}}' | grep -i mosquitto || true"), - mysql: ssh("docker ps --format '{{.Names}} {{.Status}} {{.Ports}}' | grep -Ei 'mysql|mariadb' || true"), - napcat: ssh("docker ps --format '{{.Names}} {{.Status}} {{.Ports}}' | grep -Ei 'napcat|qqbot' || true"), - ssh: ssh('hostname && date'), + gitea: ssh( + "docker ps --format '{{.Names}} {{.Status}}' | grep -i gitea || true", + ), + jenkins: ssh( + "docker ps --format '{{.Names}} {{.Status}}' | grep -i jenkins || true", + ), + k3d: ssh( + "kubectl get pods -A -o wide 2>/dev/null || k3d cluster list 2>/dev/null || true", + ), + minio: ssh( + "docker ps --format '{{.Names}} {{.Status}} {{.Ports}}' | grep -i minio || true", + ), + mosquitto: ssh( + "docker ps --format '{{.Names}} {{.Status}} {{.Ports}}' | grep -i mosquitto || true", + ), + mysql: ssh( + "docker ps --format '{{.Names}} {{.Status}} {{.Ports}}' | grep -Ei 'mysql|mariadb' || true", + ), + napcat: ssh( + "docker ps --format '{{.Names}} {{.Status}} {{.Ports}}' | grep -Ei 'napcat|qqbot' || true", + ), + ssh: ssh("hostname && date"), }; - const services = (input.services?.length ?? 0) > 0 ? input.services! : Object.keys(allCommands); + const services = + (input.services?.length ?? 0) > 0 + ? input.services! + : Object.keys(allCommands); return services .filter((service) => allCommands[service]) @@ -267,7 +343,9 @@ function buildRemoteHealthCommands(input: RemoteHealthCheckInput = {}): Array<{ })); } -export async function buildRemoteHealthCheck(input: RemoteHealthCheckInput = {}): Promise> { +export async function buildRemoteHealthCheck( + input: RemoteHealthCheckInput = {}, +): Promise> { const commands = buildRemoteHealthCommands(input); const results = input.execute ? await Promise.all( @@ -282,17 +360,19 @@ export async function buildRemoteHealthCheck(input: RemoteHealthCheckInput = {}) commands, execute: input.execute ?? false, notes: [ - '默认 execute=false,只生成只读探测命令。', - '远程命令只做 hostname/date/docker ps/kubectl get 等只读检查。', - '真实密码、token、Secret 不从仓库读取,也不写入输出。', + "默认 execute=false,只生成只读探测命令。", + "远程命令只做 hostname/date/docker ps/kubectl get 等只读检查。", + "真实密码、token、Secret 不从仓库读取,也不写入输出。", ], results, }; } -export function buildDbSyncPlan(input: DbSyncPlanInput = {}): Record { - const source = input.source || 'local'; - const target = input.target || 'remote'; +export function buildDbSyncPlan( + input: DbSyncPlanInput = {}, +): Record { + const source = input.source || "local"; + const target = input.target || "remote"; const backupName = normalizeDbIdentifier( input.backupName || `kt_template_backup_${formatShanghaiTimestamp()}_${Math.random().toString(36).slice(2, 8)}`, @@ -302,90 +382,281 @@ export function buildDbSyncPlan(input: DbSyncPlanInput = {}): Record kt_template > ${dumpPath}`, `mysql -e "CREATE DATABASE ${backupName} DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"`, `mysqldump --set-gtid-purged=OFF --single-transaction --routines --triggers kt_template | mysql ${backupName}`, `mysql kt_template < ${dumpPath}`, - '对比源库/目标库表数量、视图/函数/过程数量和关键表行数。', + "对比源库/目标库表数量、视图/函数/过程数量和关键表行数。", `验证通过后再执行:mysql -e "DROP DATABASE ${backupName};"`, ], guardrails: [ - '默认只生成计划,不执行覆盖、drop、truncate。', - '同步前必须确认 source/target 方向,不能靠变量名猜。', - '3306/3307 端口方向先用只读 SELECT 预检确认,避免把远程库误当成本地库。', - '目标库备份校验通过前不得删除备份库。', - '涉及生产库时优先只读验证,批量删除必须二次确认。', + "默认只生成计划,不执行覆盖、drop、truncate。", + "同步前必须确认 source/target 方向,不能靠变量名猜。", + "3306/3307 端口方向先用只读 SELECT 预检确认,避免把远程库误当成本地库。", + "目标库备份校验通过前不得删除备份库。", + "涉及生产库时优先只读验证,批量删除必须二次确认。", ], source, target, verification: [ - 'SHOW TABLES 数量一致。', - 'admin_user、admin_menu、qqbot_account、qqbot_message_log 等关键表行数符合预期。', - '应用登录和核心接口 smoke 通过。', - '全局 review findings=0。', + "SHOW TABLES 数量一致。", + "admin_user、admin_menu、qqbot_account、qqbot_message_log 等关键表行数符合预期。", + "应用登录和核心接口 smoke 通过。", + "全局 review findings=0。", ], }; } -export function buildComponentWorkflow(input: ComponentWorkflowInput = {}): Record { - const target = input.target || 'KtTable'; +export function buildComponentWorkflow( + input: ComponentWorkflowInput = {}, +): Record { + const target = input.target || "KtTable"; const workflows = { AdminAuth: { - antiPatterns: ['无 token 时主动 refresh', '跨端回跳时清空 Admin 已有授权态', '把 redirect 写丢或重复编码'], - files: ['Vue/kt-template-admin/apps/web-antdv-next/src/store/auth.ts', 'Vue/kt-template-online-web/src/api/auth.ts', 'Vue/kt-template-online-playground/src/api/auth.ts'], - guardrails: ['先画清楚 Admin/API/Web/Playground token 链路', '401 时先判断旧 token 是否存在', 'Admin 已登录时直接带回授权态'], - verification: ['清空 token 测无循环', 'Admin 已登录测直接回跳', '刷新页面测持久化'], + antiPatterns: [ + "无 token 时主动 refresh", + "跨端回跳时清空 Admin 已有授权态", + "把 redirect 写丢或重复编码", + ], + files: [ + "Vue/kt-template-admin/apps/web-antdv-next/src/store/auth.ts", + "Vue/kt-template-online-web/src/api/auth.ts", + "Vue/kt-template-online-playground/src/api/auth.ts", + ], + guardrails: [ + "先画清楚 Admin/API/Web/Playground token 链路", + "401 时先判断旧 token 是否存在", + "Admin 已登录时直接带回授权态", + ], + verification: [ + "清空 token 测无循环", + "Admin 已登录测直接回跳", + "刷新页面测持久化", + ], }, BlogArgon: { - antiPatterns: ['使用具名 id 控制 DOM', 'SCSS 类名逃出 kt-blog 命名空间', '主题变量散落在 scss 文件', '亮色模式仍套暗色遮罩'], - files: ['Vue/kt-blog-web/src/hooks/useBlogTheme.ts', 'Vue/kt-blog-web/src/styles', 'Vue/kt-blog-web/src/components/blog'], - guardrails: ['样式统一 kt-blog BEM 命名空间', '主题变量收敛到 useBlogTheme', '动画用 Vue Transition', '和线上 Argon DOM/视觉逐项对照'], - verification: ['亮/暗模式切换', '搜索 Enter 和动画', '文章切换动画', '无感知滚动加载', '背景图滚动到底部不断层'], + antiPatterns: [ + "使用具名 id 控制 DOM", + "SCSS 类名逃出 kt-blog 命名空间", + "主题变量散落在 scss 文件", + "亮色模式仍套暗色遮罩", + ], + files: [ + "Vue/kt-blog-web/src/hooks/useBlogTheme.ts", + "Vue/kt-blog-web/src/styles", + "Vue/kt-blog-web/src/components/blog", + ], + guardrails: [ + "样式统一 kt-blog BEM 命名空间", + "主题变量收敛到 useBlogTheme", + "动画用 Vue Transition", + "和线上 Argon DOM/视觉逐项对照", + ], + verification: [ + "亮/暗模式切换", + "搜索 Enter 和动画", + "文章切换动画", + "无感知滚动加载", + "背景图滚动到底部不断层", + ], }, Knife4jSwagger: { - antiPatterns: ['视觉只大致相似', 'TSX 里用 function render helper', 'TabPane 默认插槽不显式传 default', '把请求入参编辑拆成无法复用的零散状态'], - files: ['Plugins/knife4j-swagger-vue3/packages/knife4j-vue3-ui', 'Plugins/knife4j-swagger-vue3/packages/openapi-parser', 'Plugins/knife4j-swagger-vue3/apps/playground'], - guardrails: ['以 Knife4j 原始 UI 为 1:1 视觉源', '全部 Vue 视图使用 TSX', '嵌套组件 slot 显式 default', '路由使用 hash path 参数,不用 query 兼容旧逻辑', '调试台按 OpenAPI 标准合并 path/query/header/body/auth/global 参数'], - verification: ['pnpm run typecheck', '真实后端 48085 文档页面', '控制台 warning/error 检查', 'Body/Headers/Auth/全局参数发送链路', '小宽度 @media 与左侧调试栏固定'], + antiPatterns: [ + "视觉只大致相似", + "TSX 里用 function render helper", + "TabPane 默认插槽不显式传 default", + "把请求入参编辑拆成无法复用的零散状态", + ], + files: [ + "Plugins/knife4j-swagger-vue3/packages/knife4j-vue3-ui", + "Plugins/knife4j-swagger-vue3/packages/openapi-parser", + "Plugins/knife4j-swagger-vue3/apps/playground", + ], + guardrails: [ + "以 Knife4j 原始 UI 为 1:1 视觉源", + "全部 Vue 视图使用 TSX", + "嵌套组件 slot 显式 default", + "路由使用 hash path 参数,不用 query 兼容旧逻辑", + "调试台按 OpenAPI 标准合并 path/query/header/body/auth/global 参数", + ], + verification: [ + "pnpm run typecheck", + "真实后端 48085 文档页面", + "控制台 warning/error 检查", + "Body/Headers/Auth/全局参数发送链路", + "小宽度 @media 与左侧调试栏固定", + ], }, KtTable: { - antiPatterns: ['自定义纵向滚动条覆盖 Antdv 原生滚动', 'scroll/resize 高频写响应式状态', '表格高度参与展开收起动画', '固定列透明导致透视'], - files: ['Vue/kt-template-admin/apps/web-antdv-next/src/components/ktTable'], - guardrails: ['优先使用 antdv-next 原生 table 能力', '统计列固定到底部但不参与表单动画', '序号列和操作列固定最小/最大宽', 'resize 用 RAF 节流和非深度监听'], - verification: ['横向滚动性能', '纵向滚动表头对齐', '列宽/行高 resize', 'summary 固定底部', '宽屏无无意义横向滚动条'], + antiPatterns: [ + "自定义纵向滚动条覆盖 Antdv 原生滚动", + "scroll/resize 高频写响应式状态", + "表格高度参与展开收起动画", + "固定列透明导致透视", + ], + files: [ + "Vue/kt-template-admin/apps/web-antdv-next/src/components/ktTable", + ], + guardrails: [ + "优先使用 antdv-next 原生 table 能力", + "统计列固定到底部但不参与表单动画", + "序号列和操作列固定最小/最大宽", + "resize 用 RAF 节流和非深度监听", + ], + verification: [ + "横向滚动性能", + "纵向滚动表头对齐", + "列宽/行高 resize", + "summary 固定底部", + "宽屏无无意义横向滚动条", + ], }, QQBot: { - 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 过滤由开关控制', '扫码取消/过期/失败清理未绑定容器', '更新登录走 SSE 进度,后台重置登录态后再生成二维码'], - verification: ['账号在线', '扫码新增/更新登录', 'scan/events 每步进度', '自动回复规则命中', '权限名单三类过滤', '消息日志和发送日志闭环'], + 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 过滤由开关控制", + "扫码取消/过期/失败清理未绑定容器", + "更新登录走 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 查价中文物品名', '字典管理页面可维护映射'], + 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 查价中文物品名", + "字典管理页面可维护映射", + ], }, FnosK8s: { - antiPatterns: ['把 fnOS 原生面板改成 Docker 应用', '在 ui/config 显式配置公网端口', '提交 _build/_dist 打包产物', '只测桌面视口不测 fnOS 内嵌窗口和窄屏', '用非官方或旧缓存图标替代 Kubernetes 官方图标'], - files: ['Plugins/fnos-k8s-dashboard-fpk/src/server.ts', 'Plugins/fnos-k8s-dashboard-fpk/src/ui', 'Plugins/fnos-k8s-dashboard-fpk/apps/kt-k8s-dashboard/fnos', 'Plugins/fnos-k8s-dashboard-fpk/scripts'], - guardrails: ['保持非 Docker .fpk 形态', 'Web 面板源码只放 src/ui,由打包脚本复制进 app.tgz', 'fnOS 桌面入口使用 ui/config iframe 和 /kt-k8s-dashboard/ 代理路径', '应用标题与图标保持 Kubernetes 官方语义', '集群配置文件只作为运行时数据,不提交真实 kubeconfig'], - verification: ['bun run typecheck', 'bun run build:fpk', 'bun run verify:fpk', '本地 /api/health 和首页请求', 'Playwright 桌面与 390px 窄屏截图', '线上应用中心/桌面入口只在用户允许远程测试时验证'], + antiPatterns: [ + "把 fnOS 原生面板改成 Docker 应用", + "在 ui/config 显式配置公网端口", + "提交 _build/_dist 打包产物", + "只测桌面视口不测 fnOS 内嵌窗口和窄屏", + "用非官方或旧缓存图标替代 Kubernetes 官方图标", + ], + files: [ + "Plugins/fnos-k8s-dashboard-fpk/src/server.ts", + "Plugins/fnos-k8s-dashboard-fpk/src/ui", + "Plugins/fnos-k8s-dashboard-fpk/apps/kt-k8s-dashboard/fnos", + "Plugins/fnos-k8s-dashboard-fpk/scripts", + ], + guardrails: [ + "保持非 Docker .fpk 形态", + "Web 面板源码只放 src/ui,由打包脚本复制进 app.tgz", + "fnOS 桌面入口使用 ui/config iframe 和 /kt-k8s-dashboard/ 代理路径", + "应用标题与图标保持 Kubernetes 官方语义", + "集群配置文件只作为运行时数据,不提交真实 kubeconfig", + ], + verification: [ + "bun run typecheck", + "bun run build:fpk", + "bun run verify:fpk", + "本地 /api/health 和首页请求", + "Playwright 桌面与 390px 窄屏截图", + "线上应用中心/桌面入口只在用户允许远程测试时验证", + ], }, 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 能看到重置/生成二维码/等待扫码/成功或失败', '手动刷新不超时', '过期二维码不再复用'], + antiPatterns: [ + "更新登录复用已过期二维码 URL", + "前端等待长接口导致超时", + "手动刷新每次重启容器", + "后端拿旧 login state 判断成功", + "把 ACCOUNT 当成登录凭据,忽略 QQ 历史登录态、账号密码或扫码兜底", + "在请求日志、Loki 或列表接口里暴露 QQ 登录密码", + ], + 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: [ + "创建账号和更新登录共用同一套二维码生成事实源", + "更新登录顺序固定为 ACCOUNT/-q 快速登录 -> 账号保存的密码登录 -> reset runtime login state 并生成二维码", + "看门狗自动登录只跑 quick -> password,扫码必须由管理员手动触发", + "账号登录密码前端只传 RSA-OAEP 加密字段 encryptedLoginPassword,后端用 QQBOT_ACCOUNT_SECRET_KEY 二次加密落库,列表不回显", + "ACCOUNT 只指定目标账号;免扫码优先依赖数据卷历史登录态,密码登录失败或触发强制扫码时再进二维码", + "SSE 返回 step/status/qrcode/error", + "容器重启只在必要时触发,优先直接通信 NapCat API", + ], + verification: [ + "账号被踢下线后点击更新登录", + "docker inspect 确认已绑定容器存在 ACCOUNT=", + "docker restart 后 WebUI CheckLoginStatus/GetQQLoginInfo 验证 isLogin/online/uin;失败时检查 NapCat 日志是否为无历史登录记录、密码登录失败或需扫码验证", + "scan/events 能看到 quick-login-start/quick-login-fallback/password-login-start/password-login-fallback/重置/生成二维码/等待扫码/成功或失败", + "目标 Jest 覆盖 quick 成功、password 成功、password 失败进二维码、密文保存、容器 env 注入", + "手动刷新不超时", + "过期二维码不再复用", + ], }, 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 内容扫描,确认误报要沉淀到 review.ts'], - verification: ['API typecheck/lint/test', 'Loki /ready 和 query_range', 'Admin #/system/logs 无 console error', 'global-review findings 不含已知脱敏占位符或中文显示标签误报'], + 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 内容扫描,确认误报要沉淀到 review.ts", + ], + verification: [ + "API typecheck/lint/test", + "Loki /ready 和 query_range", + "Admin #/system/logs 无 console error", + "global-review findings 不含已知脱敏占位符或中文显示标签误报", + ], }, } satisfies Record>; diff --git a/src/types.ts b/src/types.ts index 73590b6..31274a3 100644 --- a/src/types.ts +++ b/src/types.ts @@ -261,6 +261,7 @@ export interface WorkstreamCloseoutInput { reviewEvidence?: string[]; reusablePatterns?: WorkstreamReusablePattern[]; stableSolutions?: string[]; + superpowersReviewEvidence?: string[]; title?: string; upgradeNotes?: string; verificationEvidence?: string[]; @@ -300,6 +301,7 @@ export interface WorkflowLoopAuditInput { reviewEvidence?: string[]; stableSolutions?: string[]; stage?: 'failure' | 'finish' | 'start' | 'test'; + superpowersReviewEvidence?: string[]; taskTitle?: string; testCases?: string[]; verificationEvidence?: string[];