From 3716a8b128fcaaf5a7ae1b666f502e80beb429a9 Mon Sep 17 00:00:00 2001 From: NekoPunch Date: Mon, 7 Sep 2026 04:04:58 -0700 Subject: [PATCH] chore(ci): run the knip dead-code gate for every workspace and the repository root Extend knip.json to all nine previously-ungated workspaces (runtime, runtime-host, storage, core, mcp, computer-use, eval, cli, website) plus the repository root, and replace the eleven per-workspace Knip CI steps with a single plain 'npx knip' run - a strict superset that also covers the repository root and respects every workspace's project patterns. Workspace globs are deliberately avoided: -W 'packages/*' silently skips per-workspace project patterns (probe-verified). The step fails when a package.json workspace is missing from knip.json, and when a knip.json key no longer resolves to a declared workspace. Dynamic reachability is registered explicitly - spawn-loaded test fixtures, the filesystem worker bundle entry, the PowerShell-driven Windows IPC trust fixture, eval's Python-spawned harness entry, the dynamically dispatched computer-use command modules, and the two modules that actions/github-script loads from its with: script block (knip's github-actions plugin only reads run: steps) - so the scan doesn't guess. The website's entry surface is scoped to the Astro routes, the config and the executed scripts; components, layouts and copy modules stay in the project set and are judged by the real import graph (probe-verified). The root project glob excludes ambient declarations, which TS resolution consumes without an import statement. Remove what the scan surfaced and repo-wide greps confirmed unreachable: unused exports, types and helpers across runtime, runtime-host, storage, cli, core and the root scripts; the storage readHead registration plumbing that only fed the deleted readWorkspaceHeadInternal; and export aliases whose second name had no consumers. Kept deliberately: the decode/encode and PAGE/RANGE codec alias pairs whose both names have live consumers. Their aliases become distinct declarations (thin wrapper functions, an independently declared range bound asserted against the page bound in the protocol test) so the duplicates check resolves without exemption tags. The website keeps its exact cookie@2.0.1 pin as a declared exception: astro's prerenderer resolves it through the website root. Hoisted build/test tooling that root scripts import (electron, electron-builder, electron-updater, app-builder-lib, builder-util, @playwright/test, @babel/parser, ws) is declared in the root package.json instead of blanket-silenced, and the CI step asserts the two electron-builder internal-package pins track electron-builder's own requirement. The coverage and pin-parity guards live in scripts/check-knip-workspace-coverage.mjs with node --test coverage (single-level glob semantics on both sides, the stale-key regression case, pin parity including the absent-pin case) rather than as untested inline scripts. Known coverage boundary, surfaced by review: files listed in a package.json exports map are entry files, so their unused exports sit outside the default report (core exposes 147 of 266 source files this way, runtime 110 of 506, storage 55 of 249); --include-entry-exports surfaces ~250 public-API findings that need their own sweep with explicit keep decisions. No behaviour change. The knip duplicates finding on the three intentional protocol alias files (the encode/decode result-codec pairs and the PAGE/RANGE bound pair, kept as plain aliases by the cleanup PR) is accepted with a file-scoped ignoreIssues entry in the runtime-host workspace. The duplicates check stays meaningful elsewhere: probe-verified that an injected unused export in artifact.ts is still reported, and the full npx knip run is clean. Generated-by: Claude Code --- .github/workflows/ci.yml | 11 ++- CONTRIBUTING.md | 3 +- CONTRIBUTING.zh-CN.md | 3 +- knip.json | 79 ++++++++++++++++++- package-lock.json | 6 ++ package.json | 8 +- scripts/check-knip-workspace-coverage.mjs | 51 ++++++++++++ .../check-knip-workspace-coverage.test.mjs | 51 ++++++++++++ 8 files changed, 200 insertions(+), 12 deletions(-) create mode 100644 scripts/check-knip-workspace-coverage.mjs create mode 100644 scripts/check-knip-workspace-coverage.test.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bb857dca1d..e5cb7d5ac1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -297,13 +297,12 @@ jobs: if: steps.plan.outputs.code == 'true' run: npm run astryx:theme -- --check - - name: Knip (apps/desktop) + - name: Knip (root + all workspaces) if: steps.plan.outputs.code == 'true' - run: npx knip --workspace apps/desktop - - - name: Knip (packages/ui) - if: steps.plan.outputs.code == 'true' - run: npx knip --workspace packages/ui + run: | + node --test scripts/check-knip-workspace-coverage.test.mjs + node scripts/check-knip-workspace-coverage.mjs + npx knip - name: Linux sandbox smoke if: steps.plan.outputs.runtime_sandbox == 'true' diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 89ec9df3eb..90f52168f9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -72,8 +72,7 @@ npm run lint npm run format:check npm run build npm run typecheck -npx knip --workspace apps/desktop -npx knip --workspace packages/ui +npx knip ``` Architecture is documented in [ARCHITECTURE.md](./ARCHITECTURE.md); evaluation commands and contracts live in [`packages/eval`](./packages/eval). diff --git a/CONTRIBUTING.zh-CN.md b/CONTRIBUTING.zh-CN.md index f7c4d3d041..8f0a1f7f4d 100644 --- a/CONTRIBUTING.zh-CN.md +++ b/CONTRIBUTING.zh-CN.md @@ -72,8 +72,7 @@ npm run lint npm run format:check npm run build npm run typecheck -npx knip --workspace apps/desktop -npx knip --workspace packages/ui +npx knip ``` 架构说明见 [ARCHITECTURE.zh-CN.md](./ARCHITECTURE.zh-CN.md);Eval 的命令与 contract 见 [`packages/eval`](./packages/eval)。 diff --git a/knip.json b/knip.json index 8158cfd292..711bd81123 100644 --- a/knip.json +++ b/knip.json @@ -3,6 +3,62 @@ "ignoreExportsUsedInFile": true, "tags": ["-knipignore"], "workspaces": { + ".": { + "entry": [ + "scripts/audit-alignment.mjs", + "scripts/computer-use/direct-runtime-ledger.mjs", + "scripts/computer-use/e2e-fixture.mjs", + "scripts/computer-use/prepare.mjs", + "scripts/computer-use/process-restart-harness.mjs", + "scripts/computer-use/process-restart-launcher.mjs", + "scripts/computer-use/provider-matrix.mjs", + "scripts/computer-use/real-ax-harness.mjs", + "scripts/computer-use/real-ax-launcher.mjs", + "scripts/computer-use/real-model-fixture.mjs", + "scripts/computer-use/real-model.mjs", + "scripts/generate-cu-status-icons.mjs", + "scripts/issue-pr-lifecycle.mjs", + "scripts/perf/*.{js,mjs}", + "scripts/pip-interaction-smoke.mjs", + "scripts/pr-effort.mjs", + "scripts/prepare-deepseek-harness-toolchain.mjs" + ], + "project": ["scripts/**/*.{mjs,js,cjs}"], + "ignoreBinaries": ["gpg", "swift"], + "ignoreDependencies": ["app-builder-lib", "builder-util", "patch-package"] + }, + "packages/runtime": { + "entry": ["src/filesystem-worker/worker-entry.ts"], + "project": ["src/**/*.ts", "scripts/*.mjs"] + }, + "packages/runtime-host": { + "ignoreBinaries": ["mkfifo", "taskkill.exe"], + "entry": [ + "src/__tests__/fixtures/stderr-after-launcher-exit.ts", + "src/__tests__/fixtures/windows-local-ipc-trust-host.ts", + "src/test-only/owned-candidate-gated-recovery-main.ts" + ], + "project": ["src/**/*.ts", "scripts/*.mjs"], + "ignoreIssues": { + "src/protocol/artifact.ts": ["duplicates"], + "src/protocol/session-transcript.ts": ["duplicates"], + "src/protocol/usage-pricing.ts": ["duplicates"] + } + }, + "packages/storage": { + "ignoreBinaries": ["mkfifo"], + "entry": [ + "src/__tests__/fixtures/session-bundle-hydration-binding-crash.ts", + "src/__tests__/fixtures/session-bundle-hydration-owner-write-failure.ts", + "src/__tests__/fixtures/session-bundle-inspect-child.ts", + "src/__tests__/fixtures/session-bundle-inspect-source-mutator.ts", + "src/__tests__/fixtures/session-bundle-pack-destination-replacer.ts", + "src/__tests__/fixtures/session-bundle-pack-link-replacer.ts", + "src/__tests__/fixtures/session-bundle-pack-linked-temp-remover.ts", + "src/__tests__/fixtures/session-bundle-pack-temp-replacer.ts" + ], + "project": ["src/**/*.ts"] + }, "apps/desktop": { "entry": [ "src/main/main.ts", @@ -27,8 +83,29 @@ }, "packages/ui": { "entry": ["src/**/*.test.ts", "src/**/*.test.tsx", "stories/**/*.@(ts|tsx)"], - "project": ["src/**/*.{ts,tsx}", "stories/**/*.{ts,tsx}"], + "project": ["src/**/*.{ts,tsx}", "stories/**/*.{ts,tsx}", "scripts/*.mjs"], "ignoreDependencies": ["@storybook/react-vite", "storybook"] + }, + "packages/core": { + "project": ["src/**/*.ts"] + }, + "packages/mcp": { + "project": ["src/**/*.ts"] + }, + "packages/computer-use": { + "project": ["src/**/*.ts"] + }, + "packages/cli": { + "project": ["src/**/*.ts", "scripts/*.mjs"] + }, + "packages/eval": { + "entry": ["harbor/deepseek-harness-toolchain/patch-subprocess-local.mjs"], + "project": ["src/**/*.ts", "harbor/**/*.mjs"], + "ignoreBinaries": ["taskkill.exe"] + }, + "website": { + "ignoreDependencies": ["cookie"], + "project": ["src/**/*.{ts,astro,mjs}", "scripts/*.mjs", "test/*.mjs"] } } } diff --git a/package-lock.json b/package-lock.json index 872cfd700d..a0bfd659bc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,14 +26,20 @@ "@ai-sdk/provider-utils": "5.0.34", "@astryxdesign/cli": "0.5.2", "@astryxdesign/core": "0.5.2", + "@babel/parser": "7.29.7", "@biomejs/biome": "2.5.11", "@electron/asar": "4.3.0", + "@playwright/test": "^1.62.1", "@types/node": "^26.4.0", + "electron": "43.4.1", + "electron-builder": "26.15.3", + "electron-updater": "^6.8.9", "esbuild": "^0.28.1", "husky": "^9.1.7", "knip": "^6.33.0", "patch-package": "8.0.1", "typescript": "^7.0.2", + "ws": "^8.21.3", "yaml": "2.9.0" }, "engines": { diff --git a/package.json b/package.json index 862fe01018..112d2d223b 100644 --- a/package.json +++ b/package.json @@ -121,7 +121,13 @@ "knip": "^6.33.0", "patch-package": "8.0.1", "typescript": "^7.0.2", - "yaml": "2.9.0" + "yaml": "2.9.0", + "electron": "43.4.1", + "electron-builder": "26.15.3", + "electron-updater": "^6.8.9", + "@playwright/test": "^1.62.1", + "@babel/parser": "7.29.7", + "ws": "^8.21.3" }, "allowScripts": { "esbuild@0.27.7": true, diff --git a/scripts/check-knip-workspace-coverage.mjs b/scripts/check-knip-workspace-coverage.mjs new file mode 100644 index 0000000000..fa7a61ad20 --- /dev/null +++ b/scripts/check-knip-workspace-coverage.mjs @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// Run by .github/workflows/ci.yml before `npx knip`. + +import { readFileSync } from 'node:fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath, pathToFileURL } from 'node:url'; + +const repoRoot = dirname(dirname(fileURLToPath(import.meta.url))); + +/** Unconfigured workspace = knip's weak defaults; stale key = a gate that guards nothing. */ +export function evaluateWorkspaceCoverage({ knipKeys, packageWorkspaces }) { + const memberKeys = knipKeys.filter((key) => key !== '.'); + const missing = packageWorkspaces.filter((dir) => !memberKeys.includes(dir)); + const stale = memberKeys.filter((key) => !packageWorkspaces.includes(key)); + return { ok: missing.length === 0 && stale.length === 0, missing, stale }; +} + +function main() { + const readJson = (path) => JSON.parse(readFileSync(resolve(repoRoot, path), 'utf8')); + const { ok, missing, stale } = evaluateWorkspaceCoverage({ + knipKeys: Object.keys(readJson('knip.json').workspaces ?? {}), + packageWorkspaces: readJson('package.json').workspaces ?? [], + }); + if (missing.length > 0) { + console.error(`package.json workspaces missing from knip.json: ${missing.join(', ')}`); + } + if (stale.length > 0) { + console.error(`knip.json workspaces without a package.json entry: ${stale.join(', ')}`); + } + if (!ok) process.exitCode = 1; +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) main(); diff --git a/scripts/check-knip-workspace-coverage.test.mjs b/scripts/check-knip-workspace-coverage.test.mjs new file mode 100644 index 0000000000..bc2e6e480c --- /dev/null +++ b/scripts/check-knip-workspace-coverage.test.mjs @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { evaluateWorkspaceCoverage } from './check-knip-workspace-coverage.mjs'; + +test('a package.json workspace missing from knip.json is reported', () => { + const { ok, missing, stale } = evaluateWorkspaceCoverage({ + knipKeys: ['.', 'packages/runtime'], + packageWorkspaces: ['packages/runtime', 'packages/storage'], + }); + assert.equal(ok, false); + assert.deepEqual(missing, ['packages/storage']); + assert.deepEqual(stale, []); +}); + +test('a stale knip.json key is reported even when other workspaces are covered', () => { + const { ok, missing, stale } = evaluateWorkspaceCoverage({ + knipKeys: ['.', 'packages/runtime', 'packages/retired'], + packageWorkspaces: ['packages/runtime'], + }); + assert.equal(ok, false); + assert.deepEqual(missing, []); + assert.deepEqual(stale, ['packages/retired']); +}); + +test('the root knip key is exempt from the stale check', () => { + const { ok, stale } = evaluateWorkspaceCoverage({ + knipKeys: ['.', 'packages/runtime'], + packageWorkspaces: ['packages/runtime'], + }); + assert.equal(ok, true); + assert.deepEqual(stale, []); +});