From 2ad5474f6719ea9f26cfed904baf7139f49438dc Mon Sep 17 00:00:00 2001 From: Amaad Martin Date: Fri, 31 Jul 2026 20:53:38 -0700 Subject: [PATCH 1/4] Chore: format eslint.config.js with the repo's Prettier config The repo's lint-staged hook runs `prettier --write` over `**/*.{js,ts}`, but `npm run format` only targets `**/*.ts`, so this file had never been formatted and any commit touching it drags a whole-file reformat along. Doing it on its own keeps the next commit reviewable. No behavior change. --- eslint.config.js | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 0d0f13deb..858251a85 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -4,36 +4,36 @@ * SPDX-License-Identifier: Apache-2.0 */ -import js from "@eslint/js"; -import globals from "globals"; -import tseslint from "typescript-eslint"; -import { defineConfig } from "eslint/config"; +import js from '@eslint/js'; +import {defineConfig} from 'eslint/config'; +import globals from 'globals'; +import tseslint from 'typescript-eslint'; export default defineConfig([ { - ignores: ["**/dist/**", "dev/src/browser/**"], + ignores: ['**/dist/**', 'dev/src/browser/**'], }, tseslint.configs.recommended, { - files: ["**/*.ts"], - plugins: { js }, - extends: ["js/recommended"], + files: ['**/*.ts'], + plugins: {js}, + extends: ['js/recommended'], languageOptions: { globals: { ...globals.node, - ...globals.vitest + ...globals.vitest, }, }, rules: { - "no-unused-vars": "off", - "@typescript-eslint/no-unused-vars": [ - "error", + 'no-unused-vars': 'off', + '@typescript-eslint/no-unused-vars': [ + 'error', { - "argsIgnorePattern": "^_", - "varsIgnorePattern": "^_", - "caughtErrorsIgnorePattern": "^_" - } - ] + 'argsIgnorePattern': '^_', + 'varsIgnorePattern': '^_', + 'caughtErrorsIgnorePattern': '^_', + }, + ], }, }, ]); From 766b18b804f7d0bddc4a9c0f729ecee3e86e3c99 Mon Sep 17 00:00:00 2001 From: Amaad Martin Date: Fri, 31 Jul 2026 20:53:52 -0700 Subject: [PATCH 2/4] Chore: fail lint on deep @google/adk* subpath imports The three published packages export only "." from their exports maps, so `@google/adk/` is unresolvable for a consumer, for tsc under nodenext, and for node. The four call sites that used one are removed on the base branch; this rule stops them coming back. Package-root specifiers stay allowed; every subpath under the three packages is an error. --- eslint.config.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/eslint.config.js b/eslint.config.js index 858251a85..da53575e3 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -34,6 +34,22 @@ export default defineConfig([ 'caughtErrorsIgnorePattern': '^_', }, ], + 'no-restricted-imports': [ + 'error', + { + 'patterns': [ + { + 'group': [ + '@google/adk/**', + '@google/adk-devtools/**', + '@google/adk-integrations/**', + ], + 'message': + 'Deep subpath imports are not resolvable from the published packages: their exports maps declare only ".". Import from the package root, or use a relative path into src/ for symbols that are intentionally not public API.', + }, + ], + }, + ], }, }, ]); From a9ce91a4f3cf5c8c2f0e6835617655a69ebee47c Mon Sep 17 00:00:00 2001 From: Amaad Martin Date: Fri, 31 Jul 2026 21:00:31 -0700 Subject: [PATCH 3/4] Test: pin the restricted-import rule against silent removal Lints an in-memory import statement with the repo's own config, so deleting the rule or widening its group to cover the package roots fails the suite instead of quietly leaving the guard inert. --- .../eslint_restricted_imports_test.ts | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 tests/integration/eslint_restricted_imports_test.ts diff --git a/tests/integration/eslint_restricted_imports_test.ts b/tests/integration/eslint_restricted_imports_test.ts new file mode 100644 index 000000000..03895f12f --- /dev/null +++ b/tests/integration/eslint_restricted_imports_test.ts @@ -0,0 +1,59 @@ +/** + * @license + * Copyright 2026 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {ESLint} from 'eslint'; +import path from 'node:path'; +import {fileURLToPath} from 'node:url'; +import {describe, expect, it} from 'vitest'; + +const REPO_ROOT = path.resolve( + path.dirname(fileURLToPath(import.meta.url)), + '../..', +); + +/** + * A path that never exists on disk: it only tells ESLint which config block + * applies to the linted text, and `lintText` reads nothing from the + * filesystem. + */ +const LINT_TARGET = path.join(REPO_ROOT, 'core/test/restricted_imports.ts'); + +const eslint = new ESLint({cwd: REPO_ROOT}); + +async function restrictedImportMessages(specifier: string): Promise { + const [result] = await eslint.lintText(`import '${specifier}';\n`, { + filePath: LINT_TARGET, + }); + return result.messages + .filter((message) => message.ruleId === 'no-restricted-imports') + .map((message) => message.message); +} + +describe('no-restricted-imports', () => { + it('allows the package root specifiers', async () => { + for (const specifier of [ + '@google/adk', + '@google/adk-devtools', + '@google/adk-integrations', + ]) { + expect(await restrictedImportMessages(specifier)).toEqual([]); + } + }); + + it('rejects a subpath at any depth of any published package', async () => { + for (const specifier of [ + '@google/adk/index.js', + '@google/adk/utils/logger.js', + '@google/adk/agents/processors/code_execution_request_processor.js', + '@google/adk-devtools/server/adk_api_client.js', + '@google/adk-integrations/foo.js', + ]) { + const messages = await restrictedImportMessages(specifier); + expect(messages).toHaveLength(1); + expect(messages[0]).toContain('exports maps declare only'); + } + }); +}); From a7a92ea77f7d16c911687153258f3096ef47456c Mon Sep 17 00:00:00 2001 From: Amaad Martin Date: Fri, 31 Jul 2026 21:22:36 -0700 Subject: [PATCH 4/4] Chore: cover every @google/adk* package with one restricted-import group One glob replaces the three enumerated package names and picks up any future @google/adk-* workspace package instead of letting it escape the rule. The test moves next to the other repo-tooling suites. --- eslint.config.js | 6 +----- .../{ => repo_config}/eslint_restricted_imports_test.ts | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) rename tests/integration/{ => repo_config}/eslint_restricted_imports_test.ts (99%) diff --git a/eslint.config.js b/eslint.config.js index da53575e3..548da0fd5 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -39,11 +39,7 @@ export default defineConfig([ { 'patterns': [ { - 'group': [ - '@google/adk/**', - '@google/adk-devtools/**', - '@google/adk-integrations/**', - ], + 'group': ['@google/adk*/**'], 'message': 'Deep subpath imports are not resolvable from the published packages: their exports maps declare only ".". Import from the package root, or use a relative path into src/ for symbols that are intentionally not public API.', }, diff --git a/tests/integration/eslint_restricted_imports_test.ts b/tests/integration/repo_config/eslint_restricted_imports_test.ts similarity index 99% rename from tests/integration/eslint_restricted_imports_test.ts rename to tests/integration/repo_config/eslint_restricted_imports_test.ts index 03895f12f..c0a77ac74 100644 --- a/tests/integration/eslint_restricted_imports_test.ts +++ b/tests/integration/repo_config/eslint_restricted_imports_test.ts @@ -11,7 +11,7 @@ import {describe, expect, it} from 'vitest'; const REPO_ROOT = path.resolve( path.dirname(fileURLToPath(import.meta.url)), - '../..', + '../../..', ); /**