diff --git a/AGENTS.md b/AGENTS.md index 2bc5ccb0763..4b503b7d8ec 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -126,6 +126,9 @@ const children = getChildNodes(workflow.connections, 'NodeName', 'main', 1); - **NEVER use `any` type** - use proper types or `unknown` - **Avoid type casting with `as`** - use type guards or type predicates instead (except in test code where `as` is acceptable) - **Define shared interfaces in `@n8n/api-types`** package for FE/BE communication +- **Lazy-load heavy modules** — if a module is only used in a specific code + path (not every request), use `await import()` at point of use instead of + top-level `import`. Applies especially to native modules and large parsers. ### Error Handling - Don't use `ApplicationError` class in CLI and nodes for throwing errors, diff --git a/cubic.yaml b/cubic.yaml index 03f879d4c71..29ccbb49291 100644 --- a/cubic.yaml +++ b/cubic.yaml @@ -49,21 +49,6 @@ reviews: If one exists, suggest using it. If none exist, ask why the hard-coded value is required. custom_rules: - - name: Tests - description: |- - BE REASONABLE when evaluating test coverage: - - **PASS if:** - - - Core functionality has tests - - Critical paths are tested - - Coverage is reasonable (not necessarily 100%) - - **DO NOT require tests for:** - - - Exports, types, configs - - Metadata files - - Version files - name: Security Review description: |- Proactively review PRs and flag security vulnerabilities specific to n8n's architecture: @@ -195,6 +180,34 @@ reviews: - Higher scrutiny for: expression engine, credential handling, code execution nodes, license enforcement, SSO integrations - Consider n8n's security audit categories: credentials, database, nodes, instance, filesystem risks - Community/custom nodes have higher risk profile than official nodes + - name: Quality & Performance Review + description: |- + You are a Staff Quality Engineer reviewing this PR for production + readiness. You think in terms of blast radius, resource cost, and + failure modes. You are pragmatic, not pedantic. + + Below is a curated list of known issues from real incidents and + review standards the team enforces. Review the diff against each + item. If none match, say nothing. If one matches, explain the + concrete risk and suggest a fix. Never flag existing unchanged code. + + ## Known Issues + + ### 1. Eager imports of heavy or native modules + Top-level `import` of modules only used in a specific code path loads + them into every process at startup, increasing baseline memory. + Native modules (e.g. `isolated-vm`) can crash instances that lack the + binary. Large parsers (e.g. `jsdom` at ~16 MB heap) waste memory when + the code path is rarely hit. + **Fix:** Use `await import()` at point of use. For barrel files, use + `export type` instead of value re-exports when consumers only need + the type. + + ### 2. Unreasonable test coverage expectations + Be pragmatic about test requirements. Pass if core functionality and + critical paths are tested at reasonable coverage. Do NOT require tests + for exports, types, configs, metadata files, or version files. Let + humans handle edge cases. - name: Design System Tokens description: |- For any hard-coded visual values in CSS (colors, shadows, spacing), diff --git a/packages/@n8n/ai-workflow-builder.ee/src/tools/test/web-fetch.tool.test.ts b/packages/@n8n/ai-workflow-builder.ee/src/tools/test/web-fetch.tool.test.ts index d40c1cd48ba..95070372070 100644 --- a/packages/@n8n/ai-workflow-builder.ee/src/tools/test/web-fetch.tool.test.ts +++ b/packages/@n8n/ai-workflow-builder.ee/src/tools/test/web-fetch.tool.test.ts @@ -139,7 +139,7 @@ describe('web_fetch tool', () => { contentType: 'text/html', }); - mockExtractReadableContent.mockReturnValue({ + mockExtractReadableContent.mockResolvedValue({ title: 'Test Page', content: 'Extracted content', truncated: false, @@ -174,7 +174,7 @@ describe('web_fetch tool', () => { contentType: 'text/html', }); - mockExtractReadableContent.mockReturnValue({ + mockExtractReadableContent.mockResolvedValue({ title: 'Test Page', content: 'Extracted content', truncated: false, @@ -228,7 +228,7 @@ describe('web_fetch tool', () => { contentType: 'text/html', }); - mockExtractReadableContent.mockReturnValue({ + mockExtractReadableContent.mockResolvedValue({ title: 'Test', content: 'Content', truncated: false, @@ -260,7 +260,7 @@ describe('web_fetch tool', () => { contentType: 'text/html', }); - mockExtractReadableContent.mockReturnValue({ + mockExtractReadableContent.mockResolvedValue({ title: 'Test', content: 'Content', truncated: false, @@ -338,7 +338,7 @@ describe('web_fetch tool', () => { contentType: 'text/html', }); - mockExtractReadableContent.mockReturnValue({ + mockExtractReadableContent.mockResolvedValue({ title: 'Test Page', content: 'Extracted readable content', truncated: false, @@ -364,7 +364,7 @@ describe('web_fetch tool', () => { contentType: 'text/html', }); - mockExtractReadableContent.mockReturnValue({ + mockExtractReadableContent.mockResolvedValue({ title: 'Long Page', content: 'Truncated content', truncated: true, @@ -387,7 +387,7 @@ describe('web_fetch tool', () => { contentType: 'text/html', }); - mockExtractReadableContent.mockReturnValue({ + mockExtractReadableContent.mockResolvedValue({ title: 'Page', content: 'Content', truncated: false, @@ -460,7 +460,7 @@ describe('web_fetch tool', () => { action: 'allow_once', }); - mockExtractReadableContent.mockReturnValue({ + mockExtractReadableContent.mockResolvedValue({ title: 'Redirected Page', content: 'Redirected content', truncated: false, @@ -540,7 +540,7 @@ describe('web_fetch tool', () => { action: 'allow_domain', }); - mockExtractReadableContent.mockReturnValue({ + mockExtractReadableContent.mockResolvedValue({ title: 'Test', content: 'Content', truncated: false, @@ -572,7 +572,7 @@ describe('web_fetch tool', () => { contentType: 'text/html', }); - mockExtractReadableContent.mockReturnValue({ + mockExtractReadableContent.mockResolvedValue({ title: 'Docs', content: 'Documentation content', truncated: false, @@ -627,7 +627,7 @@ describe('web_fetch tool', () => { contentType: 'text/html', }); - mockExtractReadableContent.mockReturnValue({ + mockExtractReadableContent.mockResolvedValue({ title: 'Test', content: 'Content', truncated: false, @@ -656,7 +656,7 @@ describe('web_fetch tool', () => { contentType: 'text/html', }); - mockExtractReadableContent.mockReturnValue({ + mockExtractReadableContent.mockResolvedValue({ title: 'Test', content: 'Content', truncated: false, @@ -691,7 +691,7 @@ describe('web_fetch tool', () => { contentType: 'text/html', }); - mockExtractReadableContent.mockReturnValue({ + mockExtractReadableContent.mockResolvedValue({ title: 'Test', content: 'Content', truncated: false, @@ -751,7 +751,7 @@ describe('web_fetch tool', () => { contentType: 'text/html', }); - mockExtractReadableContent.mockReturnValue({ + mockExtractReadableContent.mockResolvedValue({ title: 'Test', content: 'Content', truncated: false, @@ -788,7 +788,7 @@ describe('web_fetch tool', () => { contentType: 'text/html', }); - mockExtractReadableContent.mockReturnValue({ + mockExtractReadableContent.mockResolvedValue({ title: 'Test', content: 'Content', truncated: false, @@ -820,7 +820,7 @@ describe('web_fetch tool', () => { contentType: 'text/html', }); - mockExtractReadableContent.mockReturnValue({ + mockExtractReadableContent.mockResolvedValue({ title: 'Test', content: 'Content', truncated: false, @@ -853,7 +853,7 @@ describe('web_fetch tool', () => { contentType: 'text/html', }); - mockExtractReadableContent.mockReturnValue({ + mockExtractReadableContent.mockResolvedValue({ title: 'Test', content: 'Redirected content', truncated: false, diff --git a/packages/@n8n/ai-workflow-builder.ee/src/tools/utils/test/web-fetch.utils.test.ts b/packages/@n8n/ai-workflow-builder.ee/src/tools/utils/test/web-fetch.utils.test.ts index 42a5bb0bb61..fada9cad5f2 100644 --- a/packages/@n8n/ai-workflow-builder.ee/src/tools/utils/test/web-fetch.utils.test.ts +++ b/packages/@n8n/ai-workflow-builder.ee/src/tools/utils/test/web-fetch.utils.test.ts @@ -241,7 +241,7 @@ describe('web-fetch.utils', () => { }); describe('extractReadableContent', () => { - it('should extract title and Markdown-formatted content from HTML', () => { + it('should extract title and Markdown-formatted content from HTML', async () => { const html = `