ktworkflow-mcp/src/selfTest.ts

238 lines
9.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<void> {
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 SUCCESSK8s 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));
}