diff --git a/scripts/check-boundaries.mjs b/scripts/check-boundaries.mjs index 5766ac78..97ffef64 100644 --- a/scripts/check-boundaries.mjs +++ b/scripts/check-boundaries.mjs @@ -68,6 +68,13 @@ const contextProductionLlmBoundaryPatterns = [ }, ]; +const concreteDialectImportPatterns = [ + /from\s+['"][^'"]*connectors\/[^'"]+\/dialect\.js['"]/, + /import\s*\(\s*['"][^'"]*connectors\/[^'"]+\/dialect\.js['"]\s*\)/, + /from\s+['"]\.\/dialect\.js['"]/, + /import\s*\(\s*['"]\.\/dialect\.js['"]\s*\)/, +]; + function normalizePath(filePath) { return filePath.split(path.sep).join('/'); } @@ -99,6 +106,13 @@ function scansForContextProductionLlmBoundaries(relativePath) { return scansForLlmBoundaries(relativePath) && !isTestSource(relativePath); } +function scansForConcreteDialectImportBoundaries(relativePath) { + return ( + relativePath.startsWith('packages/cli/src/context/scan/') || + /^packages\/cli\/src\/connectors\/[^/]+\/connector\.ts$/.test(relativePath) + ); +} + function scansForForbiddenIdentifiers(relativePath) { return (isCodeSource(relativePath) && !isTestSource(relativePath)) || isRuntimeAsset(relativePath); } @@ -151,6 +165,19 @@ export function scanFileContent(relativePath, content) { } } + if (scansForConcreteDialectImportBoundaries(normalizedPath)) { + for (const pattern of concreteDialectImportPatterns) { + if (pattern.test(content)) { + violations.push({ + file: normalizedPath, + kind: 'dialect-boundary', + message: + 'Forbidden concrete connector dialect import; use getDialectForDriver() from context/connections/dialects.ts', + }); + } + } + } + if ( scansForForbiddenIdentifiers(normalizedPath) && !skipsIdentifierScan(normalizedPath) && diff --git a/scripts/check-boundaries.test.mjs b/scripts/check-boundaries.test.mjs index 25cd0f85..1961eb5c 100644 --- a/scripts/check-boundaries.test.mjs +++ b/scripts/check-boundaries.test.mjs @@ -112,6 +112,43 @@ describe('scanFileContent', () => { ); }); + it('rejects concrete connector dialect imports from scan workflow and connector classes', () => { + const violations = [ + ...scanFileContent( + 'packages/cli/src/context/scan/relationship-profiling.ts', + "import { KtxPostgresDialect } from '../../connectors/postgres/dialect.js';", + ), + ...scanFileContent( + 'packages/cli/src/connectors/postgres/connector.ts', + "import { KtxPostgresDialect } from './dialect.js';", + ), + ]; + + assert.deepEqual( + violations.map((violation) => violation.kind), + ['dialect-boundary', 'dialect-boundary'], + ); + assert.equal( + violations[0]?.message, + 'Forbidden concrete connector dialect import; use getDialectForDriver() from context/connections/dialects.ts', + ); + + assert.deepEqual( + scanFileContent( + 'packages/cli/src/context/connections/dialects.ts', + "import { KtxPostgresDialect } from '../../connectors/postgres/dialect.js';", + ), + [], + ); + assert.deepEqual( + scanFileContent( + 'packages/cli/src/connectors/postgres/dialect.test.ts', + "import { KtxPostgresDialect } from './dialect.js';", + ), + [], + ); + }); + it('rejects old KTX LLM port declarations in context', () => { const violations = [ ...scanFileContent('packages/cli/src/context/agent/agent-runner.service.ts', 'export interface LlmProviderPort {}'),