import { mkdirSync, writeFileSync } from 'node:fs'; import path from 'node:path'; import { parseGlobalReviewCliArgs } from './core/cli.js'; import { registeredToolNames } from './core/constants.js'; import { buildBlockerResolution } from './tools/blocker.js'; import { buildChangeDocSync } from './tools/docSync.js'; import { resolveInsideRoot } from './core/workspace.js'; import { buildGuardrails } from './tools/guardrails.js'; import { readObsidianContext, syncObsidianWorkflow, validateObsidianVault } from './tools/obsidian.js'; import { cleanupHistoryArtifacts, parseCliCleanupArgs } from './tools/cleanup.js'; import { buildWorkflowLoopAudit } from './tools/loop.js'; import { inspectProject } from './tools/inspect.js'; 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 = { localizedSecretLabelAllowed: isBenignCredentialReviewValue('密钥'), realTokenRejected: !isBenignCredentialReviewValue('eqc2Sbp8ufP2AcS6I2YReLqxnu3XJKC6D7C6PUcx'), }; if (!reviewClassifier.localizedSecretLabelAllowed || !reviewClassifier.realTokenRejected) { throw new Error('review credential classifier self-check failed'); } const reviewCliParser = { dashedAll: parseGlobalReviewCliArgs(['node', 'server', '--global-review', '--content-scan-all']).contentScanMode, keyValueAll: parseGlobalReviewCliArgs(['node', 'server', '--global-review', '--contentScanMode=all']).contentScanMode, splitAll: parseGlobalReviewCliArgs(['node', 'server', '--global-review', '--content-scan-mode', 'all']).contentScanMode, }; if ( reviewCliParser.dashedAll !== 'all' || reviewCliParser.keyValueAll !== 'all' || reviewCliParser.splitAll !== 'all' ) { throw new Error('global-review CLI content scan mode parser self-check failed'); } const refactorGuardrails = buildGuardrails({ project: 'mcp', taskType: 'refactor', userRequest: '重构 KT 工作流护栏', }); const refactorGuardrailOk = refactorGuardrails.guardrails.beforeEdit.some((item) => item.includes('重构前写清楚')) && refactorGuardrails.guardrails.verification.notes.some((item) => item.includes('重构验证必须覆盖原行为')); if (!refactorGuardrailOk) { throw new Error('refactor guardrail self-check failed'); } const 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 obsidianContext = readObsidianContext({ maxDocuments: 6, module: 'ktWorkflow', }); if ((obsidianContext.summary as { returnedDocuments?: number }).returnedDocuments === 0) { throw new Error('obsidian context self-check failed'); } if ((obsidianContext.summary as { codeAnchors?: number }).codeAnchors === 0) { throw new Error('obsidian code anchor self-check failed'); } const obsidianValidation = validateObsidianVault({ includeStaleReferences: false, }); if ((obsidianValidation.summary as { errors?: number }).errors !== 0) { throw new Error('obsidian validation self-check failed'); } const cleanupCliParser = { defaultDryRun: parseCliCleanupArgs(['node', 'server', '--cleanup-history']).dryRun, executeDryRun: parseCliCleanupArgs(['node', 'server', '--cleanup-history', '--execute']).dryRun, }; if (!cleanupCliParser.defaultDryRun || cleanupCliParser.executeDryRun) { throw new Error('cleanup CLI dry-run parser self-check failed'); } const cleanupPreview = cleanupHistoryArtifacts({ dryRun: true, keep: 3, roots: ['.kt-workspace/test-artifacts', '.kt-workspace'], }); const cleanupRoots = cleanupPreview.roots as Array<{ preservedNames: string[]; root: string; }>; const testArtifactsRoot = cleanupRoots.find((item) => item.root === '.kt-workspace/test-artifacts'); const workspaceHistoryRoot = cleanupRoots.find((item) => item.root === '.kt-workspace'); if (!testArtifactsRoot?.preservedNames.includes('_templates')) { throw new Error('cleanup template preservation self-check failed'); } if (!workspaceHistoryRoot?.preservedNames.includes('test-artifacts')) { throw new Error('cleanup workspace root preservation self-check failed'); } const docSyncBangDream = await buildChangeDocSync({ changedFiles: ['src/qqbot/plugins/bangDream/application/bangdream-application.service.ts'], project: 'api', }); const docSyncRequiredDocs = docSyncBangDream.requiredDocs as Array<{ path: string }>; if (!docSyncRequiredDocs.some((item) => item.path === 'Node/kt-template-online-api/API.md')) { throw new Error('doc sync API self-check failed'); } const loopAuditComplete = buildWorkflowLoopAudit({ cleanupExecuted: true, cleanupPreviewDeleted: 3, docSyncEvidence: ['kt_change_doc_sync requiredDocs 已处理。'], ktWorkflowUpdated: true, problemRecords: ['测试历史清理流程未自动执行。'], reusablePattern: true, reviewEvidence: ['global-review findings=0'], stableSolutions: ['cleanup-history 默认 dry-run,显式 --execute 后复验 deleted=0。'], taskTitle: '自动化闭环审计', testCases: ['cleanup-history dry-run/execute 回归测试'], verificationEvidence: ['typecheck/self-test/cleanup dry-run 通过'], }); if ((loopAuditComplete.canReportComplete as boolean) !== true) { throw new Error('workflow loop complete self-check failed'); } const loopAuditBlocked = buildWorkflowLoopAudit({ cleanupPreviewDeleted: 1, taskTitle: '缺证据闭环', }); if ((loopAuditBlocked.canReportComplete as boolean) !== false) { throw new Error('workflow loop blocked self-check failed'); } const data = { context: readWorkflowContext({ taskRecordCount: 2 }), guardrails: buildGuardrails({ project: 'mcp', taskType: 'mcp', userRequest: '扩展 KT 工作区 MCP 可复用能力', }), cleanupCliParser, cleanupPreview, docSyncBangDream, loopAuditBlocked, loopAuditComplete, historyCleanup: cleanupHistoryArtifacts({ dryRun: true, keep: 3, }), inspectAdmin: await inspectProject({ includeGit: false, project: 'admin' }), inspectKnife4j: await inspectProject({ includeGit: false, project: 'knife4j' }), prepareTask: await prepareTask({ includeGit: false, project: 'mcp', taskType: 'mcp', userRequest: '扩展 KT 工作区 MCP 可复用能力', }), review: await buildGlobalCodeReview({ includeContentScan: true, includeRootScan: true, maxFindingsPerProject: 10, projects: ['mcp'], }), reviewCliParser, reviewClassifier, refactorGuardrails, businessTestPlan: buildBusinessTestPlan({ flow: 'system-log-visualization', }), closeoutComplete, closeoutNeedsUpgrade, obsidianContext, obsidianSync: syncObsidianWorkflow({ includeStaleReferences: false, }), obsidianValidation, blockerResolution: buildBlockerResolution({ attempts: 2, evidence: 'self-test', problem: '重复超时', project: 'mcp', solution: '改用固定脚本和 timeout 后再验证', }), commitPlan: await buildCommitPlan({ projects: ['mcp'], }), componentWorkflow: buildComponentWorkflow({ target: 'SystemLog', }), dbSyncPlan: buildDbSyncPlan({}), finishTask: await buildFinishTask({ cleanupHistory: false, projects: ['mcp'], runValidation: false, }), pushPlan: await buildPushPlan({ projects: ['mcp'], }), remoteHealthCheck: await buildRemoteHealthCheck({ execute: false, services: ['ssh', 'docker'], }), verification: buildVerificationPlan({ changeType: 'mcp', project: 'mcp', }), }; const outputDir = resolveInsideRoot('.kt-workspace/verify/ktWorkflow'); mkdirSync(outputDir, { recursive: true }); writeFileSync( path.join(outputDir, 'self-test.json'), JSON.stringify(data, null, 2), 'utf8', ); console.log(JSON.stringify({ ok: true, outputDir, tools: registeredToolNames.length }, null, 2)); }