chore: 收敛工作区资产清理规则
This commit is contained in:
parent
1abfcbf59d
commit
e2a50f03b3
@ -92,7 +92,9 @@ export const defaultHistoryRoots = [
|
||||
'.kt-workspace/test-logs',
|
||||
'.kt-workspace/verify',
|
||||
'.kt-workspace/playwright-mcp',
|
||||
'.kt-workspace/subagents',
|
||||
'.kt-workspace/db-sync',
|
||||
'.kt-workspace/exports',
|
||||
'.kt-workspace/tmp',
|
||||
'.kt-workspace/logs',
|
||||
'.kt-workspace/deploy',
|
||||
|
||||
@ -178,7 +178,7 @@ export function registerTools(server: McpServer): void {
|
||||
server.registerTool(
|
||||
'kt_cleanup_history',
|
||||
{
|
||||
description: '清理 KT 运行态历史产物。默认 dryRun=true,只预览;执行时按 LastWriteTime 每个 .kt-workspace 历史目录仅保留最近 3 轮,template/_templates 等模板目录永久保留。',
|
||||
description: '清理 KT 运行态历史产物。默认 dryRun=true,只预览;执行时按 LastWriteTime 每个 .kt-workspace 历史目录仅保留最近 3 轮;可复用模板和参考资产应迁入 workspace-assets。',
|
||||
inputSchema: {
|
||||
dryRun: z.boolean().default(true),
|
||||
keep: z.number().int().min(0).max(50).default(3),
|
||||
|
||||
@ -1957,12 +1957,18 @@ export async function runSelfTest(): Promise<void> {
|
||||
const workspaceHistoryRoot = cleanupRoots.find(
|
||||
(item) => item.root === ".kt-workspace",
|
||||
);
|
||||
if (!testArtifactsRoot?.preservedNames.includes("_templates")) {
|
||||
throw new Error("cleanup template preservation self-check failed");
|
||||
if (testArtifactsRoot?.preservedNames.includes("_templates")) {
|
||||
throw new Error("cleanup template migration self-check failed");
|
||||
}
|
||||
if (!workspaceHistoryRoot?.preservedNames.includes("test-artifacts")) {
|
||||
throw new Error("cleanup workspace root preservation self-check failed");
|
||||
}
|
||||
if (
|
||||
workspaceHistoryRoot.preservedNames.includes("external") ||
|
||||
workspaceHistoryRoot.preservedNames.includes("sources")
|
||||
) {
|
||||
throw new Error("cleanup workspace cache preservation self-check failed");
|
||||
}
|
||||
|
||||
const docSyncBangDream = await buildChangeDocSync({
|
||||
changedFiles: [
|
||||
@ -2040,6 +2046,21 @@ export async function runSelfTest(): Promise<void> {
|
||||
throw new Error("NapCat device profile guardrail self-check failed");
|
||||
}
|
||||
|
||||
const review = await buildGlobalCodeReview({
|
||||
includeContentScan: true,
|
||||
includeRootScan: true,
|
||||
maxFindingsPerProject: 10,
|
||||
projects: ["mcp"],
|
||||
});
|
||||
const reviewFindings = review.findings as Array<{ category?: string }>;
|
||||
if (
|
||||
reviewFindings.some((finding) =>
|
||||
finding.category?.startsWith("workspace-assets-"),
|
||||
)
|
||||
) {
|
||||
throw new Error("workspace assets review self-check failed");
|
||||
}
|
||||
|
||||
const data = {
|
||||
context: readWorkflowContext({ taskRecordCount: 2 }),
|
||||
guardrails: buildGuardrails({
|
||||
@ -2067,12 +2088,7 @@ export async function runSelfTest(): Promise<void> {
|
||||
taskType: "mcp",
|
||||
userRequest: "扩展 KT 工作区 MCP 可复用能力",
|
||||
}),
|
||||
review: await buildGlobalCodeReview({
|
||||
includeContentScan: true,
|
||||
includeRootScan: true,
|
||||
maxFindingsPerProject: 10,
|
||||
projects: ["mcp"],
|
||||
}),
|
||||
review,
|
||||
reviewCliParser,
|
||||
reviewClassifier,
|
||||
refactorGuardrails,
|
||||
|
||||
@ -36,32 +36,31 @@ interface HistoryEntry {
|
||||
type: 'directory' | 'file';
|
||||
}
|
||||
|
||||
const alwaysPreservedTemplateNames = new Set(['_template', '_templates', 'template', 'templates']);
|
||||
const workspaceRootPreservedNames = new Set([
|
||||
'backup',
|
||||
'db-sync',
|
||||
'deploy',
|
||||
'external',
|
||||
'exports',
|
||||
'logs',
|
||||
'playwright-mcp',
|
||||
'sources',
|
||||
'subagents',
|
||||
'test-artifacts',
|
||||
'test-logs',
|
||||
'tmp',
|
||||
'verify',
|
||||
]);
|
||||
|
||||
function isTemplateEntryName(name: string): boolean {
|
||||
return alwaysPreservedTemplateNames.has(name.toLowerCase());
|
||||
}
|
||||
|
||||
function buildPreservedNames(rootRelativePath: string, childNames: string[]): Set<string> {
|
||||
/**
|
||||
* Builds the directory names that represent generated artifact categories under
|
||||
* `.kt-workspace`; reusable templates and references are intentionally not
|
||||
* preserved here because they belong in `workspace-assets`.
|
||||
*
|
||||
* @param rootRelativePath - Cleanup root relative to the KT workspace.
|
||||
* @returns Category names that cleanup should skip when pruning `.kt-workspace`.
|
||||
*/
|
||||
function buildPreservedNames(rootRelativePath: string): Set<string> {
|
||||
const preservedNames = new Set<string>();
|
||||
|
||||
for (const name of childNames) {
|
||||
if (isTemplateEntryName(name)) preservedNames.add(name);
|
||||
}
|
||||
|
||||
if (rootRelativePath === '.kt-workspace') {
|
||||
for (const name of workspaceRootPreservedNames) {
|
||||
preservedNames.add(name);
|
||||
@ -126,8 +125,7 @@ export function cleanupHistoryArtifacts(input: CleanupHistoryInput = {}): Record
|
||||
continue;
|
||||
}
|
||||
|
||||
const childNames = readdirSync(rootPath, { withFileTypes: true }).map((item) => item.name);
|
||||
const preservedNames = buildPreservedNames(rootRelativePath, childNames);
|
||||
const preservedNames = buildPreservedNames(rootRelativePath);
|
||||
const entries = listHistoryEntries(rootPath, preservedNames);
|
||||
const preserved = entries.slice(0, keep);
|
||||
const deleting = entries.slice(keep);
|
||||
|
||||
@ -172,7 +172,6 @@ const requiredCorePlugins = [
|
||||
const expectedIgnoredRoots = [
|
||||
'.git/',
|
||||
'.kt-workspace/',
|
||||
'_exports/',
|
||||
'Node/',
|
||||
'Plugins/',
|
||||
'Vue/',
|
||||
@ -278,7 +277,6 @@ const ignoredDirectoryNames = new Set([
|
||||
'.pnpm',
|
||||
'.turbo',
|
||||
'.vite',
|
||||
'_exports',
|
||||
'build',
|
||||
'coverage',
|
||||
'dist',
|
||||
|
||||
@ -41,10 +41,15 @@ const rootGeneratedArtifactNames = new Set([
|
||||
".codex-test-logs",
|
||||
".codex-verify",
|
||||
".playwright-mcp",
|
||||
"_exports",
|
||||
"codex-test-logs",
|
||||
"test-artifacts",
|
||||
]);
|
||||
|
||||
const workspaceAssetBlockedFilePattern =
|
||||
/(^|\/)(\.env($|\..+)|.*\.(7z|dump|gz|rar|sql|storageState[^/]*|tar|tgz|zip)|id_rsa|[^/]+\.(key|pem|pfx|p12))$/i;
|
||||
const workspaceAssetMaxFileBytes = 2 * 1024 * 1024;
|
||||
|
||||
const sensitiveTrackedFilePattern =
|
||||
/(^|\/)(\.env($|\..+)|deploy\.json|secrets?\.(env|json|ya?ml)|id_rsa|[^/]+\.(pem|key))$/i;
|
||||
|
||||
@ -990,6 +995,64 @@ function findRootGeneratedArtifacts(): string[] {
|
||||
.map((entry) => entry.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds files that would make `workspace-assets` behave like another runtime
|
||||
* dump/cache directory instead of a small reusable asset store.
|
||||
*
|
||||
* @returns Review findings for large, archived, dump-like, or secret-like files
|
||||
* found under `workspace-assets`.
|
||||
*/
|
||||
function findWorkspaceAssetPolicyFindings(): ReviewFinding[] {
|
||||
const assetRoot = path.join(workspaceRoot, "workspace-assets");
|
||||
if (!existsSync(assetRoot)) return [];
|
||||
|
||||
const findings: ReviewFinding[] = [];
|
||||
|
||||
const walk = (currentPath: string) => {
|
||||
for (const entry of readdirSync(currentPath, { withFileTypes: true })) {
|
||||
const entryPath = path.join(currentPath, entry.name);
|
||||
const relativePath = toPosix(path.relative(workspaceRoot, entryPath));
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
if (reviewSkipDirectoryNames.has(entry.name)) continue;
|
||||
walk(entryPath);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!entry.isFile()) continue;
|
||||
|
||||
const stats = statSync(entryPath);
|
||||
if (workspaceAssetBlockedFilePattern.test(relativePath)) {
|
||||
findings.push({
|
||||
category: "workspace-assets-blocked-file",
|
||||
file: relativePath,
|
||||
level: "P1",
|
||||
message: "workspace-assets 中出现禁止跟踪的归档、dump、环境或密钥类文件。",
|
||||
project: "root",
|
||||
suggestion:
|
||||
"移回 .kt-workspace 或外部安全存储;workspace-assets 只保留小型可复用资产。",
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
if (stats.size > workspaceAssetMaxFileBytes) {
|
||||
findings.push({
|
||||
category: "workspace-assets-large-file",
|
||||
file: relativePath,
|
||||
level: "P2",
|
||||
message: "workspace-assets 中出现超过 2MB 的文件,可能是缓存或第三方源码。",
|
||||
project: "root",
|
||||
suggestion:
|
||||
"确认是否必须 Git 管理;大体积参考材料应放到外部仓库或 .kt-workspace 缓存。",
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
walk(assetRoot);
|
||||
return findings;
|
||||
}
|
||||
|
||||
async function reviewProject(
|
||||
projectKey: string,
|
||||
input: Required<
|
||||
@ -1088,6 +1151,8 @@ export async function buildGlobalCodeReview(
|
||||
: findWorkspaceFilesByName(new Set(["deploy.js", "deploy.json"]), 50);
|
||||
const rootGeneratedArtifacts =
|
||||
input.includeRootScan === false ? [] : findRootGeneratedArtifacts();
|
||||
const workspaceAssetPolicyFindings =
|
||||
input.includeRootScan === false ? [] : findWorkspaceAssetPolicyFindings();
|
||||
const taskRecordFindings =
|
||||
input.includeRootScan === false
|
||||
? []
|
||||
@ -1115,13 +1180,14 @@ export async function buildGlobalCodeReview(
|
||||
message: "根目录存在生成态临时产物。",
|
||||
project: "root",
|
||||
suggestion:
|
||||
"统一迁入 .kt-workspace 对应子目录,并更新生成脚本或测试流程。",
|
||||
"生成证据迁入 .kt-workspace;可复用资产迁入 workspace-assets,并更新生成脚本或测试流程。",
|
||||
}),
|
||||
);
|
||||
const allFindings = [
|
||||
...findings,
|
||||
...rootFindings,
|
||||
...rootGeneratedFindings,
|
||||
...workspaceAssetPolicyFindings,
|
||||
...taskRecordFindings,
|
||||
];
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user