238 lines
9.2 KiB
TypeScript
238 lines
9.2 KiB
TypeScript
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 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));
|
||
}
|