From fe5fab34794fe82f84a00a4f630a2dd0e70c3aa7 Mon Sep 17 00:00:00 2001 From: sunlei Date: Sun, 7 Jun 2026 08:15:00 +0800 Subject: [PATCH] =?UTF-8?q?feat(workflow):=20=E5=A2=9E=E5=8A=A0=E5=A4=A7?= =?UTF-8?q?=E6=96=B9=E5=90=91=E9=97=AD=E7=8E=AF=E5=AE=A1=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 7 ++- package.json | 5 +- src/core/cli.ts | 52 ++++++++++++++++- src/core/constants.ts | 13 ++++- src/registerTools.ts | 21 +++++++ src/selfTest.ts | 26 +++++++++ src/server.ts | 7 ++- src/tools/closeout.ts | 129 ++++++++++++++++++++++++++++++++++++++++++ src/tools/workflow.ts | 11 ++++ src/types.ts | 20 +++++++ 10 files changed, 284 insertions(+), 7 deletions(-) create mode 100644 src/tools/closeout.ts diff --git a/README.md b/README.md index 4a0fa74..ac78eee 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # KT Workflow MCP -`ktWorkflow` 是 KT 工作区的可复用 MCP 能力包,用 TypeScript 封装工具入参、项目别名和返回结构,用来把根目录 `AGENTS.md` 的硬规则、`SKILLS.md` 的工作流索引、`TASKS.md` 的上下文记录和 `docs/` 的详细流程变成可调用工具。当前版本:`0.6.1`。 +`ktWorkflow` 是 KT 工作区的可复用 MCP 能力包,用 TypeScript 封装工具入参、项目别名和返回结构,用来把根目录 `AGENTS.md` 的硬规则、`SKILLS.md` 的工作流索引、`TASKS.md` 的上下文记录和 `docs/` 的详细流程变成可调用工具。当前版本:`0.6.2`。 ## 能力边界 @@ -10,6 +10,7 @@ - 生成验证建议:按后端、前端、样式、页面、部署、MCP 等变更类型给出轻量验证命令。 - 固化改动后 review:验证计划和提交清单都会提醒执行 `kt_global_code_review` / `pnpm run global-review`。 - 生成任务收尾包:把状态、验证计划、可选验证执行、全局 review 和历史清理收成一份结果。 +- 生成大方向完结闭环审计:确认开发目标、测试证据、问题记录、稳定解法和 ktWorkflow 升级是否齐备。 - 生成页面测试用例:内置“先写用例、可视化证据、事不过三”的测试闭环。 - 生成接口测试计划:接口改动后输出真实调用命令和统一返回结构断言。 - 生成业务链路测试计划:固化 Admin 登录、博客 CRUD、QQBot 扫码/自动回复、更新登录 SSE、FFLogs 命令、系统日志可视化、Web/Playground 回跳。 @@ -31,6 +32,7 @@ cd D:/MyFiles/KT/mcp/ktWorkflow pnpm install pnpm run typecheck pnpm run self-test +pnpm run workstream-closeout -- --title "发布闭环" --verification "Jenkins SUCCESS" --problem "无新卡点" --solution "无新增稳定解法" pnpm run cleanup-history -- --dry-run pnpm run admin-login -- --url http://127.0.0.1:5999/#/auth/login ``` @@ -77,6 +79,7 @@ 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` | 生成大方向完结闭环审计,检查测试证据、问题记录、稳定解法和 ktWorkflow 升级 | | `kt_commit_plan` | 生成多仓库提交计划、建议 commit message 和检查项 | | `kt_push_plan` | 生成多仓库推送计划和远程异常提醒 | | `kt_business_test_plan` | 生成固化业务链路测试计划,包含 QQBot SSE、FFLogs 和系统日志 | @@ -93,6 +96,7 @@ pnpm run admin-login -- --url http://127.0.0.1:5999/#/auth/login | --- | --- | | `pnpm run admin-login` | 使用可见 Edge 打开 Admin 登录页,填写账号密码,拖动滑块,保存登录态和截图。默认账号来自初始化数据 `admin/123456`,生产或个人账号用 `KT_ADMIN_USERNAME` / `KT_ADMIN_PASSWORD` 或 CLI 参数覆盖。 | | `pnpm run global-review` | 对 KT 全部子仓库做只读全局 CodeReview 扫描,默认仅对变更文件做内容深扫,输出 JSON 复审报告;确认误报时优先升级 `src/tools/review.ts`,不要把误报沉积到上下文。 | +| `pnpm run workstream-closeout` | 生成大方向完结闭环审计;识别测试证据、问题记录、稳定解法和 ktWorkflow 升级缺口。 | ## 项目别名 @@ -123,6 +127,7 @@ pnpm run admin-login -- --url http://127.0.0.1:5999/#/auth/login - 写代码前先调用 `kt_prepare_task`;只需要单项信息时再调用 `kt_read_context` 或 `kt_inspect_project`。`kt_read_context` 默认紧凑输出,只有确实需要完整 `TASKS.md` 时再传 `includeFullDocs=true`。 - 多项目联动时先调用 `kt_inspect_all_projects`。 - 要收尾时调用 `kt_finish_task`,默认只生成计划和 review;需要执行验证时显式传 `runValidation=true`。 +- 大方向结束前调用 `kt_workstream_closeout` 或 `pnpm run workstream-closeout`;如果识别到测试流、卡点、误报、清理、部署观测或命令模板,要先升级对应 ktWorkflow 规则再报告完成。 - 文件改动完成并验证后调用 `kt_global_code_review`,或运行 `pnpm run global-review`;它只读扫描,不删除文件、不提交代码。`findings` 先判定真实风险或工具误报,真实风险修业务代码,误报修 `mcp/ktWorkflow/src/tools/review.ts` 并复跑。 - 要提交或推送时先调用 `kt_commit_plan` / `kt_push_plan`,按仓库分组确认范围。 - 要测真实业务链路时调用 `kt_business_test_plan`,选择 `admin-login`、`qqbot-auto-reply`、`qqbot-login-sse`、`fflogs-command`、`system-log-visualization` 等 flow。 diff --git a/package.json b/package.json index a772a1a..61de569 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kt/mcp-kt-workflow", - "version": "0.6.1", + "version": "0.6.2", "private": true, "type": "module", "description": "Reusable MCP workflow tools for the KT workspace.", @@ -10,7 +10,8 @@ "global-review": "node --import ./node_modules/tsx/dist/loader.mjs src/server.ts --global-review", "self-test": "node --import ./node_modules/tsx/dist/loader.mjs src/server.ts --self-test", "start": "node --import ./node_modules/tsx/dist/loader.mjs src/server.ts", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit", + "workstream-closeout": "node --import ./node_modules/tsx/dist/loader.mjs src/server.ts --workstream-closeout" }, "dependencies": { "@modelcontextprotocol/sdk": "^1.20.0", diff --git a/src/core/cli.ts b/src/core/cli.ts index 6944b89..9793f80 100644 --- a/src/core/cli.ts +++ b/src/core/cli.ts @@ -1,4 +1,5 @@ -import type { GlobalCodeReviewInput } from '../types.js'; +import type { GlobalCodeReviewInput, WorkstreamCloseoutInput, WorkstreamReusablePattern } from '../types.js'; +import { workstreamReusablePatternValues } from './constants.js'; function parseContentScanMode(argv: string[]): 'all' | 'changed' { if (argv.includes('--content-scan-all') || argv.includes('--contentScanMode=all')) { @@ -20,3 +21,52 @@ export function parseGlobalReviewCliArgs( includeRootScan: !argv.includes('--no-root-scan'), }; } + +function readOption(argv: string[], names: string[]): string | undefined { + for (const name of names) { + const keyValue = argv.find((item) => item.startsWith(`${name}=`)); + if (keyValue) return keyValue.slice(name.length + 1); + + const index = argv.indexOf(name); + if (index >= 0 && argv[index + 1] && !argv[index + 1].startsWith('--')) { + return argv[index + 1]; + } + } + return undefined; +} + +function readOptionList(argv: string[], names: string[]): string[] { + const values: string[] = []; + + names.forEach((name) => { + argv.forEach((item, index) => { + if (item.startsWith(`${name}=`)) { + values.push(item.slice(name.length + 1)); + } else if (item === name && argv[index + 1] && !argv[index + 1].startsWith('--')) { + values.push(argv[index + 1]); + } + }); + }); + + return values + .flatMap((value) => value.split(',')) + .map((value) => value.trim()) + .filter(Boolean); +} + +export function parseWorkstreamCloseoutCliArgs(argv: string[]): WorkstreamCloseoutInput { + const patternSet = new Set(workstreamReusablePatternValues); + const patterns = readOptionList(argv, ['--pattern', '--reusable-pattern']) + .filter((pattern): pattern is WorkstreamReusablePattern => patternSet.has(pattern)); + + return { + changedProjects: readOptionList(argv, ['--project', '--changed-project']), + ktWorkflowUpdated: argv.includes('--kt-workflow-updated'), + problemRecords: readOptionList(argv, ['--problem', '--problem-record']), + reusablePatterns: patterns, + stableSolutions: readOptionList(argv, ['--solution', '--stable-solution']), + title: readOption(argv, ['--title']), + upgradeNotes: readOption(argv, ['--upgrade-notes']), + verificationEvidence: readOptionList(argv, ['--verification', '--verification-evidence']), + }; +} diff --git a/src/core/constants.ts b/src/core/constants.ts index 770ffb9..82f98ba 100644 --- a/src/core/constants.ts +++ b/src/core/constants.ts @@ -1,4 +1,4 @@ -import type { ProjectAlias, TaskType } from '../types.js'; +import type { ProjectAlias, TaskType, WorkstreamReusablePattern } from '../types.js'; export const projectAliases = { admin: 'Vue/kt-template-admin', api: 'Node/kt-template-online-api', @@ -38,6 +38,16 @@ export const taskTypeValues = [ 'style', ] as const satisfies readonly TaskType[]; +export const workstreamReusablePatternValues = [ + 'blocker', + 'cleanup', + 'command-template', + 'deploy-observation', + 'none', + 'review-false-positive', + 'test-flow', +] as const satisfies readonly WorkstreamReusablePattern[]; + export const registeredToolNames = [ 'kt_read_context', 'kt_inspect_project', @@ -53,6 +63,7 @@ export const registeredToolNames = [ 'kt_risk_scan', 'kt_global_code_review', 'kt_finish_task', + 'kt_workstream_closeout', 'kt_commit_plan', 'kt_push_plan', 'kt_business_test_plan', diff --git a/src/registerTools.ts b/src/registerTools.ts index d9f3e92..de4f192 100644 --- a/src/registerTools.ts +++ b/src/registerTools.ts @@ -12,7 +12,9 @@ import { buildGlobalCodeReview, defaultReviewProjects } from './tools/review.js' import { appendTaskRecord, createCommitChecklist, prepareTask, readWorkflowContext } from './tools/task.js'; import { buildBusinessTestPlan, createApiTestPlan, createPageTestCase } from './tools/testing.js'; import { buildVerificationPlan } from './tools/verification.js'; +import { buildWorkstreamCloseout } from './tools/closeout.js'; import { buildCommitPlan, buildComponentWorkflow, buildDbSyncPlan, buildFinishTask, buildPushPlan, buildRemoteHealthCheck } from './tools/workflow.js'; +import { workstreamReusablePatternValues } from './core/constants.js'; export function response(data: unknown): McpTextResponse { return { content: [ @@ -238,6 +240,25 @@ export function registerTools(server: McpServer): void { async (input) => response(await buildFinishTask(input)), ); + server.registerTool( + 'kt_workstream_closeout', + { + description: '生成大方向完结闭环审计:开发、测试证据、问题记录、稳定解法和 ktWorkflow 升级是否齐备。', + inputSchema: { + changedProjects: z.array(z.string()).default([]), + ktWorkflowUpdated: z.boolean().default(false), + problemRecords: z.array(z.string()).default([]), + reusablePatterns: z.array(z.enum(workstreamReusablePatternValues)).default(['none']), + stableSolutions: z.array(z.string()).default([]), + title: z.string().optional(), + upgradeNotes: z.string().optional(), + verificationEvidence: z.array(z.string()).default([]), + }, + title: 'KT Workstream Closeout', + }, + async (input) => response(buildWorkstreamCloseout(input)), + ); + server.registerTool( 'kt_commit_plan', { diff --git a/src/selfTest.ts b/src/selfTest.ts index 1a567f1..530cc9c 100644 --- a/src/selfTest.ts +++ b/src/selfTest.ts @@ -12,6 +12,7 @@ import { prepareTask, readWorkflowContext } from './tools/task.js'; import { buildGlobalCodeReview, isBenignCredentialReviewValue } from './tools/review.js'; import { buildBusinessTestPlan } from './tools/testing.js'; import { buildVerificationPlan } from './tools/verification.js'; +import { buildWorkstreamCloseout } from './tools/closeout.js'; import { buildCommitPlan, buildComponentWorkflow, buildDbSyncPlan, buildFinishTask, buildPushPlan, buildRemoteHealthCheck } from './tools/workflow.js'; export async function runSelfTest(): Promise { const reviewClassifier = { @@ -47,6 +48,29 @@ export async function runSelfTest(): Promise { throw new Error('refactor guardrail self-check failed'); } + const closeoutNeedsUpgrade = buildWorkstreamCloseout({ + problemRecords: ['Jenkins 状态需要结合日志和 K8s 状态确认。'], + reusablePatterns: ['deploy-observation'], + stableSolutions: ['收尾时固定观测 build、commit、镜像、Deployment、Pod 和 smoke。'], + title: '发布闭环', + verificationEvidence: ['Jenkins #132 SUCCESS,K8s Pod Running。'], + }); + if ((closeoutNeedsUpgrade.canReportComplete as boolean) !== false) { + throw new Error('workstream closeout upgrade gate self-check failed'); + } + + const closeoutComplete = buildWorkstreamCloseout({ + ktWorkflowUpdated: true, + problemRecords: ['无新卡点。'], + reusablePatterns: ['none'], + stableSolutions: ['无新增稳定解法。'], + title: '文档收尾', + verificationEvidence: ['typecheck/self-test/global-review 通过。'], + }); + if ((closeoutComplete.canReportComplete as boolean) !== true) { + throw new Error('workstream closeout complete self-check failed'); + } + const data = { context: readWorkflowContext({ taskRecordCount: 2 }), guardrails: buildGuardrails({ @@ -78,6 +102,8 @@ export async function runSelfTest(): Promise { businessTestPlan: buildBusinessTestPlan({ flow: 'system-log-visualization', }), + closeoutComplete, + closeoutNeedsUpgrade, blockerResolution: buildBlockerResolution({ attempts: 2, evidence: 'self-test', diff --git a/src/server.ts b/src/server.ts index add1c0e..acfa267 100644 --- a/src/server.ts +++ b/src/server.ts @@ -2,8 +2,9 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; -import { parseGlobalReviewCliArgs } from './core/cli.js'; +import { parseGlobalReviewCliArgs, parseWorkstreamCloseoutCliArgs } from './core/cli.js'; import { cleanupHistoryArtifacts, parseCliCleanupArgs } from './tools/cleanup.js'; +import { buildWorkstreamCloseout } from './tools/closeout.js'; import { buildGlobalCodeReview } from './tools/review.js'; import { registerTools } from './registerTools.js'; import { runSelfTest } from './selfTest.js'; @@ -20,10 +21,12 @@ if (process.argv.includes('--cleanup-history')) { ); } else if (process.argv.includes('--self-test')) { await runSelfTest(); +} else if (process.argv.includes('--workstream-closeout')) { + console.log(JSON.stringify(buildWorkstreamCloseout(parseWorkstreamCloseoutCliArgs(process.argv)), null, 2)); } else { const server = new McpServer({ name: 'kt-workflow', - version: '0.6.1', + version: '0.6.2', }); registerTools(server); await server.connect(new StdioServerTransport()); diff --git a/src/tools/closeout.ts b/src/tools/closeout.ts new file mode 100644 index 0000000..fe94739 --- /dev/null +++ b/src/tools/closeout.ts @@ -0,0 +1,129 @@ +import type { WorkstreamCloseoutInput, WorkstreamReusablePattern } from '../types.js'; +import { workstreamReusablePatternValues } from '../core/constants.js'; +import { defaultReviewProjects } from './review.js'; + +interface UpgradeTarget { + files: string[]; + label: string; + prompt: string; +} + +const upgradeTargets = { + blocker: { + files: ['src/tools/blocker.ts', 'src/tools/workflow.ts', 'README.md'], + label: '卡点固化', + prompt: '把重复失败的命令、稳定解法、停止条件和后续入口沉淀成 kt_blocker_resolution 或收尾规则。', + }, + cleanup: { + files: ['src/tools/cleanup.ts', 'src/tools/verification.ts', 'README.md'], + label: '清理需求', + prompt: '把历史产物、验证进程或远程临时目录清理变成可复用命令或验证提醒。', + }, + 'command-template': { + files: ['src/tools/workflow.ts', 'src/tools/testing.ts', 'scripts/'], + label: '命令模板', + prompt: '把反复使用且容易写错的命令模板封装到工作流、业务测试计划或脚本。', + }, + 'deploy-observation': { + files: ['src/tools/workflow.ts', 'src/tools/testing.ts', 'README.md'], + label: '部署观测', + prompt: '把 Jenkins/K8s build、commit、镜像、Deployment、Pod、日志和 smoke 观测步骤写成固定计划。', + }, + none: { + files: [], + label: '无可执行升级', + prompt: '明确说明本轮没有新的可复用流程、误报、清理或命令模板需要沉淀。', + }, + 'review-false-positive': { + files: ['src/tools/review.ts', 'src/selfTest.ts', 'README.md'], + label: '复审误报', + prompt: '把已确认的误报规则沉淀到 review 过滤器和 self-test,避免后续污染上下文。', + }, + 'test-flow': { + files: ['src/tools/testing.ts', 'scripts/', 'README.md'], + label: '测试链路', + prompt: '把新的真实接口、页面、QQBot、部署或业务 smoke 流程加入测试计划或脚本。', + }, +} satisfies Record; + +function normalizeList(values?: string[]): string[] { + return (values || []).map((value) => value.trim()).filter(Boolean); +} + +function normalizePatterns(values?: WorkstreamReusablePattern[]): WorkstreamReusablePattern[] { + const validValues = new Set(workstreamReusablePatternValues); + const uniqueValues = Array.from( + new Set((values || []).filter((value) => validValues.has(value))), + ); + const patterns: WorkstreamReusablePattern[] = uniqueValues.length > 0 ? uniqueValues : ['none']; + + return patterns.length > 1 ? patterns.filter((pattern) => pattern !== 'none') : patterns; +} + +function buildPhase(name: string, evidence: string[], emptyHint: string): Record { + return { + evidence, + name, + status: evidence.length > 0 ? 'ok' : 'missing', + suggestion: evidence.length > 0 ? undefined : emptyHint, + }; +} + +export function buildWorkstreamCloseout(input: WorkstreamCloseoutInput = {}): Record { + const title = input.title?.trim() || ''; + const changedProjects = normalizeList(input.changedProjects); + const verificationEvidence = normalizeList(input.verificationEvidence); + const problemRecords = normalizeList(input.problemRecords); + const stableSolutions = normalizeList(input.stableSolutions); + const patterns = normalizePatterns(input.reusablePatterns); + const upgradeNeeded = patterns.some((pattern) => pattern !== 'none'); + const ktWorkflowUpdated = input.ktWorkflowUpdated === true; + const missingItems = [ + title ? '' : '填写本轮大方向名称或目标。', + verificationEvidence.length > 0 ? '' : '补充测试或 smoke 验证证据;只有计划不算闭环完成。', + problemRecords.length > 0 ? '' : '补充问题记录;如无新卡点,显式写“无新卡点”。', + stableSolutions.length > 0 ? '' : '补充稳定解法;如无新解法,显式写“无新增稳定解法”。', + upgradeNeeded && !ktWorkflowUpdated + ? '本轮识别到可复用模式,先升级 ktWorkflow 对应规则或脚本,再报告完成。' + : '', + ].filter(Boolean); + const targetProjects = changedProjects.length > 0 ? changedProjects : [...defaultReviewProjects]; + const selectedUpgradeTargets = patterns.map((pattern) => ({ + pattern, + ...upgradeTargets[pattern], + })); + + return { + canReportComplete: missingItems.length === 0, + closeoutOrder: [ + buildPhase('development', title ? [title] : [], '写清本轮完成的开发目标。'), + buildPhase('test/smoke verification', verificationEvidence, '补一条可复验的验证证据。'), + buildPhase('problem record', problemRecords, '记录问题点;没有问题也要显式写明。'), + buildPhase('stable solution', stableSolutions, '记录稳定解法;没有新增解法也要显式写明。'), + { + evidence: upgradeNeeded + ? [ + ktWorkflowUpdated + ? input.upgradeNotes || '已升级 ktWorkflow。' + : '待升级 ktWorkflow。', + ] + : [input.upgradeNotes || '无新的可执行 ktWorkflow 升级需求。'], + name: 'ktWorkflow upgrade', + status: upgradeNeeded ? (ktWorkflowUpdated ? 'ok' : 'missing') : 'not-needed', + }, + ], + commands: [ + 'git status --short', + 'pnpm run typecheck', + 'pnpm run self-test', + 'pnpm run global-review', + 'git diff --check', + ], + missingItems, + patterns, + targetProjects, + title: title || '未命名大方向', + upgradeNeeded, + upgradeTargets: selectedUpgradeTargets, + }; +} diff --git a/src/tools/workflow.ts b/src/tools/workflow.ts index 879e8bb..798bf37 100644 --- a/src/tools/workflow.ts +++ b/src/tools/workflow.ts @@ -6,6 +6,7 @@ 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[]; @@ -109,6 +110,16 @@ export async function buildFinishTask(input: FinishTaskInput = {}): Promise snapshot.project.relativePath), + problemRecords: [], + reusablePatterns: ['none'], + stableSolutions: [], + 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', notes: [ '默认只生成收尾结果;runValidation=true 时才执行验证命令。', diff --git a/src/types.ts b/src/types.ts index 878ca4f..71d5e17 100644 --- a/src/types.ts +++ b/src/types.ts @@ -25,6 +25,15 @@ export type TaskType = | 'refactor' | 'style'; +export type WorkstreamReusablePattern = + | 'blocker' + | 'cleanup' + | 'command-template' + | 'deploy-observation' + | 'none' + | 'review-false-positive' + | 'test-flow'; + export interface ResolvedProject { alias: ProjectAlias | null; label: string; @@ -242,6 +251,17 @@ export interface WorkflowContextInput { taskRecordCount?: number; } +export interface WorkstreamCloseoutInput { + changedProjects?: string[]; + ktWorkflowUpdated?: boolean; + problemRecords?: string[]; + reusablePatterns?: WorkstreamReusablePattern[]; + stableSolutions?: string[]; + title?: string; + upgradeNotes?: string; + verificationEvidence?: string[]; +} + export interface VerificationPlanResult { changeType: TaskType; commands: string[];