Files
tkmind_go/ui/goose2/scripts/check-file-sizes.mjs
T
2026-04-21 16:22:41 +00:00

162 lines
5.9 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: 570,
justification:
"ACP prewarm guards, project-aware working dir selection, working context sync, chat bootstrapping, context-ring compaction wiring, and gated [perf:chatview] logging via perfLog (dev-only by default).",
},
"src/features/chat/hooks/useChat.ts": {
limit: 510,
justification:
"Session preparation, provider/model handoff, persona-aware sends, cancellation, and compaction replay still live in one chat lifecycle hook.",
},
"src/shared/api/acpNotificationHandler.ts": {
limit: 550,
justification:
"ACP replay/live update handling, pending session buffering, model/config propagation, and streaming perf tracking still share one notification entrypoint.",
},
"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: 730,
justification:
"Shell still coordinates ACP session loading, replay-buffer cleanup on load failure, project reassignment, home-session restoration, app-level chat routing, and restored project-draft reuse. Includes gated [perf:load]/[perf:newtab] logging via perfLog (dev-only by default).",
},
"src/features/chat/hooks/useChatSessionController.ts": {
limit: 690,
justification:
"Controller now centralizes home-to-chat pending state transfer, workspace/project preparation, provider/model/persona handoff, Goose cross-provider model selection sequencing with rollback, and chat input orchestration pending a later decomposition pass.",
},
"src/features/chat/ui/AgentModelPicker.tsx": {
limit: 570,
justification:
"Agent-first picker currently keeps the full trigger, recommended-model view, searchable full-model view, and ACP/goose-specific labeling logic in one component pending later extraction.",
},
"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/features/chat/ui/ChatInput.tsx": {
limit: 510,
justification:
"Voice dictation send/stop guards, attachment handling, and mention/picker coordination still share one chat composer component.",
},
"src/features/chat/ui/MessageBubble.tsx": {
limit: 520,
justification:
"Bubble rendering still owns assistant identity, grouped tool output, attachments, and the inline actions tray pending a later extraction pass.",
},
"src/features/chat/ui/__tests__/ChatInput.test.tsx": {
limit: 520,
justification:
"Composer regression coverage spans personas, queueing, attachments, and voice-input edge cases in one interaction-heavy suite.",
},
"src-tauri/src/commands/projects.rs": {
limit: 520,
justification:
"Project CRUD plus reorder_projects command for sidebar drag-and-drop ordering.",
},
"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.");
}