fix(workflow): 收紧清理闭环门禁
This commit is contained in:
parent
432320dd9d
commit
b5f24b3ee0
16
README.md
16
README.md
@ -18,7 +18,7 @@
|
|||||||
- 生成卡点固化记录:把超时、卡进程、远程命令误写、重复失败整理成“问题点 / 稳定解法 / 后续入口 / 验证证据”,避免原样重试。
|
- 生成卡点固化记录:把超时、卡进程、远程命令误写、重复失败整理成“问题点 / 稳定解法 / 后续入口 / 验证证据”,避免原样重试。
|
||||||
- 生成改动文档同步计划:按变更文件自动提示需要同步的 README、API、AGENTS、docs、Obsidian、skill 和 ktWorkflow 入口。
|
- 生成改动文档同步计划:按变更文件自动提示需要同步的 README、API、AGENTS、docs、Obsidian、skill 和 ktWorkflow 入口。
|
||||||
- 生成多仓库提交/推送计划:按仓库分组、建议提交信息、列出提交和推送前检查。
|
- 生成多仓库提交/推送计划:按仓库分组、建议提交信息、列出提交和推送前检查。
|
||||||
- 生成远程只读健康检查和数据库同步安全向导:覆盖飞牛 NAS 服务探测、GTID、备份和行数校验。
|
- 生成远程只读健康检查和数据库同步安全向导:覆盖飞牛 NAS 服务探测、GTID、`.kt-workspace/db-sync` 转储、备份库和行数校验。
|
||||||
- 生成专项组件工作流:KtTable、BlogArgon、AdminAuth、QQBot、FF14Plugin、NapCatLogin、SystemLog、Knife4jSwagger、FnosK8s 的防踩坑清单和验证点。
|
- 生成专项组件工作流:KtTable、BlogArgon、AdminAuth、QQBot、FF14Plugin、NapCatLogin、SystemLog、Knife4jSwagger、FnosK8s 的防踩坑清单和验证点。
|
||||||
- 生成验证进程清理计划:按项目路径和端口给出 PowerShell 检查命令,不直接杀进程。
|
- 生成验证进程清理计划:按项目路径和端口给出 PowerShell 检查命令,不直接杀进程。
|
||||||
- 清理历史产物:统一治理 `.kt-workspace` 下的测试/验证产物,按目录最近修改时间只保留最近 3 轮,模板目录永久保留;CLI 默认 dry-run,真实清理必须显式传 `--execute`。
|
- 清理历史产物:统一治理 `.kt-workspace` 下的测试/验证产物,按目录最近修改时间只保留最近 3 轮,模板目录永久保留;CLI 默认 dry-run,真实清理必须显式传 `--execute`。
|
||||||
@ -37,7 +37,7 @@ pnpm run self-test
|
|||||||
pnpm run obsidian-context -- --module ktWorkflow
|
pnpm run obsidian-context -- --module ktWorkflow
|
||||||
pnpm run obsidian-validate
|
pnpm run obsidian-validate
|
||||||
pnpm run obsidian-sync
|
pnpm run obsidian-sync
|
||||||
pnpm run workstream-closeout -- --title "发布闭环" --verification "Jenkins SUCCESS" --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" --problem "无新卡点" --solution "无新增稳定解法"
|
||||||
pnpm run cleanup-history -- --dry-run
|
pnpm run cleanup-history -- --dry-run
|
||||||
pnpm run cleanup-history -- --execute
|
pnpm run cleanup-history -- --execute
|
||||||
pnpm run admin-login -- --url http://127.0.0.1:5999/#/auth/login
|
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_risk_scan` | 扫描当前或传入变更文件的偏差风险 |
|
||||||
| `kt_global_code_review` | 对 KT 全部子仓库做只读全局 CodeReview 扫描,默认仅深扫变更文件 |
|
| `kt_global_code_review` | 对 KT 全部子仓库做只读全局 CodeReview 扫描,默认仅深扫变更文件 |
|
||||||
| `kt_finish_task` | 生成任务收尾包,可选执行验证和历史清理 |
|
| `kt_finish_task` | 生成任务收尾包,可选执行验证和历史清理 |
|
||||||
| `kt_workstream_closeout` | 生成大方向完结闭环审计,检查测试证据、问题记录、稳定解法和 ktWorkflow 升级 |
|
| `kt_workstream_closeout` | 生成大方向完结闭环审计,检查测试证据、文档同步、历史清理、全局 review、问题记录、稳定解法和 ktWorkflow 升级 |
|
||||||
| `kt_workflow_loop_audit` | 审计上下文锚定、测试证据、文档同步、历史清理、问题固化、ktWorkflow 升级和全局 review |
|
| `kt_workflow_loop_audit` | 审计上下文锚定、测试证据、文档同步、历史清理最终 `deleted=0`、问题固化、ktWorkflow 升级和全局 review |
|
||||||
| `kt_change_doc_sync` | 根据改动路径生成 README/API/AGENTS/docs/Obsidian/skill/ktWorkflow 文档同步清单 |
|
| `kt_change_doc_sync` | 根据改动路径生成 README/API/AGENTS/docs/Obsidian/skill/ktWorkflow 文档同步清单 |
|
||||||
| `kt_commit_plan` | 生成多仓库提交计划、建议 commit message 和检查项 |
|
| `kt_commit_plan` | 生成多仓库提交计划、建议 commit message 和检查项 |
|
||||||
| `kt_push_plan` | 生成多仓库推送计划和远程异常提醒 |
|
| `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-validate` | 校验 KT Obsidian vault 结构和链接;默认 warning 不让脚本失败,需要严格模式时传 `--fail-on-warnings`。 |
|
||||||
| `pnpm run obsidian-sync` | 审计 Obsidian 工作流入口是否连通,并联动执行 validate。 |
|
| `pnpm run obsidian-sync` | 审计 Obsidian 工作流入口是否连通,并联动执行 validate。 |
|
||||||
| `pnpm run cleanup-history` | 预览 `.kt-workspace` 测试/验证历史产物清理;真实清理用 `pnpm run cleanup-history -- --execute`,只保留最近 3 轮并保留模板目录。 |
|
| `pnpm run cleanup-history` | 预览 `.kt-workspace` 测试/验证历史产物清理;真实清理用 `pnpm run cleanup-history -- --execute`,只保留最近 3 轮并保留模板目录。 |
|
||||||
| `pnpm run workstream-closeout` | 生成大方向完结闭环审计;识别测试证据、问题记录、稳定解法和 ktWorkflow 升级缺口。 |
|
| `pnpm run workstream-closeout` | 生成大方向完结闭环审计;识别测试证据、文档同步、历史清理、全局 review、问题记录、稳定解法和 ktWorkflow 升级缺口。 |
|
||||||
|
|
||||||
## 项目别名
|
## 项目别名
|
||||||
|
|
||||||
@ -146,12 +146,12 @@ pnpm run admin-login -- --url http://127.0.0.1:5999/#/auth/login
|
|||||||
- 大方向结束前调用 `kt_workstream_closeout` 或 `pnpm run workstream-closeout`;如果识别到测试流、卡点、误报、清理、部署观测或命令模板,要先升级对应 ktWorkflow 规则再报告完成。
|
- 大方向结束前调用 `kt_workstream_closeout` 或 `pnpm run workstream-closeout`;如果识别到测试流、卡点、误报、清理、部署观测或命令模板,要先升级对应 ktWorkflow 规则再报告完成。
|
||||||
- 文件改动完成并验证后调用 `kt_global_code_review`,或运行 `pnpm run global-review`;它只读扫描,不删除文件、不提交代码。`findings` 先判定真实风险或工具误报,真实风险修业务代码,误报修 `mcp/ktWorkflow/src/tools/review.ts` 并复跑。
|
- 文件改动完成并验证后调用 `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_change_doc_sync`,把需要同步的 README/API/AGENTS/docs/Obsidian/skill/ktWorkflow 入口补齐;无需同步时把原因写进收尾证据。
|
||||||
- 报告非平凡任务完成前调用 `kt_workflow_loop_audit`,确认测试证据、文档同步、历史清理、问题固化、ktWorkflow 升级和全局 review 都过门。
|
- 报告非平凡任务完成前调用 `kt_workflow_loop_audit`,确认测试证据、文档同步、历史清理最终 `deleted=0`、问题固化、ktWorkflow 升级和全局 review 都过门;清理证据必须显式传入,不能依赖缺省值。
|
||||||
- 要提交或推送时先调用 `kt_commit_plan` / `kt_push_plan`,按仓库分组确认范围。
|
- 要提交或推送时先调用 `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_business_test_plan`,选择 `admin-login`、`qqbot-auto-reply`、`qqbot-login-sse`、`fflogs-command`、`system-log-visualization` 等 flow。
|
||||||
- 遇到同一命令或同一远程步骤重复卡住时调用 `kt_blocker_resolution`;第二次仍失败时先写入 `TASKS.md` 或补成脚本,再继续。
|
- 遇到同一命令或同一远程步骤重复卡住时调用 `kt_blocker_resolution`;第二次仍失败时先写入 `TASKS.md` 或补成脚本,再继续。
|
||||||
- 远程服务排查先调用 `kt_remote_health_check`,默认只生成只读命令;需要执行时显式传 `execute=true`。
|
- 远程服务排查先调用 `kt_remote_health_check`,默认只生成只读命令;需要执行时显式传 `execute=true`。
|
||||||
- 数据库同步前调用 `kt_db_sync_plan`,先确认源库、目标库、备份库和校验点。
|
- 数据库同步前调用 `kt_db_sync_plan`,先确认源库、目标库、`.kt-workspace/db-sync` 转储目录、带时间戳的备份库和校验点。
|
||||||
- 改 KtTable、BlogArgon、AdminAuth、QQBot、FF14Plugin、NapCatLogin、SystemLog、Knife4jSwagger、FnosK8s 前调用 `kt_component_workflow`,先看专项禁区。
|
- 改 KtTable、BlogArgon、AdminAuth、QQBot、FF14Plugin、NapCatLogin、SystemLog、Knife4jSwagger、FnosK8s 前调用 `kt_component_workflow`,先看专项禁区。
|
||||||
- 不确定任务边界时调用 `kt_guardrails`,先拿到“能做什么、不能做什么、怎么验证”。
|
- 不确定任务边界时调用 `kt_guardrails`,先拿到“能做什么、不能做什么、怎么验证”。
|
||||||
- 验证前调用 `kt_suggest_verification`,避免盲跑全量构建。
|
- 验证前调用 `kt_suggest_verification`,避免盲跑全量构建。
|
||||||
@ -160,7 +160,7 @@ pnpm run admin-login -- --url http://127.0.0.1:5999/#/auth/login
|
|||||||
- Admin 页面测试前可先执行 `pnpm run admin-login -- --url <Admin登录页>` 固化登录态,输出的 `storageState` 可作为后续 Playwright 用例前置状态。
|
- Admin 页面测试前可先执行 `pnpm run admin-login -- --url <Admin登录页>` 固化登录态,输出的 `storageState` 可作为后续 Playwright 用例前置状态。
|
||||||
- 接口改动后调用 `kt_api_test_plan`,并真实请求一次接口。
|
- 接口改动后调用 `kt_api_test_plan`,并真实请求一次接口。
|
||||||
- 验证启动过本地服务后调用 `kt_cleanup_process_plan`,清掉本次进程。
|
- 验证启动过本地服务后调用 `kt_cleanup_process_plan`,清掉本次进程。
|
||||||
- 每轮测试/验证结束后先调用 `kt_cleanup_history`,或运行 `pnpm run cleanup-history -- --dry-run`;如果预览里 `deleted` 非空,确认范围后执行 `pnpm run cleanup-history -- --execute`,让 `.kt-workspace` 历史产物只保留最近 3 轮并保留模板目录。
|
- 每轮测试/验证结束后先调用 `kt_cleanup_history`,或运行 `pnpm run cleanup-history -- --dry-run`;如果预览里 `deleted` 非空,确认范围后执行 `pnpm run cleanup-history -- --execute`,再复跑 dry-run 确认 `deleted=0`,让 `.kt-workspace` 历史产物只保留最近 3 轮并保留模板目录。`kt_workstream_closeout` / `kt_workflow_loop_audit` 只认可明确的 `deleted=0` / `deleted=[]` 文本证据或结构化 `cleanupFinalDeleted=0`。
|
||||||
- 更新 Obsidian 图谱、模块页、文档矩阵或 `.obsidian` 配置后运行 `kt_obsidian_validate` / `pnpm run obsidian-validate`;整理工作流入口时再跑 `kt_obsidian_sync`。
|
- 更新 Obsidian 图谱、模块页、文档矩阵或 `.obsidian` 配置后运行 `kt_obsidian_validate` / `pnpm run obsidian-validate`;整理工作流入口时再跑 `kt_obsidian_sync`。
|
||||||
- 改完文件后用 `kt_append_task_record` 先 `dryRun` 预览记录,再决定是否写入。
|
- 改完文件后用 `kt_append_task_record` 先 `dryRun` 预览记录,再决定是否写入。
|
||||||
- 提交前调用 `kt_commit_checklist`,确认文件范围和提交信息。
|
- 提交前调用 `kt_commit_checklist`,确认文件范围和提交信息。
|
||||||
|
|||||||
@ -65,11 +65,18 @@ export function parseWorkstreamCloseoutCliArgs(argv: string[]): WorkstreamCloseo
|
|||||||
const patternSet = new Set<string>(workstreamReusablePatternValues);
|
const patternSet = new Set<string>(workstreamReusablePatternValues);
|
||||||
const patterns = readOptionList(argv, ['--pattern', '--reusable-pattern'])
|
const patterns = readOptionList(argv, ['--pattern', '--reusable-pattern'])
|
||||||
.filter((pattern): pattern is WorkstreamReusablePattern => patternSet.has(pattern));
|
.filter((pattern): pattern is WorkstreamReusablePattern => patternSet.has(pattern));
|
||||||
|
const cleanupFinalDeleted = Number(
|
||||||
|
readOption(argv, ['--cleanup-final-deleted', '--cleanupFinalDeleted']),
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
changedProjects: readOptionList(argv, ['--project', '--changed-project']),
|
changedProjects: readOptionList(argv, ['--project', '--changed-project']),
|
||||||
|
cleanupEvidence: readOptionList(argv, ['--cleanup', '--cleanup-evidence']),
|
||||||
|
cleanupFinalDeleted: Number.isFinite(cleanupFinalDeleted) ? cleanupFinalDeleted : undefined,
|
||||||
|
docSyncEvidence: readOptionList(argv, ['--doc-sync', '--doc-sync-evidence']),
|
||||||
ktWorkflowUpdated: argv.includes('--kt-workflow-updated'),
|
ktWorkflowUpdated: argv.includes('--kt-workflow-updated'),
|
||||||
problemRecords: readOptionList(argv, ['--problem', '--problem-record']),
|
problemRecords: readOptionList(argv, ['--problem', '--problem-record']),
|
||||||
|
reviewEvidence: readOptionList(argv, ['--review', '--review-evidence']),
|
||||||
reusablePatterns: patterns,
|
reusablePatterns: patterns,
|
||||||
stableSolutions: readOptionList(argv, ['--solution', '--stable-solution']),
|
stableSolutions: readOptionList(argv, ['--solution', '--stable-solution']),
|
||||||
title: readOption(argv, ['--title']),
|
title: readOption(argv, ['--title']),
|
||||||
|
|||||||
@ -249,8 +249,12 @@ export function registerTools(server: McpServer): void {
|
|||||||
description: '生成大方向完结闭环审计:开发、测试证据、问题记录、稳定解法和 ktWorkflow 升级是否齐备。',
|
description: '生成大方向完结闭环审计:开发、测试证据、问题记录、稳定解法和 ktWorkflow 升级是否齐备。',
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
changedProjects: z.array(z.string()).default([]),
|
changedProjects: z.array(z.string()).default([]),
|
||||||
|
cleanupEvidence: z.array(z.string()).default([]),
|
||||||
|
cleanupFinalDeleted: z.number().int().min(0).optional(),
|
||||||
|
docSyncEvidence: z.array(z.string()).default([]),
|
||||||
ktWorkflowUpdated: z.boolean().default(false),
|
ktWorkflowUpdated: z.boolean().default(false),
|
||||||
problemRecords: z.array(z.string()).default([]),
|
problemRecords: z.array(z.string()).default([]),
|
||||||
|
reviewEvidence: z.array(z.string()).default([]),
|
||||||
reusablePatterns: z.array(z.enum(workstreamReusablePatternValues)).default(['none']),
|
reusablePatterns: z.array(z.enum(workstreamReusablePatternValues)).default(['none']),
|
||||||
stableSolutions: z.array(z.string()).default([]),
|
stableSolutions: z.array(z.string()).default([]),
|
||||||
title: z.string().optional(),
|
title: z.string().optional(),
|
||||||
@ -269,7 +273,9 @@ export function registerTools(server: McpServer): void {
|
|||||||
inputSchema: {
|
inputSchema: {
|
||||||
changedProjects: z.array(z.string()).default([]),
|
changedProjects: z.array(z.string()).default([]),
|
||||||
cleanupExecuted: z.boolean().default(false),
|
cleanupExecuted: z.boolean().default(false),
|
||||||
cleanupPreviewDeleted: z.number().int().min(0).default(0),
|
cleanupEvidence: z.array(z.string()).default([]),
|
||||||
|
cleanupFinalDeleted: z.number().int().min(0).optional(),
|
||||||
|
cleanupPreviewDeleted: z.number().int().min(0).optional(),
|
||||||
docSyncEvidence: z.array(z.string()).default([]),
|
docSyncEvidence: z.array(z.string()).default([]),
|
||||||
failureRecords: z.array(z.string()).default([]),
|
failureRecords: z.array(z.string()).default([]),
|
||||||
ktWorkflowUpdated: z.boolean().default(false),
|
ktWorkflowUpdated: z.boolean().default(false),
|
||||||
|
|||||||
@ -101,6 +101,9 @@ export async function runSelfTest(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const closeoutNeedsUpgrade = buildWorkstreamCloseout({
|
const closeoutNeedsUpgrade = buildWorkstreamCloseout({
|
||||||
|
cleanupEvidence: ['cleanup-history dry-run deleted=0。'],
|
||||||
|
docSyncEvidence: ['kt_change_doc_sync requiredDocs 已处理。'],
|
||||||
|
reviewEvidence: ['global-review findings=0。'],
|
||||||
problemRecords: ['Jenkins 状态需要结合日志和 K8s 状态确认。'],
|
problemRecords: ['Jenkins 状态需要结合日志和 K8s 状态确认。'],
|
||||||
reusablePatterns: ['deploy-observation'],
|
reusablePatterns: ['deploy-observation'],
|
||||||
stableSolutions: ['收尾时固定观测 build、commit、镜像、Deployment、Pod 和 smoke。'],
|
stableSolutions: ['收尾时固定观测 build、commit、镜像、Deployment、Pod 和 smoke。'],
|
||||||
@ -112,8 +115,11 @@ export async function runSelfTest(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const closeoutComplete = buildWorkstreamCloseout({
|
const closeoutComplete = buildWorkstreamCloseout({
|
||||||
|
cleanupEvidence: ['cleanup-history dry-run deleted=0。'],
|
||||||
|
docSyncEvidence: ['无需文档更新,已记录原因。'],
|
||||||
ktWorkflowUpdated: true,
|
ktWorkflowUpdated: true,
|
||||||
problemRecords: ['无新卡点。'],
|
problemRecords: ['无新卡点。'],
|
||||||
|
reviewEvidence: ['global-review findings=0。'],
|
||||||
reusablePatterns: ['none'],
|
reusablePatterns: ['none'],
|
||||||
stableSolutions: ['无新增稳定解法。'],
|
stableSolutions: ['无新增稳定解法。'],
|
||||||
title: '文档收尾',
|
title: '文档收尾',
|
||||||
@ -123,6 +129,20 @@ export async function runSelfTest(): Promise<void> {
|
|||||||
throw new Error('workstream closeout complete self-check failed');
|
throw new Error('workstream closeout complete self-check failed');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const closeoutInvalidCleanup = buildWorkstreamCloseout({
|
||||||
|
cleanupEvidence: ['cleanup-history dry-run deleted=3。'],
|
||||||
|
docSyncEvidence: ['kt_change_doc_sync requiredDocs 已处理。'],
|
||||||
|
problemRecords: ['无新卡点。'],
|
||||||
|
reviewEvidence: ['global-review findings=0。'],
|
||||||
|
reusablePatterns: ['none'],
|
||||||
|
stableSolutions: ['无新增稳定解法。'],
|
||||||
|
title: '无效清理证据',
|
||||||
|
verificationEvidence: ['typecheck 通过。'],
|
||||||
|
});
|
||||||
|
if ((closeoutInvalidCleanup.canReportComplete as boolean) !== false) {
|
||||||
|
throw new Error('workstream closeout cleanup evidence self-check failed');
|
||||||
|
}
|
||||||
|
|
||||||
const obsidianContext = readObsidianContext({
|
const obsidianContext = readObsidianContext({
|
||||||
maxDocuments: 6,
|
maxDocuments: 6,
|
||||||
module: 'ktWorkflow',
|
module: 'ktWorkflow',
|
||||||
@ -178,6 +198,7 @@ export async function runSelfTest(): Promise<void> {
|
|||||||
|
|
||||||
const loopAuditComplete = buildWorkflowLoopAudit({
|
const loopAuditComplete = buildWorkflowLoopAudit({
|
||||||
cleanupExecuted: true,
|
cleanupExecuted: true,
|
||||||
|
cleanupFinalDeleted: 0,
|
||||||
cleanupPreviewDeleted: 3,
|
cleanupPreviewDeleted: 3,
|
||||||
docSyncEvidence: ['kt_change_doc_sync requiredDocs 已处理。'],
|
docSyncEvidence: ['kt_change_doc_sync requiredDocs 已处理。'],
|
||||||
ktWorkflowUpdated: true,
|
ktWorkflowUpdated: true,
|
||||||
@ -201,6 +222,28 @@ export async function runSelfTest(): Promise<void> {
|
|||||||
throw new Error('workflow loop blocked self-check failed');
|
throw new Error('workflow loop blocked self-check failed');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loopAuditMissingCleanup = buildWorkflowLoopAudit({
|
||||||
|
docSyncEvidence: ['kt_change_doc_sync requiredDocs 已处理。'],
|
||||||
|
reviewEvidence: ['global-review findings=0。'],
|
||||||
|
taskTitle: '缺清理证据闭环',
|
||||||
|
testCases: ['self-test'],
|
||||||
|
verificationEvidence: ['typecheck 通过。'],
|
||||||
|
});
|
||||||
|
if ((loopAuditMissingCleanup.canReportComplete as boolean) !== false) {
|
||||||
|
throw new Error('workflow loop missing cleanup evidence self-check failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
const dbSyncPlan = buildDbSyncPlan({});
|
||||||
|
const dbSyncCommands = (dbSyncPlan.commands as string[]).join('\n');
|
||||||
|
const backupName = dbSyncPlan.backupName as string;
|
||||||
|
if (
|
||||||
|
!backupName.includes('_') ||
|
||||||
|
!dbSyncCommands.includes('.kt-workspace/db-sync') ||
|
||||||
|
dbSyncCommands.includes('CREATE DATABASE IF NOT EXISTS')
|
||||||
|
) {
|
||||||
|
throw new Error('DB sync plan safety self-check failed');
|
||||||
|
}
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
context: readWorkflowContext({ taskRecordCount: 2 }),
|
context: readWorkflowContext({ taskRecordCount: 2 }),
|
||||||
guardrails: buildGuardrails({
|
guardrails: buildGuardrails({
|
||||||
@ -258,7 +301,7 @@ export async function runSelfTest(): Promise<void> {
|
|||||||
componentWorkflow: buildComponentWorkflow({
|
componentWorkflow: buildComponentWorkflow({
|
||||||
target: 'SystemLog',
|
target: 'SystemLog',
|
||||||
}),
|
}),
|
||||||
dbSyncPlan: buildDbSyncPlan({}),
|
dbSyncPlan,
|
||||||
finishTask: await buildFinishTask({
|
finishTask: await buildFinishTask({
|
||||||
cleanupHistory: false,
|
cleanupHistory: false,
|
||||||
projects: ['mcp'],
|
projects: ['mcp'],
|
||||||
|
|||||||
13
src/tools/cleanupEvidence.ts
Normal file
13
src/tools/cleanupEvidence.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
export function cleanupEvidenceShowsDeletedZero(values: string[]): boolean {
|
||||||
|
const text = values.join('\n');
|
||||||
|
return [
|
||||||
|
/\bdeleted\s*=\s*0\b/i,
|
||||||
|
/\bdeleted\s*:\s*0\b/i,
|
||||||
|
/["']deleted["']\s*:\s*\[\s*]/i,
|
||||||
|
/\bdeleted\s*[=:]\s*\[\s*]/i,
|
||||||
|
].some((pattern) => pattern.test(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeCleanupDeletedCount(value?: number): number | undefined {
|
||||||
|
return value === undefined ? undefined : Math.max(0, value);
|
||||||
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
import type { WorkstreamCloseoutInput, WorkstreamReusablePattern } from '../types.js';
|
import type { WorkstreamCloseoutInput, WorkstreamReusablePattern } from '../types.js';
|
||||||
import { workstreamReusablePatternValues } from '../core/constants.js';
|
import { workstreamReusablePatternValues } from '../core/constants.js';
|
||||||
|
import { cleanupEvidenceShowsDeletedZero, normalizeCleanupDeletedCount } from './cleanupEvidence.js';
|
||||||
import { defaultReviewProjects } from './review.js';
|
import { defaultReviewProjects } from './review.js';
|
||||||
|
|
||||||
interface UpgradeTarget {
|
interface UpgradeTarget {
|
||||||
@ -72,8 +73,14 @@ function buildPhase(name: string, evidence: string[], emptyHint: string): Record
|
|||||||
export function buildWorkstreamCloseout(input: WorkstreamCloseoutInput = {}): Record<string, unknown> {
|
export function buildWorkstreamCloseout(input: WorkstreamCloseoutInput = {}): Record<string, unknown> {
|
||||||
const title = input.title?.trim() || '';
|
const title = input.title?.trim() || '';
|
||||||
const changedProjects = normalizeList(input.changedProjects);
|
const changedProjects = normalizeList(input.changedProjects);
|
||||||
|
const cleanupEvidence = normalizeList(input.cleanupEvidence);
|
||||||
|
const cleanupFinalDeleted = normalizeCleanupDeletedCount(input.cleanupFinalDeleted);
|
||||||
|
const cleanupOk =
|
||||||
|
cleanupFinalDeleted === 0 || cleanupEvidenceShowsDeletedZero(cleanupEvidence);
|
||||||
|
const docSyncEvidence = normalizeList(input.docSyncEvidence);
|
||||||
const verificationEvidence = normalizeList(input.verificationEvidence);
|
const verificationEvidence = normalizeList(input.verificationEvidence);
|
||||||
const problemRecords = normalizeList(input.problemRecords);
|
const problemRecords = normalizeList(input.problemRecords);
|
||||||
|
const reviewEvidence = normalizeList(input.reviewEvidence);
|
||||||
const stableSolutions = normalizeList(input.stableSolutions);
|
const stableSolutions = normalizeList(input.stableSolutions);
|
||||||
const patterns = normalizePatterns(input.reusablePatterns);
|
const patterns = normalizePatterns(input.reusablePatterns);
|
||||||
const upgradeNeeded = patterns.some((pattern) => pattern !== 'none');
|
const upgradeNeeded = patterns.some((pattern) => pattern !== 'none');
|
||||||
@ -81,6 +88,9 @@ export function buildWorkstreamCloseout(input: WorkstreamCloseoutInput = {}): Re
|
|||||||
const missingItems = [
|
const missingItems = [
|
||||||
title ? '' : '填写本轮大方向名称或目标。',
|
title ? '' : '填写本轮大方向名称或目标。',
|
||||||
verificationEvidence.length > 0 ? '' : '补充测试或 smoke 验证证据;只有计划不算闭环完成。',
|
verificationEvidence.length > 0 ? '' : '补充测试或 smoke 验证证据;只有计划不算闭环完成。',
|
||||||
|
docSyncEvidence.length > 0 ? '' : '补充文档同步证据;无需更新时也要显式写明原因。',
|
||||||
|
cleanupOk ? '' : '补充历史产物清理证据;至少包含 cleanup-history dry-run deleted=0 或结构化 cleanupFinalDeleted=0。',
|
||||||
|
reviewEvidence.length > 0 ? '' : '补充全局 review 证据。',
|
||||||
problemRecords.length > 0 ? '' : '补充问题记录;如无新卡点,显式写“无新卡点”。',
|
problemRecords.length > 0 ? '' : '补充问题记录;如无新卡点,显式写“无新卡点”。',
|
||||||
stableSolutions.length > 0 ? '' : '补充稳定解法;如无新解法,显式写“无新增稳定解法”。',
|
stableSolutions.length > 0 ? '' : '补充稳定解法;如无新解法,显式写“无新增稳定解法”。',
|
||||||
upgradeNeeded && !ktWorkflowUpdated
|
upgradeNeeded && !ktWorkflowUpdated
|
||||||
@ -98,6 +108,21 @@ export function buildWorkstreamCloseout(input: WorkstreamCloseoutInput = {}): Re
|
|||||||
closeoutOrder: [
|
closeoutOrder: [
|
||||||
buildPhase('development', title ? [title] : [], '写清本轮完成的开发目标。'),
|
buildPhase('development', title ? [title] : [], '写清本轮完成的开发目标。'),
|
||||||
buildPhase('test/smoke verification', verificationEvidence, '补一条可复验的验证证据。'),
|
buildPhase('test/smoke verification', verificationEvidence, '补一条可复验的验证证据。'),
|
||||||
|
buildPhase('documentation sync', docSyncEvidence, '补充 kt_change_doc_sync 证据或无需更新说明。'),
|
||||||
|
{
|
||||||
|
evidence: [
|
||||||
|
...cleanupEvidence,
|
||||||
|
...(cleanupFinalDeleted === undefined
|
||||||
|
? []
|
||||||
|
: [`cleanupFinalDeleted=${cleanupFinalDeleted}`]),
|
||||||
|
],
|
||||||
|
name: 'history cleanup',
|
||||||
|
status: cleanupOk ? 'ok' : 'missing',
|
||||||
|
suggestion: cleanupOk
|
||||||
|
? undefined
|
||||||
|
: '补充 cleanup-history dry-run deleted=0 证据或传入 cleanupFinalDeleted=0。',
|
||||||
|
},
|
||||||
|
buildPhase('global review', reviewEvidence, '补充 kt_global_code_review 或 global-review 证据。'),
|
||||||
buildPhase('problem record', problemRecords, '记录问题点;没有问题也要显式写明。'),
|
buildPhase('problem record', problemRecords, '记录问题点;没有问题也要显式写明。'),
|
||||||
buildPhase('stable solution', stableSolutions, '记录稳定解法;没有新增解法也要显式写明。'),
|
buildPhase('stable solution', stableSolutions, '记录稳定解法;没有新增解法也要显式写明。'),
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import type { WorkflowLoopAuditInput } from '../types.js';
|
import type { WorkflowLoopAuditInput } from '../types.js';
|
||||||
|
import { cleanupEvidenceShowsDeletedZero, normalizeCleanupDeletedCount } from './cleanupEvidence.js';
|
||||||
|
|
||||||
interface LoopGate {
|
interface LoopGate {
|
||||||
evidence: string[];
|
evidence: string[];
|
||||||
@ -43,8 +44,18 @@ export function buildWorkflowLoopAudit(input: WorkflowLoopAuditInput = {}): Reco
|
|||||||
const problemRecords = normalizeList(input.problemRecords);
|
const problemRecords = normalizeList(input.problemRecords);
|
||||||
const stableSolutions = normalizeList(input.stableSolutions);
|
const stableSolutions = normalizeList(input.stableSolutions);
|
||||||
const reviewEvidence = normalizeList(input.reviewEvidence);
|
const reviewEvidence = normalizeList(input.reviewEvidence);
|
||||||
const cleanupPreviewDeleted = Math.max(0, input.cleanupPreviewDeleted ?? 0);
|
const cleanupEvidence = normalizeList(input.cleanupEvidence);
|
||||||
const cleanupOk = cleanupPreviewDeleted === 0 || input.cleanupExecuted === true;
|
const cleanupPreviewDeleted = normalizeCleanupDeletedCount(
|
||||||
|
input.cleanupPreviewDeleted,
|
||||||
|
);
|
||||||
|
const cleanupFinalDeleted = normalizeCleanupDeletedCount(
|
||||||
|
input.cleanupFinalDeleted,
|
||||||
|
);
|
||||||
|
const cleanupEvidenceOk = cleanupEvidenceShowsDeletedZero(cleanupEvidence);
|
||||||
|
const cleanupOk =
|
||||||
|
cleanupEvidenceOk ||
|
||||||
|
cleanupFinalDeleted === 0 ||
|
||||||
|
cleanupPreviewDeleted === 0;
|
||||||
const hasFailure = failureRecords.length > 0;
|
const hasFailure = failureRecords.length > 0;
|
||||||
const reusablePattern = input.reusablePattern === true;
|
const reusablePattern = input.reusablePattern === true;
|
||||||
const needsSolidification = hasFailure || reusablePattern;
|
const needsSolidification = hasFailure || reusablePattern;
|
||||||
@ -59,11 +70,19 @@ export function buildWorkflowLoopAudit(input: WorkflowLoopAuditInput = {}): Reco
|
|||||||
cleanupOk,
|
cleanupOk,
|
||||||
cleanupOk
|
cleanupOk
|
||||||
? [
|
? [
|
||||||
cleanupPreviewDeleted === 0
|
cleanupEvidenceOk
|
||||||
|
? 'cleanup-history evidence deleted=0'
|
||||||
|
: cleanupPreviewDeleted === 0
|
||||||
? 'cleanup-history dry-run deleted=0'
|
? 'cleanup-history dry-run deleted=0'
|
||||||
: 'cleanup-history executed after stale artifacts were found',
|
: 'cleanup-history executed and final dry-run deleted=0',
|
||||||
]
|
]
|
||||||
: [`cleanup-history dry-run deleted=${cleanupPreviewDeleted}`],
|
: [
|
||||||
|
cleanupFinalDeleted === undefined
|
||||||
|
? cleanupPreviewDeleted === undefined
|
||||||
|
? 'cleanup-history evidence missing'
|
||||||
|
: `cleanup-history dry-run deleted=${cleanupPreviewDeleted}; final dry-run missing`
|
||||||
|
: `cleanup-history final dry-run deleted=${cleanupFinalDeleted}`,
|
||||||
|
],
|
||||||
'执行 kt_cleanup_history dryRun=false 或 pnpm run cleanup-history -- --execute,并复跑 dry-run 确认 deleted=0。',
|
'执行 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('documentation sync evidence', docSyncEvidence, '运行 kt_change_doc_sync 并同步 README/API/AGENTS/docs/Obsidian/skills/ktWorkflow 中需要更新的入口;无需更新时写明原因。'),
|
||||||
@ -136,7 +155,9 @@ export function buildWorkflowLoopAudit(input: WorkflowLoopAuditInput = {}): Reco
|
|||||||
stage,
|
stage,
|
||||||
summary: {
|
summary: {
|
||||||
changedProjects,
|
changedProjects,
|
||||||
|
cleanupFinalDeleted,
|
||||||
cleanupPreviewDeleted,
|
cleanupPreviewDeleted,
|
||||||
|
cleanupEvidenceCount: cleanupEvidence.length,
|
||||||
docSyncEvidenceCount: docSyncEvidence.length,
|
docSyncEvidenceCount: docSyncEvidence.length,
|
||||||
failureCount: failureRecords.length,
|
failureCount: failureRecords.length,
|
||||||
problemRecordCount: problemRecords.length,
|
problemRecordCount: problemRecords.length,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import type { CommitPlanInput, ComponentWorkflowInput, DbSyncPlanInput, ExecResult, FinishTaskInput, PushPlanInput, RemoteHealthCheckInput, RepoType, ResolvedProject } from '../types.js';
|
import type { CommitPlanInput, ComponentWorkflowInput, DbSyncPlanInput, ExecResult, FinishTaskInput, PushPlanInput, RemoteHealthCheckInput, RepoType, ResolvedProject } from '../types.js';
|
||||||
import { tryExecFile, tryPowerShell } from '../core/exec.js';
|
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 { 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 { karpathyChangedFileChecklistItem, karpathyFinishNote, karpathyStopCondition } from './karpathy.js';
|
||||||
import { buildGlobalCodeReview, defaultReviewProjects } from './review.js';
|
import { buildGlobalCodeReview, defaultReviewProjects } from './review.js';
|
||||||
@ -21,6 +21,25 @@ function normalizeProjectKeys(projects?: string[]): string[] {
|
|||||||
return Array.from(new Set(selected.filter((project) => project !== 'root')));
|
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',
|
||||||
|
hour12: false,
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
async function collectRepoSnapshot(projectKey: string): Promise<RepoSnapshot> {
|
async function collectRepoSnapshot(projectKey: string): Promise<RepoSnapshot> {
|
||||||
const project = resolveProject(projectKey);
|
const project = resolveProject(projectKey);
|
||||||
const repoType = detectRepoType(project.path);
|
const repoType = detectRepoType(project.path);
|
||||||
@ -274,17 +293,22 @@ export async function buildRemoteHealthCheck(input: RemoteHealthCheckInput = {})
|
|||||||
export function buildDbSyncPlan(input: DbSyncPlanInput = {}): Record<string, unknown> {
|
export function buildDbSyncPlan(input: DbSyncPlanInput = {}): Record<string, unknown> {
|
||||||
const source = input.source || 'local';
|
const source = input.source || 'local';
|
||||||
const target = input.target || 'remote';
|
const target = input.target || 'remote';
|
||||||
const backupName = input.backupName || `kt_template_backup_${formatDateInShanghai().replaceAll('-', '')}`;
|
const backupName = normalizeDbIdentifier(
|
||||||
|
input.backupName ||
|
||||||
|
`kt_template_backup_${formatShanghaiTimestamp()}_${Math.random().toString(36).slice(2, 8)}`,
|
||||||
|
);
|
||||||
|
const dumpPath = `.kt-workspace/db-sync/${backupName}/kt_template.sql`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
backupName,
|
backupName,
|
||||||
commands: [
|
commands: [
|
||||||
'从 Node/kt-template-online-api/.env.development 和 .env.production 读取连接信息,只在本机 shell 环境使用,不写入 Git。',
|
'从 Node/kt-template-online-api/.env.development 和 .env.production 读取连接信息,只在本机 shell 环境使用,不写入 Git。',
|
||||||
'KT 当前约定:127.0.0.1:3306 是本地 MySQL,127.0.0.1:3307 是远程 NAS 打洞连接;3307 不通时先建立 ssh -N -L 127.0.0.1:3307:127.0.0.1:3306 nas。',
|
'KT 当前约定:127.0.0.1:3306 是本地 MySQL,127.0.0.1:3307 是远程 NAS 打洞连接;3307 不通时先建立 ssh -N -L 127.0.0.1:3307:127.0.0.1:3306 nas。',
|
||||||
'mysqldump --set-gtid-purged=OFF --single-transaction --routines --triggers <source-conn> kt_template > .codex-db-sync/kt_template.sql',
|
`mkdir -p .kt-workspace/db-sync/${backupName}`,
|
||||||
`mysql <target-conn> -e "CREATE DATABASE IF NOT EXISTS ${backupName} DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"`,
|
`mysqldump --set-gtid-purged=OFF --single-transaction --routines --triggers <source-conn> kt_template > ${dumpPath}`,
|
||||||
|
`mysql <target-conn> -e "CREATE DATABASE ${backupName} DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"`,
|
||||||
`mysqldump --set-gtid-purged=OFF --single-transaction --routines --triggers <target-conn> kt_template | mysql <target-conn> ${backupName}`,
|
`mysqldump --set-gtid-purged=OFF --single-transaction --routines --triggers <target-conn> kt_template | mysql <target-conn> ${backupName}`,
|
||||||
'mysql <target-conn> kt_template < .codex-db-sync/kt_template.sql',
|
`mysql <target-conn> kt_template < ${dumpPath}`,
|
||||||
'对比源库/目标库表数量、视图/函数/过程数量和关键表行数。',
|
'对比源库/目标库表数量、视图/函数/过程数量和关键表行数。',
|
||||||
`验证通过后再执行:mysql <target-conn> -e "DROP DATABASE ${backupName};"`,
|
`验证通过后再执行:mysql <target-conn> -e "DROP DATABASE ${backupName};"`,
|
||||||
],
|
],
|
||||||
|
|||||||
@ -253,8 +253,12 @@ export interface WorkflowContextInput {
|
|||||||
|
|
||||||
export interface WorkstreamCloseoutInput {
|
export interface WorkstreamCloseoutInput {
|
||||||
changedProjects?: string[];
|
changedProjects?: string[];
|
||||||
|
cleanupEvidence?: string[];
|
||||||
|
cleanupFinalDeleted?: number;
|
||||||
|
docSyncEvidence?: string[];
|
||||||
ktWorkflowUpdated?: boolean;
|
ktWorkflowUpdated?: boolean;
|
||||||
problemRecords?: string[];
|
problemRecords?: string[];
|
||||||
|
reviewEvidence?: string[];
|
||||||
reusablePatterns?: WorkstreamReusablePattern[];
|
reusablePatterns?: WorkstreamReusablePattern[];
|
||||||
stableSolutions?: string[];
|
stableSolutions?: string[];
|
||||||
title?: string;
|
title?: string;
|
||||||
@ -284,6 +288,8 @@ export interface ObsidianContextInput {
|
|||||||
export interface WorkflowLoopAuditInput {
|
export interface WorkflowLoopAuditInput {
|
||||||
changedProjects?: string[];
|
changedProjects?: string[];
|
||||||
cleanupExecuted?: boolean;
|
cleanupExecuted?: boolean;
|
||||||
|
cleanupEvidence?: string[];
|
||||||
|
cleanupFinalDeleted?: number;
|
||||||
cleanupPreviewDeleted?: number;
|
cleanupPreviewDeleted?: number;
|
||||||
docSyncEvidence?: string[];
|
docSyncEvidence?: string[];
|
||||||
failureRecords?: string[];
|
failureRecords?: string[];
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user