8b2c6e0129
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
152 lines
5.0 KiB
JavaScript
152 lines
5.0 KiB
JavaScript
import { readFileSync, readdirSync } from "node:fs";
|
|
import { join, relative } from "node:path";
|
|
|
|
const DEFAULT_LIMIT = 500;
|
|
|
|
// Add narrowly scoped exceptions here with justification
|
|
const EXCEPTIONS = {
|
|
"src/features/sidebar/ui/SidebarProjectsSection.tsx": {
|
|
limit: 570,
|
|
justification:
|
|
"Drag-and-drop handlers for session-to-project moves and project reorder, plus activeProjectId highlight.",
|
|
},
|
|
"src/features/chat/ui/ChatView.tsx": {
|
|
limit: 535,
|
|
justification:
|
|
"ACP prewarm guards, project-aware working dir selection, working context sync, and chat bootstrapping still live together here.",
|
|
},
|
|
"src/features/chat/ui/__tests__/ContextPanel.test.tsx": {
|
|
limit: 550,
|
|
justification:
|
|
"Workspace widget integration tests cover branch switching, worktree creation, dirty-state dialogs, and picker interactions.",
|
|
},
|
|
"src/features/sidebar/ui/Sidebar.tsx": {
|
|
limit: 580,
|
|
justification:
|
|
"Search-as-you-type filtering and draft-aware sidebar highlight logic.",
|
|
},
|
|
"src/app/AppShell.tsx": {
|
|
limit: 640,
|
|
justification:
|
|
"Shell still coordinates ACP session loading, project reassignment, and app-level chat routing.",
|
|
},
|
|
"src/features/chat/hooks/useAcpStream.ts": {
|
|
limit: 580,
|
|
justification:
|
|
"ACP replay, streaming, session binding, model-state event handling, and replay timeout are still centralized here.",
|
|
},
|
|
"src/features/chat/hooks/__tests__/useAcpStream.test.ts": {
|
|
limit: 570,
|
|
justification:
|
|
"Covers replay buffering, timeout error state, streaming edge cases, and provider identity persistence in one cohesive suite.",
|
|
},
|
|
"src/features/chat/stores/__tests__/chatSessionStore.test.ts": {
|
|
limit: 540,
|
|
justification:
|
|
"ACP session overlay regressions currently need one broad integration-style store suite.",
|
|
},
|
|
"src/features/chat/stores/chatSessionStore.ts": {
|
|
limit: 640,
|
|
justification:
|
|
"ACP-backed session overlay persistence, draft migration, and sidebar-facing session merge logic live together for now.",
|
|
},
|
|
"src-tauri/src/commands/projects.rs": {
|
|
limit: 520,
|
|
justification:
|
|
"Project CRUD plus reorder_projects command for sidebar drag-and-drop ordering.",
|
|
},
|
|
"src-tauri/src/services/acp/manager/dispatcher.rs": {
|
|
limit: 540,
|
|
justification:
|
|
"ACP replay and live-stream event fan-out share one dispatcher with replay event counting for drain stabilisation.",
|
|
},
|
|
"src-tauri/src/services/acp/manager.rs": {
|
|
limit: 630,
|
|
justification:
|
|
"ACP manager command dispatch loop — export/import/fork session ext_method dispatch adds boilerplate.",
|
|
},
|
|
"src-tauri/src/services/acp/manager/session_ops.rs": {
|
|
limit: 620,
|
|
justification:
|
|
"Session prepare/load/list logic, working-dir updates, wait_for_replay_drain helper with iteration cap, and composite prepared-session reuse remain colocated while ACP session ownership stabilizes.",
|
|
},
|
|
"src-tauri/src/commands/system.rs": {
|
|
limit: 640,
|
|
justification:
|
|
"Desktop system commands still centralize file mentions, attachment inspection, platform-aware path dedupe, guarded image loading, and export helpers in one Tauri command surface.",
|
|
},
|
|
};
|
|
|
|
// Directories excluded from size checks (imported library code)
|
|
const EXCLUDED_DIRS = [
|
|
"src/shared/ui",
|
|
"src/components/ai-elements",
|
|
"src/hooks",
|
|
];
|
|
|
|
const DIRS_TO_CHECK = [
|
|
{ dir: "src/app", glob: /\.[jt]sx?$/ },
|
|
{ dir: "src/features", glob: /\.[jt]sx?$/ },
|
|
{ dir: "src/shared", glob: /\.[jt]sx?$/ },
|
|
{ dir: "src/components", glob: /\.[jt]sx?$/ },
|
|
{ dir: "src/hooks", glob: /\.[jt]sx?$/ },
|
|
{ dir: "src-tauri/src", glob: /\.rs$/ },
|
|
];
|
|
|
|
function countLines(filePath) {
|
|
const content = readFileSync(filePath, "utf8");
|
|
return content.split("\n").length;
|
|
}
|
|
|
|
function isExcluded(filePath) {
|
|
const rel = relative(".", filePath);
|
|
return EXCLUDED_DIRS.some((dir) => rel.startsWith(dir));
|
|
}
|
|
|
|
function walkDir(dir, pattern) {
|
|
const results = [];
|
|
let entries;
|
|
try {
|
|
entries = readdirSync(dir, { withFileTypes: true });
|
|
} catch {
|
|
return results;
|
|
}
|
|
for (const entry of entries) {
|
|
const fullPath = join(dir, entry.name);
|
|
if (entry.isDirectory()) {
|
|
results.push(...walkDir(fullPath, pattern));
|
|
} else if (pattern.test(entry.name)) {
|
|
results.push(fullPath);
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
|
|
const violations = [];
|
|
|
|
for (const { dir, glob } of DIRS_TO_CHECK) {
|
|
const files = walkDir(dir, glob);
|
|
for (const file of files) {
|
|
if (isExcluded(file)) continue;
|
|
const rel = relative(".", file);
|
|
const limit = EXCEPTIONS[rel]?.limit ?? DEFAULT_LIMIT;
|
|
const lines = countLines(file);
|
|
if (lines > limit) {
|
|
violations.push({ file: rel, lines, limit });
|
|
}
|
|
}
|
|
}
|
|
|
|
if (violations.length > 0) {
|
|
console.error("Desktop file size check failed:");
|
|
for (const v of violations) {
|
|
console.error(` - ${v.file}: ${v.lines} lines (limit ${v.limit})`);
|
|
}
|
|
console.error(
|
|
"\nSplit the file or add a narrowly scoped exception in `scripts/check-file-sizes.mjs`.",
|
|
);
|
|
process.exit(1);
|
|
} else {
|
|
console.log("File size check passed.");
|
|
}
|