chore: 收敛工作区资产清理规则
This commit is contained in:
parent
1abfcbf59d
commit
e2a50f03b3
@ -92,7 +92,9 @@ export const defaultHistoryRoots = [
|
|||||||
'.kt-workspace/test-logs',
|
'.kt-workspace/test-logs',
|
||||||
'.kt-workspace/verify',
|
'.kt-workspace/verify',
|
||||||
'.kt-workspace/playwright-mcp',
|
'.kt-workspace/playwright-mcp',
|
||||||
|
'.kt-workspace/subagents',
|
||||||
'.kt-workspace/db-sync',
|
'.kt-workspace/db-sync',
|
||||||
|
'.kt-workspace/exports',
|
||||||
'.kt-workspace/tmp',
|
'.kt-workspace/tmp',
|
||||||
'.kt-workspace/logs',
|
'.kt-workspace/logs',
|
||||||
'.kt-workspace/deploy',
|
'.kt-workspace/deploy',
|
||||||
|
|||||||
@ -178,7 +178,7 @@ export function registerTools(server: McpServer): void {
|
|||||||
server.registerTool(
|
server.registerTool(
|
||||||
'kt_cleanup_history',
|
'kt_cleanup_history',
|
||||||
{
|
{
|
||||||
description: '清理 KT 运行态历史产物。默认 dryRun=true,只预览;执行时按 LastWriteTime 每个 .kt-workspace 历史目录仅保留最近 3 轮,template/_templates 等模板目录永久保留。',
|
description: '清理 KT 运行态历史产物。默认 dryRun=true,只预览;执行时按 LastWriteTime 每个 .kt-workspace 历史目录仅保留最近 3 轮;可复用模板和参考资产应迁入 workspace-assets。',
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
dryRun: z.boolean().default(true),
|
dryRun: z.boolean().default(true),
|
||||||
keep: z.number().int().min(0).max(50).default(3),
|
keep: z.number().int().min(0).max(50).default(3),
|
||||||
|
|||||||
@ -1957,12 +1957,18 @@ export async function runSelfTest(): Promise<void> {
|
|||||||
const workspaceHistoryRoot = cleanupRoots.find(
|
const workspaceHistoryRoot = cleanupRoots.find(
|
||||||
(item) => item.root === ".kt-workspace",
|
(item) => item.root === ".kt-workspace",
|
||||||
);
|
);
|
||||||
if (!testArtifactsRoot?.preservedNames.includes("_templates")) {
|
if (testArtifactsRoot?.preservedNames.includes("_templates")) {
|
||||||
throw new Error("cleanup template preservation self-check failed");
|
throw new Error("cleanup template migration self-check failed");
|
||||||
}
|
}
|
||||||
if (!workspaceHistoryRoot?.preservedNames.includes("test-artifacts")) {
|
if (!workspaceHistoryRoot?.preservedNames.includes("test-artifacts")) {
|
||||||
throw new Error("cleanup workspace root preservation self-check failed");
|
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({
|
const docSyncBangDream = await buildChangeDocSync({
|
||||||
changedFiles: [
|
changedFiles: [
|
||||||
@ -2040,6 +2046,21 @@ export async function runSelfTest(): Promise<void> {
|
|||||||
throw new Error("NapCat device profile guardrail self-check failed");
|
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 = {
|
const data = {
|
||||||
context: readWorkflowContext({ taskRecordCount: 2 }),
|
context: readWorkflowContext({ taskRecordCount: 2 }),
|
||||||
guardrails: buildGuardrails({
|
guardrails: buildGuardrails({
|
||||||
@ -2067,12 +2088,7 @@ export async function runSelfTest(): Promise<void> {
|
|||||||
taskType: "mcp",
|
taskType: "mcp",
|
||||||
userRequest: "扩展 KT 工作区 MCP 可复用能力",
|
userRequest: "扩展 KT 工作区 MCP 可复用能力",
|
||||||
}),
|
}),
|
||||||
review: await buildGlobalCodeReview({
|
review,
|
||||||
includeContentScan: true,
|
|
||||||
includeRootScan: true,
|
|
||||||
maxFindingsPerProject: 10,
|
|
||||||
projects: ["mcp"],
|
|
||||||
}),
|
|
||||||
reviewCliParser,
|
reviewCliParser,
|
||||||
reviewClassifier,
|
reviewClassifier,
|
||||||
refactorGuardrails,
|
refactorGuardrails,
|
||||||
|
|||||||
@ -36,32 +36,31 @@ interface HistoryEntry {
|
|||||||
type: 'directory' | 'file';
|
type: 'directory' | 'file';
|
||||||
}
|
}
|
||||||
|
|
||||||
const alwaysPreservedTemplateNames = new Set(['_template', '_templates', 'template', 'templates']);
|
|
||||||
const workspaceRootPreservedNames = new Set([
|
const workspaceRootPreservedNames = new Set([
|
||||||
'backup',
|
'backup',
|
||||||
'db-sync',
|
'db-sync',
|
||||||
'deploy',
|
'deploy',
|
||||||
'external',
|
'exports',
|
||||||
'logs',
|
'logs',
|
||||||
'playwright-mcp',
|
'playwright-mcp',
|
||||||
'sources',
|
'subagents',
|
||||||
'test-artifacts',
|
'test-artifacts',
|
||||||
'test-logs',
|
'test-logs',
|
||||||
'tmp',
|
'tmp',
|
||||||
'verify',
|
'verify',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
function isTemplateEntryName(name: string): boolean {
|
/**
|
||||||
return alwaysPreservedTemplateNames.has(name.toLowerCase());
|
* 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`.
|
||||||
function buildPreservedNames(rootRelativePath: string, childNames: string[]): Set<string> {
|
*
|
||||||
|
* @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>();
|
const preservedNames = new Set<string>();
|
||||||
|
|
||||||
for (const name of childNames) {
|
|
||||||
if (isTemplateEntryName(name)) preservedNames.add(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rootRelativePath === '.kt-workspace') {
|
if (rootRelativePath === '.kt-workspace') {
|
||||||
for (const name of workspaceRootPreservedNames) {
|
for (const name of workspaceRootPreservedNames) {
|
||||||
preservedNames.add(name);
|
preservedNames.add(name);
|
||||||
@ -126,8 +125,7 @@ export function cleanupHistoryArtifacts(input: CleanupHistoryInput = {}): Record
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const childNames = readdirSync(rootPath, { withFileTypes: true }).map((item) => item.name);
|
const preservedNames = buildPreservedNames(rootRelativePath);
|
||||||
const preservedNames = buildPreservedNames(rootRelativePath, childNames);
|
|
||||||
const entries = listHistoryEntries(rootPath, preservedNames);
|
const entries = listHistoryEntries(rootPath, preservedNames);
|
||||||
const preserved = entries.slice(0, keep);
|
const preserved = entries.slice(0, keep);
|
||||||
const deleting = entries.slice(keep);
|
const deleting = entries.slice(keep);
|
||||||
|
|||||||
@ -172,7 +172,6 @@ const requiredCorePlugins = [
|
|||||||
const expectedIgnoredRoots = [
|
const expectedIgnoredRoots = [
|
||||||
'.git/',
|
'.git/',
|
||||||
'.kt-workspace/',
|
'.kt-workspace/',
|
||||||
'_exports/',
|
|
||||||
'Node/',
|
'Node/',
|
||||||
'Plugins/',
|
'Plugins/',
|
||||||
'Vue/',
|
'Vue/',
|
||||||
@ -278,7 +277,6 @@ const ignoredDirectoryNames = new Set([
|
|||||||
'.pnpm',
|
'.pnpm',
|
||||||
'.turbo',
|
'.turbo',
|
||||||
'.vite',
|
'.vite',
|
||||||
'_exports',
|
|
||||||
'build',
|
'build',
|
||||||
'coverage',
|
'coverage',
|
||||||
'dist',
|
'dist',
|
||||||
|
|||||||
@ -41,10 +41,15 @@ const rootGeneratedArtifactNames = new Set([
|
|||||||
".codex-test-logs",
|
".codex-test-logs",
|
||||||
".codex-verify",
|
".codex-verify",
|
||||||
".playwright-mcp",
|
".playwright-mcp",
|
||||||
|
"_exports",
|
||||||
"codex-test-logs",
|
"codex-test-logs",
|
||||||
"test-artifacts",
|
"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 =
|
const sensitiveTrackedFilePattern =
|
||||||
/(^|\/)(\.env($|\..+)|deploy\.json|secrets?\.(env|json|ya?ml)|id_rsa|[^/]+\.(pem|key))$/i;
|
/(^|\/)(\.env($|\..+)|deploy\.json|secrets?\.(env|json|ya?ml)|id_rsa|[^/]+\.(pem|key))$/i;
|
||||||
|
|
||||||
@ -990,6 +995,64 @@ function findRootGeneratedArtifacts(): string[] {
|
|||||||
.map((entry) => entry.name);
|
.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(
|
async function reviewProject(
|
||||||
projectKey: string,
|
projectKey: string,
|
||||||
input: Required<
|
input: Required<
|
||||||
@ -1088,6 +1151,8 @@ export async function buildGlobalCodeReview(
|
|||||||
: findWorkspaceFilesByName(new Set(["deploy.js", "deploy.json"]), 50);
|
: findWorkspaceFilesByName(new Set(["deploy.js", "deploy.json"]), 50);
|
||||||
const rootGeneratedArtifacts =
|
const rootGeneratedArtifacts =
|
||||||
input.includeRootScan === false ? [] : findRootGeneratedArtifacts();
|
input.includeRootScan === false ? [] : findRootGeneratedArtifacts();
|
||||||
|
const workspaceAssetPolicyFindings =
|
||||||
|
input.includeRootScan === false ? [] : findWorkspaceAssetPolicyFindings();
|
||||||
const taskRecordFindings =
|
const taskRecordFindings =
|
||||||
input.includeRootScan === false
|
input.includeRootScan === false
|
||||||
? []
|
? []
|
||||||
@ -1115,13 +1180,14 @@ export async function buildGlobalCodeReview(
|
|||||||
message: "根目录存在生成态临时产物。",
|
message: "根目录存在生成态临时产物。",
|
||||||
project: "root",
|
project: "root",
|
||||||
suggestion:
|
suggestion:
|
||||||
"统一迁入 .kt-workspace 对应子目录,并更新生成脚本或测试流程。",
|
"生成证据迁入 .kt-workspace;可复用资产迁入 workspace-assets,并更新生成脚本或测试流程。",
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const allFindings = [
|
const allFindings = [
|
||||||
...findings,
|
...findings,
|
||||||
...rootFindings,
|
...rootFindings,
|
||||||
...rootGeneratedFindings,
|
...rootGeneratedFindings,
|
||||||
|
...workspaceAssetPolicyFindings,
|
||||||
...taskRecordFindings,
|
...taskRecordFindings,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user