From 19e6f7d907e5bb12f0760606e7bff8bcc44d3d33 Mon Sep 17 00:00:00 2001 From: imccyu <276526105+imccyu@users.noreply.github.com> Date: Wed, 22 Jul 2026 23:58:37 +0800 Subject: [PATCH 01/11] refactor(tsconfig): single root solution graph over host/client aggregates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root tsconfig.json becomes a pure solution (files:[] + two references); the former root host aggregate moves verbatim to tsconfig.host.json. New tsconfig.base.client.json carries the shared client compiler shape (jsx/DOM lib/types:[]), and tsconfig.client.json plus the 12 packages/client tsconfigs extend it instead of restating the trio. tsconfig.build.json is deleted: the solution covers the full emit graph, absorbing the command-goal typecheck/build drift. Consumers migrate to the single graph: typecheck/build scripts and the lefthook pre-push hook run bare `tsc -b` (pre-push now covers the client side); the run-gates build gate needs typecheck so two concurrent tsc -b runs cannot race the same tsbuildinfo; ts-project.ts and the standalone doc-typecheck mode seed tsconfig.host.json explicitly (never the root solution — flattening host+client into one program collides the cordis Context merges); verify-cordis-config BFS seeds the root solution alone. Per missions/tsconfig-single-graph-migration.md §2–§3. --- lefthook.yml | 2 +- package.json | 4 +- packages/client/connection/tsconfig.json | 10 +- packages/client/i18n/tsconfig.json | 10 +- packages/client/runtime/tsconfig.json | 10 +- packages/client/ui-conversation/tsconfig.json | 11 +- packages/client/ui-layout/tsconfig.json | 11 +- packages/client/ui-primitives/tsconfig.json | 11 +- packages/client/ui-sidebar/tsconfig.json | 11 +- packages/client/ui-slots/tsconfig.json | 10 +- packages/client/ui-theme/tsconfig.json | 10 +- packages/client/ui-trajectory/tsconfig.json | 11 +- packages/client/web-react/tsconfig.json | 11 +- packages/client/web/tsconfig.json | 11 +- scripts/doc-typecheck.ts | 22 ++- scripts/gen-scoped-events.ts | 2 +- scripts/run-gates.ts | 5 +- scripts/ts-project.ts | 8 +- scripts/verify-cordis-config.ts | 11 +- tsconfig.base.client.json | 11 ++ tsconfig.client.json | 7 +- tsconfig.build.json => tsconfig.host.json | 42 +++-- tsconfig.json | 154 ++---------------- tsdown.config.ts | 2 +- 24 files changed, 111 insertions(+), 286 deletions(-) create mode 100644 tsconfig.base.client.json rename tsconfig.build.json => tsconfig.host.json (88%) diff --git a/lefthook.yml b/lefthook.yml index 49a1499976..cf2e6bb11d 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -20,4 +20,4 @@ pre-commit: pre-push: jobs: - name: typecheck - run: node_modules/.bin/tsc -b tsconfig.json --pretty false + run: node_modules/.bin/tsc -b --pretty false diff --git a/package.json b/package.json index 611e1d49e2..38db3a2ac7 100644 --- a/package.json +++ b/package.json @@ -14,10 +14,10 @@ "website" ], "scripts": { - "build": "tsc -b tsconfig.build.json && tsdown", + "build": "tsc -b && tsdown", "build:web": "pnpm --filter @deepseek-ai/dsh-frontend run build", "clean:build": "rm -rf .typecheck packages/*/*/lib vendor/*/lib *.tsbuildinfo", - "typecheck": "tsc -b tsconfig.json tsconfig.client.json", + "typecheck": "tsc -b", "lint": "eslint .", "lint:fix": "eslint . --fix", "duplication": "jscpd --config .jscpd.json packages scripts", diff --git a/packages/client/connection/tsconfig.json b/packages/client/connection/tsconfig.json index 3f45ca0231..8b0357cf97 100644 --- a/packages/client/connection/tsconfig.json +++ b/packages/client/connection/tsconfig.json @@ -1,14 +1,8 @@ { - "extends": "../../../tsconfig.base.json", + "extends": "../../../tsconfig.base.client.json", "compilerOptions": { "rootDir": "src", - "outDir": "lib/types", - "lib": [ - "ES2024", - "DOM", - "DOM.Iterable" - ], - "types": [] + "outDir": "lib/types" }, "include": [ "src" diff --git a/packages/client/i18n/tsconfig.json b/packages/client/i18n/tsconfig.json index 76dd83e8d6..63eb9779ca 100644 --- a/packages/client/i18n/tsconfig.json +++ b/packages/client/i18n/tsconfig.json @@ -1,14 +1,8 @@ { - "extends": "../../../tsconfig.base.json", + "extends": "../../../tsconfig.base.client.json", "compilerOptions": { "rootDir": "src", - "outDir": "lib/types", - "lib": [ - "ES2024", - "DOM", - "DOM.Iterable" - ], - "types": [] + "outDir": "lib/types" }, "include": [ "src" diff --git a/packages/client/runtime/tsconfig.json b/packages/client/runtime/tsconfig.json index 82ebf4f989..9584ecc857 100644 --- a/packages/client/runtime/tsconfig.json +++ b/packages/client/runtime/tsconfig.json @@ -1,14 +1,8 @@ { - "extends": "../../../tsconfig.base.json", + "extends": "../../../tsconfig.base.client.json", "compilerOptions": { "rootDir": "src", - "outDir": "lib/types", - "lib": [ - "ES2024", - "DOM", - "DOM.Iterable" - ], - "types": [] + "outDir": "lib/types" }, "include": [ "src" diff --git a/packages/client/ui-conversation/tsconfig.json b/packages/client/ui-conversation/tsconfig.json index ed0065fa04..7755337093 100644 --- a/packages/client/ui-conversation/tsconfig.json +++ b/packages/client/ui-conversation/tsconfig.json @@ -1,15 +1,8 @@ { - "extends": "../../../tsconfig.base.json", + "extends": "../../../tsconfig.base.client.json", "compilerOptions": { "rootDir": "src", - "outDir": "lib/types", - "jsx": "react-jsx", - "lib": [ - "ES2024", - "DOM", - "DOM.Iterable" - ], - "types": [] + "outDir": "lib/types" }, "include": [ "src" diff --git a/packages/client/ui-layout/tsconfig.json b/packages/client/ui-layout/tsconfig.json index f2c03d8d12..c82482294b 100644 --- a/packages/client/ui-layout/tsconfig.json +++ b/packages/client/ui-layout/tsconfig.json @@ -1,15 +1,8 @@ { - "extends": "../../../tsconfig.base.json", + "extends": "../../../tsconfig.base.client.json", "compilerOptions": { "rootDir": "src", - "outDir": "lib/types", - "jsx": "react-jsx", - "lib": [ - "ES2024", - "DOM", - "DOM.Iterable" - ], - "types": [] + "outDir": "lib/types" }, "include": [ "src" diff --git a/packages/client/ui-primitives/tsconfig.json b/packages/client/ui-primitives/tsconfig.json index fa9b40f9e6..e2650b47b6 100644 --- a/packages/client/ui-primitives/tsconfig.json +++ b/packages/client/ui-primitives/tsconfig.json @@ -1,15 +1,8 @@ { - "extends": "../../../tsconfig.base.json", + "extends": "../../../tsconfig.base.client.json", "compilerOptions": { "rootDir": "src", - "outDir": "lib/types", - "jsx": "react-jsx", - "lib": [ - "ES2024", - "DOM", - "DOM.Iterable" - ], - "types": [] + "outDir": "lib/types" }, "include": [ "src" diff --git a/packages/client/ui-sidebar/tsconfig.json b/packages/client/ui-sidebar/tsconfig.json index a560748366..c48fe2567f 100644 --- a/packages/client/ui-sidebar/tsconfig.json +++ b/packages/client/ui-sidebar/tsconfig.json @@ -1,15 +1,8 @@ { - "extends": "../../../tsconfig.base.json", + "extends": "../../../tsconfig.base.client.json", "compilerOptions": { "rootDir": "src", - "outDir": "lib/types", - "jsx": "react-jsx", - "lib": [ - "ES2024", - "DOM", - "DOM.Iterable" - ], - "types": [] + "outDir": "lib/types" }, "include": [ "src" diff --git a/packages/client/ui-slots/tsconfig.json b/packages/client/ui-slots/tsconfig.json index fc829c7a83..b8ccb71310 100644 --- a/packages/client/ui-slots/tsconfig.json +++ b/packages/client/ui-slots/tsconfig.json @@ -1,14 +1,8 @@ { - "extends": "../../../tsconfig.base.json", + "extends": "../../../tsconfig.base.client.json", "compilerOptions": { "rootDir": "src", - "outDir": "lib/types", - "lib": [ - "ES2024", - "DOM", - "DOM.Iterable" - ], - "types": [] + "outDir": "lib/types" }, "include": [ "src" diff --git a/packages/client/ui-theme/tsconfig.json b/packages/client/ui-theme/tsconfig.json index 076aa22e9f..51f9171643 100644 --- a/packages/client/ui-theme/tsconfig.json +++ b/packages/client/ui-theme/tsconfig.json @@ -1,14 +1,8 @@ { - "extends": "../../../tsconfig.base.json", + "extends": "../../../tsconfig.base.client.json", "compilerOptions": { "rootDir": "src", - "outDir": "lib/types", - "lib": [ - "ES2024", - "DOM", - "DOM.Iterable" - ], - "types": [] + "outDir": "lib/types" }, "include": [ "src" diff --git a/packages/client/ui-trajectory/tsconfig.json b/packages/client/ui-trajectory/tsconfig.json index e3b1abe266..f525474d9d 100644 --- a/packages/client/ui-trajectory/tsconfig.json +++ b/packages/client/ui-trajectory/tsconfig.json @@ -1,15 +1,8 @@ { - "extends": "../../../tsconfig.base.json", + "extends": "../../../tsconfig.base.client.json", "compilerOptions": { "rootDir": "src", - "outDir": "lib/types", - "jsx": "react-jsx", - "lib": [ - "ES2024", - "DOM", - "DOM.Iterable" - ], - "types": [] + "outDir": "lib/types" }, "include": [ "src" diff --git a/packages/client/web-react/tsconfig.json b/packages/client/web-react/tsconfig.json index 57b533b2a6..90b7619fed 100644 --- a/packages/client/web-react/tsconfig.json +++ b/packages/client/web-react/tsconfig.json @@ -1,15 +1,8 @@ { - "extends": "../../../tsconfig.base.json", + "extends": "../../../tsconfig.base.client.json", "compilerOptions": { "rootDir": "src", - "outDir": "lib/types", - "jsx": "react-jsx", - "lib": [ - "ES2024", - "DOM", - "DOM.Iterable" - ], - "types": [] + "outDir": "lib/types" }, "include": [ "src" diff --git a/packages/client/web/tsconfig.json b/packages/client/web/tsconfig.json index 87a6b0dcab..f974787045 100644 --- a/packages/client/web/tsconfig.json +++ b/packages/client/web/tsconfig.json @@ -1,15 +1,8 @@ { - "extends": "../../../tsconfig.base.json", + "extends": "../../../tsconfig.base.client.json", "compilerOptions": { "rootDir": "src", - "outDir": "lib/types", - "jsx": "react-jsx", - "lib": [ - "ES2024", - "DOM", - "DOM.Iterable" - ], - "types": [] + "outDir": "lib/types" }, "include": [ "src" diff --git a/scripts/doc-typecheck.ts b/scripts/doc-typecheck.ts index 5034d65cae..9a9e007758 100644 --- a/scripts/doc-typecheck.ts +++ b/scripts/doc-typecheck.ts @@ -55,15 +55,20 @@ const configHost: ts.ParseConfigFileHost = { }, } -/** Load root settings and redirect workspace aliases to declarations from the coordinated build. */ +/** + * Load host-aggregate settings and redirect workspace aliases to declarations + * from the coordinated build. Doc fragments speak the host vocabulary; the host + * aggregate (never the root solution — it has no compilerOptions) carries the + * workspace paths via tsconfig.base.json. + */ function builtTypeCompilerOptions(): ts.CompilerOptions { - const configPath = join(root, 'tsconfig.json') + const configPath = join(root, 'tsconfig.host.json') const parsed = ts.getParsedCommandLineOfConfigFile(configPath, {}, configHost) if (!parsed) throw new Error(`doc-typecheck: cannot parse ${configPath}`) if (parsed.errors.length > 0) { throw new Error(parsed.errors.map(error => ts.flattenDiagnosticMessageText(error.messageText, '\n')).join('\n')) } - if (parsed.options.paths === undefined) throw new Error('doc-typecheck: root tsconfig has no workspace paths') + if (parsed.options.paths === undefined) throw new Error('doc-typecheck: host tsconfig has no workspace paths') const paths = Object.fromEntries(Object.entries(parsed.options.paths).map(([specifier, candidates]) => [ specifier, candidates.map(builtDeclarationPath), @@ -125,9 +130,14 @@ function formatDiagnostics(diagnostics: readonly ts.Diagnostic[], blocks: Block[ return remapBlockPaths(formatted, blocks) } -/** Reuse the repo typecheck graph references from a temp project one directory below root. */ +/** + * Reuse the host-aggregate references from a temp project one directory below + * root. Doc fragments speak the host vocabulary, so the standalone project + * seeds tsconfig.host.json (never the root solution: flattening host+client + * into one program collides the cordis Context merges). + */ function workspaceReferences(): { path: string }[] { - const file = join(root, 'tsconfig.json') + const file = join(root, 'tsconfig.host.json') // Parse with TypeScript's own JSONC reader: a regex comment stripper corrupts the `/*/` path // candidate in the workspace wildcard. const result = ts.readConfigFile(file, path => readFileSync(path, 'utf8')) @@ -144,7 +154,7 @@ function workspaceReferences(): { path: string }[] { /** The standalone temp project used when no coordinated build owns declaration freshness. */ function tempTsconfig(): string { return JSON.stringify({ - extends: '../tsconfig.json', + extends: '../tsconfig.host.json', compilerOptions: { noUnusedLocals: false, noUnusedParameters: false, diff --git a/scripts/gen-scoped-events.ts b/scripts/gen-scoped-events.ts index 4aa6637934..ead001a79b 100644 --- a/scripts/gen-scoped-events.ts +++ b/scripts/gen-scoped-events.ts @@ -370,7 +370,7 @@ function quote(value: string): string { /** * Render the generated scoped-event resolver module for one repository root. - * @param projectRoot - repository root carrying tsconfig.json. + * @param projectRoot - repository root carrying tsconfig.host.json. * @returns complete generated TypeScript source. */ export function renderScopedEvents(projectRoot: string = root): string { diff --git a/scripts/run-gates.ts b/scripts/run-gates.ts index 1c3958434c..896ffacfd1 100644 --- a/scripts/run-gates.ts +++ b/scripts/run-gates.ts @@ -228,7 +228,10 @@ function ciPrimaryGates(): Gate[] { ...docSyncLeafGates(), pnpmScript('module-graph', 'verify-module-graph', { label: 'module graph' }), pnpmScript('knip', 'knip'), - pnpmScript('build', 'build'), + // typecheck and build now drive the same root solution graph; without the + // dependency two concurrent `tsc -b` runs race the same tsbuildinfo files. + // The tsc step is an incremental no-op after typecheck. + pnpmScript('build', 'build', { needs: ['typecheck'] }), pnpmScript('publint', 'publint', { needs: ['build'] }), pnpmScript('node-next-types', 'verify-node-next-types', { label: 'node-next types', diff --git a/scripts/ts-project.ts b/scripts/ts-project.ts index 0b59aee873..930c055d4f 100644 --- a/scripts/ts-project.ts +++ b/scripts/ts-project.ts @@ -22,9 +22,13 @@ const configHost: ts.ParseConfigFileHost = { }, } -/** Parse a root tsconfig and flatten all referenced projects into one semantic graph. */ +/** + * Parse the host aggregate tsconfig and flatten all referenced projects into one + * semantic graph. Never seed the root solution: flattening host+client into one + * program collides the cordis Context merges. + */ function loadProjectGraph(projectRoot: string): ProjectGraph { - const rootConfigPath = resolve(projectRoot, 'tsconfig.json') + const rootConfigPath = resolve(projectRoot, 'tsconfig.host.json') const rootConfig = parseConfig(rootConfigPath) const rootNames = new Set() const visited = new Set() diff --git a/scripts/verify-cordis-config.ts b/scripts/verify-cordis-config.ts index c9820a67a8..916db4a168 100644 --- a/scripts/verify-cordis-config.ts +++ b/scripts/verify-cordis-config.ts @@ -152,12 +152,13 @@ function localPackageDirectories(): Map { } function rootProjectReferences(): Set { - // Typecheck runs two sibling aggregates (root = host program, - // tsconfig.client.json = client program; the two sides merge cordis Context - // under the same keys, so one program cannot see both). Seed both and follow - // any nested aggregate references to collect the covered leaf project set. + // The root solution references the host and client aggregates (the two + // sides merge cordis Context under the same keys, so one program cannot see + // both — but this BFS only collects reference paths, it never forms a + // program). Seed the solution and follow nested aggregate references to + // collect the covered leaf project set. const collected = new Set() - const queue = [resolve(root, 'tsconfig.json'), resolve(root, 'tsconfig.client.json')] + const queue = [resolve(root, 'tsconfig.json')] const seen = new Set() for (let file = queue.pop(); file !== undefined; file = queue.pop()) { if (seen.has(file)) continue diff --git a/tsconfig.base.client.json b/tsconfig.base.client.json new file mode 100644 index 0000000000..411c006da9 --- /dev/null +++ b/tsconfig.base.client.json @@ -0,0 +1,11 @@ +{ + // Client-side compiler shape shared by tsconfig.client.json and every + // packages/client/* package: browser lib surface, React JSX, no ambient + // node types (packages that need them override locally). + "extends": "./tsconfig.base.json", + "compilerOptions": { + "jsx": "react-jsx", + "lib": ["ES2024", "DOM", "DOM.Iterable"], + "types": [] + } +} diff --git a/tsconfig.client.json b/tsconfig.client.json index ceb498a1a9..d7ee9090e7 100644 --- a/tsconfig.client.json +++ b/tsconfig.client.json @@ -4,14 +4,13 @@ // under the same keys (sessions, loader) with different services; shared // leaves (session/llm/tools/apiproxy/...) build once and are referenced by // both programs through each client package's own references. - "extends": "./tsconfig.base.json", + "extends": "./tsconfig.base.client.json", "compilerOptions": { "noEmit": true, "rewriteRelativeImportExtensions": false, - "jsx": "react-jsx", - "lib": ["ES2024", "DOM", "DOM.Iterable"], // Tests execute under vitest on node (e2e files spawn processes); browser - // purity of package src is each package's own tsconfig + verify-client-closure. + // purity of package src is each package's own tsconfig plus + // scripts/client-bundle-purity.spec.ts. "types": ["node"] }, "include": [ diff --git a/tsconfig.build.json b/tsconfig.host.json similarity index 88% rename from tsconfig.build.json rename to tsconfig.host.json index fc0050f34b..f340f235da 100644 --- a/tsconfig.build.json +++ b/tsconfig.host.json @@ -1,5 +1,25 @@ { - "files": [], + // Host aggregate (one of the two check units; see tsconfig.json). packages/client + // type-checks in tsconfig.client.json: the two sides merge cordis Context under + // the same keys, one program cannot see both. + "extends": "./tsconfig.base.json", + "compilerOptions": { + "noEmit": true, + "rewriteRelativeImportExtensions": false + }, + "include": [ + "examples/*/src/**/*.ts", + "examples/*/start.ts", + "examples/*/tests/**/*.ts", + "packages/*/*/tests/**/*.ts", + "scripts/**/*.ts", + "website/**/*.ts", + "website/.vitepress/**/*.ts" + ], + "exclude": [ + "packages/client/**", + "scripts/client-bundle-purity.spec.ts" + ], "references": [ { "path": "./vendor/cosmokit" }, { "path": "./vendor/schemastery" }, @@ -33,7 +53,9 @@ { "path": "./packages/goal/goal" }, { "path": "./packages/goal/tool-goal" }, { "path": "./packages/goal/goal-session" }, + { "path": "./packages/goal/command-goal" }, { "path": "./packages/context/time-context" }, + { "path": "./packages/context/session-reference" }, { "path": "./packages/ui/user-interaction" }, { "path": "./packages/ui/user-approval" }, { "path": "./packages/ui/permission" }, @@ -53,9 +75,6 @@ { "path": "./packages/pty/tool-pty" }, { "path": "./packages/code-runtime/code-runtime" }, { "path": "./packages/code-runtime/code-runtime-worker" }, - { "path": "./packages/compact/compact" }, - { "path": "./packages/compact/compact-basic" }, - { "path": "./packages/compact/compact-tool-result-prune" }, { "path": "./packages/llm/llm-deepseek" }, { "path": "./packages/llm/llm-pi-ai" }, { "path": "./packages/bash/bash-local" }, @@ -70,6 +89,9 @@ { "path": "./packages/fs/fs-sandbox" }, { "path": "./packages/fs/tool-fs" }, { "path": "./packages/fs/tool-fs-search" }, + { "path": "./packages/compact/compact" }, + { "path": "./packages/compact/compact-basic" }, + { "path": "./packages/compact/compact-tool-result-prune" }, { "path": "./packages/web/web" }, { "path": "./packages/web/web-search-exa" }, { "path": "./packages/web/web-search-perplexity" }, @@ -116,18 +138,6 @@ { "path": "./packages/host/apiproxy" }, { "path": "./packages/host/runtime" }, { "path": "./packages/host/webserver" }, - { "path": "./packages/client/ui-slots" }, - { "path": "./packages/client/ui-primitives" }, - { "path": "./packages/client/web-react" }, - { "path": "./packages/client/connection" }, - { "path": "./packages/client/runtime" }, - { "path": "./packages/client/ui-layout" }, - { "path": "./packages/client/ui-sidebar" }, - { "path": "./packages/client/ui-conversation" }, - { "path": "./packages/client/ui-trajectory" }, - { "path": "./packages/client/ui-theme" }, - { "path": "./packages/client/i18n" }, - { "path": "./packages/client/web" }, { "path": "./packages/sdk/helper" }, { "path": "./packages/sdk/scripts" }, { "path": "./packages/sdk/create-sdk" }, diff --git a/tsconfig.json b/tsconfig.json index c42fda4147..a98f88aae0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,149 +1,15 @@ { - // Host aggregate. packages/client type-checks in tsconfig.client.json: the two - // sides merge cordis Context under the same keys, one program cannot see both. + // Solution file: the whole-repo graph for `tsc -b tsconfig.json` and the + // tsserver entry. `extends` carries the base paths for get-tsconfig + // consumers — tsx running examples/ and scripts/ (no nearer tsconfig) + // resolves workspace imports through this file. `files: []` keeps it + // program-less, so the host/client cordis Context merges never meet. + // NEVER add include/files entries, and NEVER flatten this solution into a + // single ts.Program (scripts seed tsconfig.host.json or tsconfig.client.json). "extends": "./tsconfig.base.json", - "compilerOptions": { - "noEmit": true, - "rewriteRelativeImportExtensions": false - }, - "include": [ - "examples/*/src/**/*.ts", - "examples/*/start.ts", - "examples/*/tests/**/*.ts", - "packages/*/*/tests/**/*.ts", - "scripts/**/*.ts", - "website/**/*.ts", - "website/.vitepress/**/*.ts" - ], - "exclude": [ - "packages/client/**", - "scripts/client-bundle-purity.spec.ts" - ], + "files": [], "references": [ - { "path": "./vendor/cosmokit" }, - { "path": "./vendor/schemastery" }, - { "path": "./vendor/cordis" }, - { "path": "./vendor/loader" }, - { "path": "./vendor/include" }, - { "path": "./vendor/group" }, - { "path": "./vendor/timer" }, - { "path": "./vendor/hmr" }, - { "path": "./vendor/logger-console" }, - { "path": "./packages/util/brand" }, - { "path": "./packages/util/paths" }, - { "path": "./packages/util/timeout" }, - { "path": "./packages/util/retention" }, - { "path": "./packages/llm/llm" }, - { "path": "./packages/llm/token-meter" }, - { "path": "./packages/core/session" }, - { "path": "./packages/core/scope" }, - { "path": "./packages/session-persistence/session-persistence" }, - { "path": "./packages/session-persistence/session-checkpoint-policy" }, - { "path": "./packages/session-persistence/session-persistence-jsonl" }, - { "path": "./packages/session-persistence/session-persistence-sqlite" }, - { "path": "./packages/session-query/session-query" }, - { "path": "./packages/session-title/session-title" }, - { "path": "./packages/session-title/session-title-llm" }, - { "path": "./packages/session-title/session-title-first-message-llm" }, - { "path": "./packages/session-title/session-title-all-messages-llm" }, - { "path": "./packages/core/system-prompt" }, - { "path": "./packages/core/agent" }, - { "path": "./packages/ui/commands" }, - { "path": "./packages/goal/goal" }, - { "path": "./packages/goal/tool-goal" }, - { "path": "./packages/goal/goal-session" }, - { "path": "./packages/goal/command-goal" }, - { "path": "./packages/context/time-context" }, - { "path": "./packages/context/session-reference" }, - { "path": "./packages/ui/user-interaction" }, - { "path": "./packages/ui/user-approval" }, - { "path": "./packages/ui/permission" }, - { "path": "./packages/core/tools" }, - { "path": "./packages/skill/skill" }, - { "path": "./packages/skill/skill-local" }, - { "path": "./packages/skill/tool-skill" }, - { "path": "./packages/ui/tool-ask-user" }, - { "path": "./packages/context/workspace-context" }, - { "path": "./packages/core/agent-loop" }, - { "path": "./packages/llm/llm-retry" }, - { "path": "./packages/examples/agent-spine-demo" }, - { "path": "./packages/examples/cli-demo" }, - { "path": "./packages/bash/bash" }, - { "path": "./packages/pty/pty" }, - { "path": "./packages/pty/pty-local" }, - { "path": "./packages/pty/tool-pty" }, - { "path": "./packages/code-runtime/code-runtime" }, - { "path": "./packages/code-runtime/code-runtime-worker" }, - { "path": "./packages/llm/llm-deepseek" }, - { "path": "./packages/llm/llm-pi-ai" }, - { "path": "./packages/bash/bash-local" }, - { "path": "./packages/sandbox/sandbox" }, - { "path": "./packages/sandbox/sandbox-local" }, - { "path": "./packages/sandbox/sandbox-policy" }, - { "path": "./packages/bash/bash-sandbox" }, - { "path": "./packages/bash/tool-bash" }, - { "path": "./packages/fs/fs" }, - { "path": "./packages/fs/fs-local" }, - { "path": "./packages/fs/fs-policy" }, - { "path": "./packages/fs/fs-sandbox" }, - { "path": "./packages/fs/tool-fs" }, - { "path": "./packages/fs/tool-fs-search" }, - { "path": "./packages/compact/compact" }, - { "path": "./packages/compact/compact-basic" }, - { "path": "./packages/compact/compact-tool-result-prune" }, - { "path": "./packages/web/web" }, - { "path": "./packages/web/web-search-exa" }, - { "path": "./packages/web/web-search-perplexity" }, - { "path": "./packages/web/web-search-deepseek" }, - { "path": "./packages/web/web-fetch-local" }, - { "path": "./packages/web/tool-web" }, - { "path": "./packages/spill/spill" }, - { "path": "./packages/spill/spill-local" }, - { "path": "./packages/spill/spill-policy" }, - { "path": "./packages/timeout/timeout-policy" }, - { "path": "./packages/support/invariants" }, - { "path": "./packages/support/agent-loop-testkit" }, - { "path": "./packages/ui/acp" }, - { "path": "./packages/examples/acp-demo" }, - { "path": "./packages/ui/app-boot" }, - { "path": "./packages/ui/jsonrpc" }, - { "path": "./packages/examples/jsonrpc-demo" }, - { "path": "./packages/ui/tui" }, - { "path": "./packages/examples/tui-demo" }, - { "path": "./packages/support/llm-replay" }, - { "path": "./packages/support/acp-snapshot" }, - { "path": "./packages/support/loader-smoke" }, - { "path": "./packages/subagent/subagent" }, - { "path": "./packages/subagent/tool-subagent" }, - { "path": "./packages/subagent/subagent-inprocess" }, - { "path": "./packages/subagent/subagent-subprocess" }, - { "path": "./packages/subagent/subagent-spawn" }, - { "path": "./packages/subagent/subagent-fork" }, - { "path": "./packages/subagent/subagent-acp" }, - { "path": "./packages/tasks/tasks" }, - { "path": "./packages/tasks/tool-tasks" }, - { "path": "./packages/workflow/workflow" }, - { "path": "./packages/workflow/workflow-workerthread" }, - { "path": "./packages/workflow/tool-workflow" }, - { "path": "./packages/workflow/tool-ralph" }, - { "path": "./packages/todo/tool-todo" }, - { "path": "./packages/plan/plan-mode" }, - { "path": "./packages/guard/repeat-tool-guard" }, - { "path": "./packages/cordis/tool-cordis" }, - { "path": "./packages/hooks/hook-protocol" }, - { "path": "./packages/hooks/hooks-claude" }, - { "path": "./packages/hooks/hooks-codex" }, - { "path": "./packages/mcp/mcp-client" }, - { "path": "./packages/host/apiproxy" }, - { "path": "./packages/host/runtime" }, - { "path": "./packages/host/webserver" }, - { "path": "./packages/sdk/helper" }, - { "path": "./packages/sdk/scripts" }, - { "path": "./packages/sdk/create-sdk" }, - { "path": "./packages/sdk/telemetry" }, - { "path": "./packages/lsp/lsp" }, - { "path": "./packages/lsp/lsp-local" }, - { "path": "./packages/lsp/tool-lsp" }, - { "path": "./apps/cli" } + { "path": "./tsconfig.host.json" }, + { "path": "./tsconfig.client.json" } ] } diff --git a/tsdown.config.ts b/tsdown.config.ts index 8ad1a5c41c..41a490436f 100644 --- a/tsdown.config.ts +++ b/tsdown.config.ts @@ -2,7 +2,7 @@ import { defineConfig } from 'tsdown' /** * JS bundling for vendored Cordis and Harness TypeScript packages. - * TypeScript source is compiled first by `tsc -b tsconfig.build.json`; tsdown + * TypeScript source is compiled first by `tsc -b` (the root solution); tsdown * reads only the emitted JS under lib/types and writes the package root and * invariant companion runtime bundles. Declarations are NOT produced here, * hence `dts: false`. From e1cb3da75516fa22c912fc50fca119f78eb6b829 Mon Sep 17 00:00:00 2001 From: imccyu <276526105+imccyu@users.noreply.github.com> Date: Thu, 23 Jul 2026 00:02:21 +0800 Subject: [PATCH 02/11] refactor(vitest): resolve every lane through the tsconfig.base.json facade MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tsconfig.vitest.json is deleted; its job (a paths map applying to all test files) is inherent in tsconfig.base.json having no include — vite-tsconfig-paths treats that as match-all. All four vitest configs now pin the same facade: the unit config gains a shared pathsPlugin() helper, the web lane drops its handwritten webserver alias (apps/web tests are covered by match-all), and the snapshot lane leaves the root solution (which no longer carries paths) and stops resolving client imports through package exports. tsconfig.base.json documents the facade role and bans include/files; the CI eslint cache key hashes the four graph tsconfigs; the eslint tests-block comment states resolution via the solution to tsconfig.host.json. Per missions/tsconfig-single-graph-migration.md §3. --- .github/workflows/ci.yml | 10 +++++----- eslint.config.mjs | 3 ++- tsconfig.base.json | 8 ++++++-- tsconfig.vitest.json | 15 --------------- vitest.config.ts | 17 +++++++++-------- vitest.e2e.config.ts | 14 +++++++------- vitest.snapshot.config.ts | 5 +++-- vitest.web.config.ts | 13 +++++-------- 8 files changed, 37 insertions(+), 48 deletions(-) delete mode 100644 tsconfig.vitest.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f52274cddd..2c6036968f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -58,7 +58,7 @@ jobs: - uses: actions/cache/restore@v4 with: path: .cache/eslint - key: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-eslint-full-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.mjs', 'tsconfig.json', 'packages/*/*/tsconfig.json', 'examples/*/tsconfig.json') }} + key: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-eslint-full-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.mjs', 'tsconfig.json', 'tsconfig.base.json', 'tsconfig.base.client.json', 'tsconfig.host.json', 'tsconfig.client.json', 'packages/*/*/tsconfig.json', 'examples/*/tsconfig.json') }} restore-keys: | ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-eslint-full- @@ -173,7 +173,7 @@ jobs: - uses: actions/cache/restore@v4 with: path: .cache/eslint - key: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-eslint-full-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.mjs', 'tsconfig.json', 'packages/*/*/tsconfig.json', 'examples/*/tsconfig.json') }} + key: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-eslint-full-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.mjs', 'tsconfig.json', 'tsconfig.base.json', 'tsconfig.base.client.json', 'tsconfig.host.json', 'tsconfig.client.json', 'packages/*/*/tsconfig.json', 'examples/*/tsconfig.json') }} restore-keys: | ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-eslint-full- @@ -237,7 +237,7 @@ jobs: - uses: actions/cache@v4 with: path: .cache/eslint - key: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-eslint-full-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.mjs', 'tsconfig.json', 'packages/*/*/tsconfig.json', 'examples/*/tsconfig.json') }} + key: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-eslint-full-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.mjs', 'tsconfig.json', 'tsconfig.base.json', 'tsconfig.base.client.json', 'tsconfig.host.json', 'tsconfig.client.json', 'packages/*/*/tsconfig.json', 'examples/*/tsconfig.json') }} restore-keys: | ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-eslint-full- @@ -309,7 +309,7 @@ jobs: - uses: actions/cache@v4 with: path: .cache/eslint - key: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-eslint-full-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.mjs', 'tsconfig.json', 'packages/*/*/tsconfig.json', 'examples/*/tsconfig.json') }} + key: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-eslint-full-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.mjs', 'tsconfig.json', 'tsconfig.base.json', 'tsconfig.base.client.json', 'tsconfig.host.json', 'tsconfig.client.json', 'packages/*/*/tsconfig.json', 'examples/*/tsconfig.json') }} restore-keys: | ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-eslint-full- @@ -525,7 +525,7 @@ jobs: if: matrix.platform == 'linux' with: path: .cache/eslint - key: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-eslint-full-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.mjs', 'tsconfig.json', 'packages/*/*/tsconfig.json', 'examples/*/tsconfig.json') }} + key: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-eslint-full-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.mjs', 'tsconfig.json', 'tsconfig.base.json', 'tsconfig.base.client.json', 'tsconfig.host.json', 'tsconfig.client.json', 'packages/*/*/tsconfig.json', 'examples/*/tsconfig.json') }} restore-keys: | ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-eslint-full- diff --git a/eslint.config.mjs b/eslint.config.mjs index 334d3799af..eec63181df 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -86,7 +86,8 @@ export default tseslint.config( languageOptions: { parserOptions: { // Same shared project service as the src block: test files resolve - // through the root tsconfig (its include covers every tests/ tree). + // through the root solution to tsconfig.host.json (its include covers + // every host tests/ tree). projectService: true, tsconfigRootDir: import.meta.dirname, }, diff --git a/tsconfig.base.json b/tsconfig.base.json index 28f7e2d0f3..2018a1c747 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1,4 +1,7 @@ { + // Doubles as the resolution facade for vite-tsconfig-paths (vitest configs + // point here). NEVER add include/files to this file: it would leak into + // every extending package project and narrow the facade's match-all scope. "compilerOptions": { "target": "es2024", "module": "esnext", @@ -87,8 +90,9 @@ // One wildcard maps every @deepseek-ai/dsh- to its source. Package // dir names are unique across groups, so first-on-disk-wins resolution is // unambiguous; adding a package under an existing group needs no edit - // here. The build graph's project references (tsconfig.build.json) stay - // explicit — TS project references have no wildcard form. + // here. The aggregates' project references (tsconfig.host.json / + // tsconfig.client.json) stay explicit — TS project references have no + // wildcard form. "@deepseek-ai/dsh-host-apiproxy": ["./packages/host/apiproxy/src"], "@deepseek-ai/dsh-host-apiproxy/client": ["./packages/host/apiproxy/src/fetch/client.ts"], "@deepseek-ai/dsh-host-apiproxy/*": ["./packages/host/apiproxy/src/*"], diff --git a/tsconfig.vitest.json b/tsconfig.vitest.json deleted file mode 100644 index 9cb8a888c6..0000000000 --- a/tsconfig.vitest.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - // Path-map scope for vite-tsconfig-paths in vitest only (never a tsc -b target): - // widens include to package src and .tsx so bare workspace imports resolve to source. - "extends": "./tsconfig.base.json", - "include": [ - "examples/*/src/**/*.ts", - "examples/*/start.ts", - "examples/*/tests/**/*.ts", - "packages/*/*/src/**/*.ts", - "packages/*/*/src/**/*.tsx", - "packages/*/*/tests/**/*.ts", - "packages/*/*/tests/**/*.tsx", - "scripts/**/*.ts" - ] -} diff --git a/vitest.config.ts b/vitest.config.ts index 408f100130..1177782a8a 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,6 +1,12 @@ import tsconfigPaths from 'vite-tsconfig-paths' import { defineConfig } from 'vitest/config' +// Resolution facade shared by every plugin instance below: tsconfig.base.json +// has no include, which vite-tsconfig-paths treats as match-all, so its paths +// map applies to every test file. paths must win over package exports so built +// lib/ never loads a second module-singleton copy. +const pathsPlugin = (): ReturnType => tsconfigPaths({ projects: ['./tsconfig.base.json'] }) + const windowsUnsupportedPackages = process.platform === 'win32' ? [ 'packages/bash/*', @@ -40,12 +46,7 @@ const processBoundTests = [ ] export default defineConfig({ - // Native path resolution reads each package's nearest tsconfig, but only the root defines - // workspace paths. Keep this plugin pinned to the root map so bare package imports resolve - // to source — with built lib/ present, manifest-exports fallthrough would load a second - // copy of module singletons. tsconfig.vitest.json widens include to .tsx specs (the root - // include stops at .ts for tsc -b; the plugin scopes applicability by include). - plugins: [tsconfigPaths({ projects: ['./tsconfig.vitest.json'] })], + plugins: [pathsPlugin()], test: { setupFiles: ['./scripts/test-invariants.ts'], // .tsx: client component specs (jsdom via per-file @vitest-environment pragma). @@ -55,7 +56,7 @@ export default defineConfig({ // for lower startup/IPC overhead; only explicit process-bound suites fork. projects: [ { - plugins: [tsconfigPaths({ projects: ['./tsconfig.vitest.json'] })], + plugins: [pathsPlugin()], test: { name: 'thread-safe', pool: 'threads', @@ -68,7 +69,7 @@ export default defineConfig({ }, }, { - plugins: [tsconfigPaths({ projects: ['./tsconfig.vitest.json'] })], + plugins: [pathsPlugin()], test: { name: 'process-bound', pool: 'forks', diff --git a/vitest.e2e.config.ts b/vitest.e2e.config.ts index 97be1395b3..2e2221b6e8 100644 --- a/vitest.e2e.config.ts +++ b/vitest.e2e.config.ts @@ -29,13 +29,13 @@ const e2eMaxWorkers = positiveIntFromEnv('DSH_E2E_MAX_WORKERS', DEFAULT_E2E_MAX_ export default defineConfig({ // Same resolution note as vitest.config.ts: bare workspace names resolve - // through the vitest-scoped tsconfig paths map (its include spans package - // src, so client-package sources get mapping too — the root tsconfig - // excludes packages/client, which would drop /client subpath imports onto - // package exports and load browser dist bundles into node). Built-artifact - // e2e suites are unaffected: their built-ness lives in subprocesses and - // createRequire lookups, which bypass vite resolution entirely. - plugins: [tsconfigPaths({ projects: ['./tsconfig.vitest.json'] })], + // through the tsconfig.base.json paths facade (no include = match-all, so + // client-package sources get mapping too — dropping /client subpath imports + // onto package exports would load browser dist bundles into node). + // Built-artifact e2e suites are unaffected: their built-ness lives in + // subprocesses and createRequire lookups, which bypass vite resolution + // entirely. + plugins: [tsconfigPaths({ projects: ['./tsconfig.base.json'] })], test: { setupFiles: ['./scripts/test-invariants.ts'], include: ['packages/*/*/tests/**/*.e2e.ts', 'examples/*/tests/**/*.e2e.ts'], diff --git a/vitest.snapshot.config.ts b/vitest.snapshot.config.ts index 142528e604..24764cf6c7 100644 --- a/vitest.snapshot.config.ts +++ b/vitest.snapshot.config.ts @@ -36,8 +36,9 @@ if (process.env.DSH_SNAPSHOT === 'record') { export default defineConfig({ // Same resolution note as vitest.config.ts: bare workspace names resolve - // through the root tsconfig paths map; the native option cannot do this. - plugins: [tsconfigPaths({ projects: ['./tsconfig.json'] })], + // through the tsconfig.base.json paths facade; the native option cannot do + // this (the root tsconfig is a solution file with no paths). + plugins: [tsconfigPaths({ projects: ['./tsconfig.base.json'] })], test: { setupFiles: ['./scripts/test-invariants.ts'], include: [ diff --git a/vitest.web.config.ts b/vitest.web.config.ts index 765fb2e4a9..de220c9f12 100644 --- a/vitest.web.config.ts +++ b/vitest.web.config.ts @@ -1,4 +1,4 @@ -import { fileURLToPath } from 'node:url' +import tsconfigPaths from 'vite-tsconfig-paths' import { defineConfig } from 'vitest/config' // Web smoke lane (GUI, gate-exempt — not part of the CI sequence yet): built @@ -13,13 +13,10 @@ try { } export default defineConfig({ - resolve: { - alias: { - // apps/web/tests is outside the root tsconfig include, so the shared - // tsconfig-paths plugin never maps it; alias the one bare import to source. - '@deepseek-ai/dsh-host-webserver': fileURLToPath(new URL('packages/host/webserver/src/index.ts', import.meta.url)), - }, - }, + // Same resolution note as vitest.config.ts: the tsconfig.base.json paths + // facade has no include (match-all), so apps/web/tests resolves bare + // workspace imports to source like every other lane. + plugins: [tsconfigPaths({ projects: ['./tsconfig.base.json'] })], test: { include: ['apps/web/tests/**/*.e2e.ts'], // Browser boot + real-model turns are slow; files share one browser, run serial. From 62125c0dcb2212b6ebd98ff6fb84e6fc8e4896d9 Mon Sep 17 00:00:00 2001 From: imccyu <276526105+imccyu@users.noreply.github.com> Date: Thu, 23 Jul 2026 00:06:35 +0800 Subject: [PATCH 03/11] docs(ts): document the solution-root TypeScript project layout New authoritative section docs/development.md#typescript-project-layout (five files, three roles, the program-vs-resolution principle and its two disciplines), two repo conventions in AGENTS.md, package tsconfig shape in packages/AGENTS.md, cookbook touch-lists updated for the two aggregates, test source-plane rule in docs/testing.md. Decision record: new Agent Note 2026-07-22-tsconfig-solution-root-two-aggregates; ts-build-config note updated in place (tsc-first pipeline unchanged); the two GUI RFCs now name tsconfig.host.json. All bilingual pairs re-recorded. Doc budget ceilings raised: AGENTS.md 1680, docs/testing.md 1020, packages/AGENTS.md 660 (two new one-line conventions and one new section on already-near-ceiling docs). Mission spec for the code-side migration: missions/tsconfig-single-graph-migration.md. --- ...19-gui-layering-and-rpc-protocol.i18n.yaml | 4 +- ...026-07-19-gui-layering-and-rpc-protocol.md | 2 +- ...-07-19-gui-layering-and-rpc-protocol.zh.md | 2 +- ...7-19-gui-web-client-architecture.i18n.yaml | 4 +- .../2026-07-19-gui-web-client-architecture.md | 2 +- ...26-07-19-gui-web-client-architecture.zh.md | 2 +- .../process/2026-06-17-ts-build-config.md | 22 +++++----- ...2-tsconfig-solution-root-two-aggregates.md | 43 +++++++++++++++++++ AGENTS.md | 2 + docs/cookbook/adding-a-package.i18n.yaml | 4 +- docs/cookbook/adding-a-package.md | 5 ++- docs/cookbook/adding-a-package.zh.md | 5 ++- .../adding-a-vendored-package.i18n.yaml | 4 +- docs/cookbook/adding-a-vendored-package.md | 5 +-- docs/cookbook/adding-a-vendored-package.zh.md | 5 +-- docs/development.i18n.yaml | 4 +- docs/development.md | 25 +++++++++-- docs/development.zh.md | 25 +++++++++-- docs/testing.md | 4 ++ packages/AGENTS.md | 1 + scripts/doc-budgets.manifest.json | 6 +-- 21 files changed, 133 insertions(+), 43 deletions(-) create mode 100644 .agents/notes/implemented/process/2026-07-22-tsconfig-solution-root-two-aggregates.md diff --git a/.agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.i18n.yaml index d3e5608c22..c2f039b148 100644 --- a/.agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write -2026-07-19-gui-layering-and-rpc-protocol.md: ebe21a6060ec69ba9807ab9fbf9906ae24b07823 -2026-07-19-gui-layering-and-rpc-protocol.zh.md: 0c256b60ce44a8e16ec6edfba146c776c4ae2129 +2026-07-19-gui-layering-and-rpc-protocol.md: 65fb01f44698c61e6bf6958e332e1854fbb77fa9 +2026-07-19-gui-layering-and-rpc-protocol.zh.md: e8b15789846ea124fb6a90f2afef437184d4348a diff --git a/.agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.md b/.agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.md index ebe21a6060..65fb01f446 100644 --- a/.agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.md +++ b/.agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.md @@ -53,7 +53,7 @@ Direction discipline (every rule auditable from package deps): - `webserver` does not depend on `runtime`: it provides a `{ fetch }`-shaped implementation — "webserver ← runtime" is a runtime injection relationship, not a package dependency. - Cross-package client imports use the `/client` subpath for plugin packages (a bare package name would inline a second runtime instance into a browser bundle; the tsdown purity gate rewrites or rejects it). -TypeScript checks in **two aggregate programs** (`tsconfig.json` = host side + tests, excluding `packages/client`; `tsconfig.client.json` = client packages and their tests): both sides merge the cordis `Context` interface under the same keys (`sessions`, `loader`) with different services, so one program would see both declaration merges and report a collision. Shared leaves (session/llm/tools/apiproxy…) build once and are referenced by both programs. +TypeScript checks in **two aggregate programs** referenced by a solution root (`tsconfig.json` = solution; `tsconfig.host.json` = host side + tests, excluding `packages/client`; `tsconfig.client.json` = client packages and their tests): both sides merge the cordis `Context` interface under the same keys (`sessions`, `loader`) with different services, so one program would see both declaration merges and report a collision. Shared leaves (session/llm/tools/apiproxy…) build once and are referenced by both programs ([topology](../process/2026-07-22-tsconfig-solution-root-two-aggregates.md)). On the protocol side: TS interfaces (`packages/host/apiproxy/src/api/`, zero Node dependencies, browser-importable); wire messages unify under a **bidirectional model** — each logical message is shaped by "who initiates × request/response" (two axes, four cells, called the four quadrants below), decoupled from the physical channel; clients all inherit `AbstractApiClient` (protocol invariants live entirely in the base class, platform differences are just the `doFetch` transport aspect). diff --git a/.agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.zh.md b/.agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.zh.md index 0c256b60ce..e8b1578984 100644 --- a/.agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.zh.md @@ -51,7 +51,7 @@ harness core packages ──────────────────┘ - `webserver` 不依赖 `runtime`:它提供 `{ fetch }` 特定实现 ——「webserver ← runtime」只是运行时注入关系,不是包依赖。 - client 侧跨包 import 插件包一律走 `/client` 子路径(裸包名会把第二份运行时实例内联进浏览器 bundle;tsdown 纯度门禁会改写或拒收)。 -TypeScript 以**两个聚合 program** 检查(`tsconfig.json` = host 侧 + 测试,排除 `packages/client`;`tsconfig.client.json` = client 各包及其测试):两侧在相同键(`sessions`、`loader`)下以不同服务合并 cordis `Context` 接口,单一 program 会同时看到两份声明合并而报冲突。共享叶子包(session/llm/tools/apiproxy 等)只构建一次,由两个 program 共同引用。 +TypeScript 以 solution 根引用的**两个聚合 program** 检查(`tsconfig.json` = solution;`tsconfig.host.json` = host 侧 + 测试,排除 `packages/client`;`tsconfig.client.json` = client 各包及其测试):两侧在相同键(`sessions`、`loader`)下以不同服务合并 cordis `Context` 接口,单一 program 会同时看到两份声明合并而报冲突。共享叶子包(session/llm/tools/apiproxy 等)只构建一次,由两个 program 共同引用([拓扑](../process/2026-07-22-tsconfig-solution-root-two-aggregates.md))。 协议侧:TS interface(`packages/host/apiproxy/src/api/`,零 Node 依赖,浏览器可 import);wire 消息统一为**双向模型**——每条逻辑消息由「谁发起 × request/response」定形(两轴四格,后文称四象限),与物理通道解耦;客户端统一继承 `AbstractApiClient`(协议不变量全在基类,平台差异只是 `doFetch` 传输切面)。 diff --git a/.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.i18n.yaml index b3b5462ecf..85970cbb0e 100644 --- a/.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write -2026-07-19-gui-web-client-architecture.md: f704a00c3b38f5a3a62d66cb37b121f0f21770df -2026-07-19-gui-web-client-architecture.zh.md: 3aa8fdbe82102d8c920a9f77635320c432d8b95d +2026-07-19-gui-web-client-architecture.md: 5a6ef46d6f6c19dcc8cc98f7c2159ea08d20f012 +2026-07-19-gui-web-client-architecture.zh.md: 005d1231ac874376291e884e88ae687df28fc912 diff --git a/.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.md b/.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.md index f704a00c3b..f21b840493 100644 --- a/.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.md +++ b/.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.md @@ -39,7 +39,7 @@ The loading chain, end to end: **The dual-instance ban**: a module-table package inlined into a plugin bundle would duplicate runtime identity (two React copies, two store registries — the root cause of an actual white-screen P0). The tsdown client preset enforces purity at build time: a bare-name import of a module-table package must resolve external (rewritten to its `/client` form where applicable), and any other workspace leak that is not an inline-safe wire/type layer fails the build (`packages/client/tsdown.client.ts`, pinned by `scripts/client-bundle-purity.spec.ts`). -Dev equals prod: plugins rebuild under `tsdown --watch`, refresh reloads the same chain; vite serves only the shell (`apps/web`). Type universes stay split at the aggregate level — the root `tsconfig.json` is the host program, `tsconfig.client.json` the client program, because both sides merge cordis `Context` under the same keys (`sessions`, `loader`) with different services; client packages consume the wire vocabulary through pure type subpaths (`@deepseek-ai/dsh-session/types` and kin) so no host augmentation rides into the client program. +Dev equals prod: plugins rebuild under `tsdown --watch`, refresh reloads the same chain; vite serves only the shell (`apps/web`). Type universes stay split at the aggregate level — `tsconfig.host.json` is the host program and `tsconfig.client.json` the client program, both referenced by the solution root `tsconfig.json` — because both sides merge cordis `Context` under the same keys (`sessions`, `loader`) with different services; client packages consume the wire vocabulary through pure type subpaths (`@deepseek-ai/dsh-session/types` and kin) so no host augmentation rides into the client program. ## The slot system: how the page composes diff --git a/.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.zh.md b/.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.zh.md index 3aa8fdbe82..a50dc556cf 100644 --- a/.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.zh.md @@ -39,7 +39,7 @@ Status: implemented **双实例禁令**:模块表包若被内联进插件 bundle,会复制运行时身份(两份 React、两套 store 注册表——一次真实白屏 P0 的根因)。tsdown client 预设在构建期把守纯度:模块表包的裸名 import 必须解析为 external(适用时改写为其 `/client` 形态),其余任何非 inline 安全 wire/类型层的 workspace 泄漏都令构建大声失败(`packages/client/tsdown.client.ts`,由 `scripts/client-bundle-purity.spec.ts` 钉住)。 -dev 与 prod 同链:插件在 `tsdown --watch` 下重编译,刷新即重走同一条链;vite 只管壳(`apps/web`)。类型宇宙在聚合层拆分——根 `tsconfig.json` 是 host program,`tsconfig.client.json` 是 client program,因为两侧都在相同键(`sessions`、`loader`)上对 cordis `Context` 做声明合并且服务不同;client 包经纯类型子路径(`@deepseek-ai/dsh-session/types` 等)消费协议词汇,host 侧的声明合并不会搭车进入 client program。 +dev 与 prod 同链:插件在 `tsdown --watch` 下重编译,刷新即重走同一条链;vite 只管壳(`apps/web`)。类型宇宙在聚合层拆分——`tsconfig.host.json` 是 host program、`tsconfig.client.json` 是 client program,二者由 solution 根 `tsconfig.json` 引用,因为两侧都在相同键(`sessions`、`loader`)上对 cordis `Context` 做声明合并且服务不同;client 包经纯类型子路径(`@deepseek-ai/dsh-session/types` 等)消费协议词汇,host 侧的声明合并不会搭车进入 client program。 ## slot 体系:页面怎么拼 diff --git a/.agents/notes/implemented/process/2026-06-17-ts-build-config.md b/.agents/notes/implemented/process/2026-06-17-ts-build-config.md index 8f67b6fc2f..529400b8d7 100644 --- a/.agents/notes/implemented/process/2026-06-17-ts-build-config.md +++ b/.agents/notes/implemented/process/2026-06-17-ts-build-config.md @@ -1,7 +1,9 @@ -# Agent Note: TSC-first build and one tsconfig +# Agent Note: TSC-first build and one compiler ownership Status: implemented +> Root project topology (which tsconfig owns which graph) has since moved to a solution root over two aggregate programs; see the [solution-root note](2026-07-22-tsconfig-solution-root-two-aggregates.md). The tsc-first pipeline decided here is unchanged. + ## Problem The current TypeScript build and typecheck setup had these issues: @@ -28,29 +30,29 @@ In-package relative imports use explicit `.ts` specifiers. `pnpm run build` is a two-stage build: -- Stage 1: `tsc -b tsconfig.build.json` emits per-module `.js`, declarations `.d.ts`, JS sourcemaps `.js.map`, and declaration sourcemaps `.d.ts.map` into each package's `lib/types`. This is the authoritative TypeScript compilation result. For publish we keep `.d.ts` / `.d.ts.map` and ignore `.js` / `.js.map`. - - The build project uses the project-reference graph that `tsc -b` compiles. For example, root `tsconfig.build.json` references package and vendor tsconfigs. It validates and emits package/vendor build results. +- Stage 1: `tsc -b` over the root solution emits per-module `.js`, declarations `.d.ts`, JS sourcemaps `.js.map`, and declaration sourcemaps `.d.ts.map` into each package's `lib/types`. This is the authoritative TypeScript compilation result. For publish we keep `.d.ts` / `.d.ts.map` and ignore `.js` / `.js.map`. + - The graph is the project-reference graph reachable from the root solution `tsconfig.json` through the two aggregates ([topology](2026-07-22-tsconfig-solution-root-two-aggregates.md)). It validates and emits package/vendor build results. - Stage 2: a bundler reads the emitted JS under `lib/types` and writes the bundled runtime entry as `lib/index.js` or `lib/index.mjs` (follow current behavior). This stage is bundling only. It must not read TypeScript source or emit declarations. `tsdown` is no longer the owner of TypeScript compilation or declaration output. -`pnpm run typecheck` runs build mode over the root `tsconfig.json`. -- The root `tsconfig.json` is the single development/typecheck project. It typechecks examples, tests, and scripts with `noEmit`, and validates package/vendor source through references. -- Referenced package/vendor projects keep the same emit behavior as build, so typecheck can refresh their `lib/types` outputs instead of using a separate no-emit graph. Project-specific strictness changes live in the owning `packages/*/*/tsconfig.json` or `vendor/*/tsconfig.json`. -- The root no-emit project disables `rewriteRelativeImportExtensions`; it emits nothing and includes tests that import helpers across project-reference boundaries. Package/vendor emit projects keep the rewrite enabled. +`pnpm run typecheck` runs the same `tsc -b` graph. +- The aggregates (`tsconfig.host.json`, `tsconfig.client.json`) typecheck examples, tests, and scripts with `noEmit`, and validate package/vendor source through references. +- Referenced package/vendor projects keep the same emit behavior as build, so typecheck refreshes their `lib/types` outputs instead of using a separate no-emit graph. Project-specific strictness changes live in the owning `packages/*/*/tsconfig.json` or `vendor/*/tsconfig.json`. +- The no-emit aggregates disable `rewriteRelativeImportExtensions`; they emit nothing and include tests that import helpers across project-reference boundaries. Package/vendor emit projects keep the rewrite enabled. The command orchestration shape is: ```sh pnpm run build: -tsc -b tsconfig.build.json +tsc -b tsdown pnpm run verify-node-next-types: tsx scripts/verify-node-next-types.ts pnpm run typecheck: -tsc -b tsconfig.json +tsc -b ``` `pnpm run demo:*` still runs `src` directly through tsx and root paths, without a compile step. @@ -65,7 +67,7 @@ tsc -b tsconfig.json Build responsibilities are clearer: - Each module under `packages//` and `vendor/*` has one local tsconfig for build, typecheck, and tools that run source directly, such as `tsx` and `vitest`. -- The `build` command uses `tsconfig.build.json`. `tsc -b` owns the publishable per-module `.js` and `.d.ts` output, and the bundler owns only `lib/index.*`. +- The `build` command drives the root solution graph. `tsc -b` owns the publishable per-module `.js` and `.d.ts` output, and the bundler owns only `lib/index.*`. - `lib/types/*.d.ts` and `.d.ts.map` are the publish declaration output. - `lib/types/*.d.ts` uses explicit `.ts` relative specifiers, which TypeScript's NodeNext/Node16 resolver maps to sibling `.d.ts` files. - `lib/types/*.js` is only a bundler input and must not be used as a runtime entry or public import target. diff --git a/.agents/notes/implemented/process/2026-07-22-tsconfig-solution-root-two-aggregates.md b/.agents/notes/implemented/process/2026-07-22-tsconfig-solution-root-two-aggregates.md new file mode 100644 index 0000000000..3ad397a7dc --- /dev/null +++ b/.agents/notes/implemented/process/2026-07-22-tsconfig-solution-root-two-aggregates.md @@ -0,0 +1,43 @@ +# Agent Note: Solution root over two aggregate programs + +Status: implemented + +## Problem + +The GUI split introduced a second aggregate program (`tsconfig.client.json`, [layering RFC](../architecture/2026-07-19-gui-layering-and-rpc-protocol.md)) while the root `tsconfig.json` kept doubling as the host aggregate, and `tsconfig.build.json` remained a third, hand-maintained full emit graph. That triple bookkeeping produced four concrete asymmetries: + +- The typecheck and build references lists drifted apart (`packages/goal/command-goal` was in the typecheck graph but missing from the build graph). +- The lefthook pre-push hook ran `tsc -b tsconfig.json` only, so client-side type breakage passed the local checkpoint and surfaced in CI. +- tsserver discovers only configs named `tsconfig.json`, so client test files sat on no discoverable config chain and fell back to inferred projects (no paths, wrong lib/jsx). +- The vitest configs pointed at three different resolution sources (`tsconfig.vitest.json`, the root config, and one hand-written alias). + +## Decision + +One solution root, two check units, one shared base pair, no separate build or vitest config: + +| File | Role | Forms a program? | +|---|---|---| +| `tsconfig.json` | Solution root: `extends` base, `files: []`, two references; the whole-repo `tsc -b tsconfig.json` graph, the tsserver entry, and the nearest config for get-tsconfig consumers (tsx running `examples/`, `scripts/`, doc fences) whose bare workspace imports resolve through the inherited `paths` | No | +| `tsconfig.base.json` | Shared compilerOptions and the source `paths` map; doubles as the resolution facade for vite-tsconfig-paths (no `include`, so it applies to every importer) | No | +| `tsconfig.base.client.json` | Browser compiler shape (`jsx: react-jsx`, DOM libs, `types: []`) shared by the client aggregate and every `packages/client/*` package | No | +| `tsconfig.host.json` | The former root aggregate, moved verbatim: host packages, examples, tests, scripts, website; excludes `packages/client` | Yes | +| `tsconfig.client.json` | Client packages and their tests; extends `tsconfig.base.client.json` | Yes | + +The load-bearing principle: **cordis `Context` declaration-merge collisions exist only inside a `ts.Program`, never in module resolution.** A solution file forms no program, so referencing both aggregates from one root cannot collide the merges; vite-tsconfig-paths reads only `paths` and `include` and discards types, so one facade may span both sides. The only way to explode is to flatten both sides into a single program — hence two derived disciplines: `tsconfig.base.json` never gains `include`/`files` (it would leak into every extending package and narrow the facade), and every repo-wide `ts.Program` consumer (`scripts/ts-project.ts`, doc-typecheck standalone mode) seeds `tsconfig.host.json` or `tsconfig.client.json` explicitly, never the root solution. Program-backed generators and semantic gates intentionally stay host-only; the client side gets program-backed gates only when a real need arrives. + +Commands collapse to one graph and keep the config name explicit: `typecheck` = `tsc -b tsconfig.json`, `build` = `tsc -b tsconfig.json && tsdown`, lefthook pre-push stays `tsc -b tsconfig.json --pretty false` unchanged (the same line now covers both sides through the solution). `tsconfig.build.json` and `tsconfig.vitest.json` are deleted; all vitest configs point vite-tsconfig-paths at `tsconfig.base.json`. + +The solution root `extends` the base deliberately: `examples/` and `scripts/` have no nearer tsconfig, so tsx (get-tsconfig) resolves their workspace imports through the root file. `extends` restores the `paths` map there while `files: []` keeps the file program-less. Their *type checking* is unaffected by this: examples, scripts, and website files are included by the host aggregate. + +## Alternatives considered + +- **Rename `tsconfig.build.json` to `tsconfig.host.json`** — rejected: the build graph was the full emit graph including all client packages, not a host graph; the name that fits the former root aggregate is `tsconfig.host.json`, and the build graph itself is subsumed by the solution. +- **Point vitest at the root solution** — rejected: a solution has neither `paths` nor `include`, so resolution would become a function of how far the plugin walks references, and the client aggregate's include (tests only, no src) would leave transitive src→src imports unmapped, falling through to `exports` and loading a second copy of module singletons. +- **Keep `tsconfig.vitest.json` as a dedicated facade** — retained only as the fallback if vite-tsconfig-paths mishandles an include-less config; the base file already carries the paths map, and an include-less config applies everywhere, which is strictly wider than the facade's hand-kept include list. + +## Consequences + +- `docs/development.md#typescript-project-layout` is the authoritative description; root `AGENTS.md` carries the two disciplines as conventions. +- The [ts-build-config note](2026-06-17-ts-build-config.md) keeps ownership of the tsc-first build pipeline (tsc emits, tsdown bundles, `.ts` specifiers with `rewriteRelativeImportExtensions`); its former "one root typecheck project" shape is superseded by this note. +- Adding a package registers it in exactly one aggregate's references (host packages in `tsconfig.host.json`, client packages in `tsconfig.client.json`); the build graph needs no separate registration. +- The build gate depends on the typecheck gate: both now drive the same `tsc -b` graph, so running them concurrently would race the same `.tsbuildinfo` files. diff --git a/AGENTS.md b/AGENTS.md index 8dcdc059f4..8007f32b90 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -100,6 +100,8 @@ Real-API tests and demos read `DEEPSEEK_API_KEY`, optional `DEEPSEEK_BASE_URL`, - **Misconfiguration fails loud** at load when self-contained, otherwise at the earliest resolvable point; never silently skip a missing referent. - **Opaque cross-boundary ids are branded** (`Branded` from `dsh-brand`), never bare `string`. - **Trust TypeScript at typed same-process seams.** Do not add runtime validation, fallback behavior, or hostile-input tests solely for values the static interface requires; validate at parser/config, queued, model/tool JSON, durable/file, worker, process, and wire boundaries. +- **Source plane vs artifact plane, never mixed.** Static gates and tests resolve workspace imports through tsconfig `paths` to `src` and pass on a clean tree; gates consuming built `lib/` declare that dependency ([layout](docs/development.md#typescript-project-layout)). +- **`ts.Program` consumers seed `tsconfig.host.json` or `tsconfig.client.json`, never the root solution** — one program holding both sides collides the cordis `Context` merges ([layout](docs/development.md#typescript-project-layout)). - **An empty `catch` names what it swallows** and why nothing else can reach it; keep the `try` to one statement. - **Prefer symmetry for parallel values**; unexplained asymmetry usually signals a missed extraction. - **Tests describe behavior, not correctness.** Change obsolete behavior with its tests; explain why in the PR. diff --git a/docs/cookbook/adding-a-package.i18n.yaml b/docs/cookbook/adding-a-package.i18n.yaml index 6fd3feebbd..c71c37cb00 100644 --- a/docs/cookbook/adding-a-package.i18n.yaml +++ b/docs/cookbook/adding-a-package.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write -adding-a-package.md: 556a48493af4452c178634c0abb4e23e2419dd8e -adding-a-package.zh.md: 5f7e4692233448c746d25e4808c078c390cf39e6 +adding-a-package.md: 1859310965538b35a353ee05c94b01d1093a3e43 +adding-a-package.zh.md: 7a820c2c8892d429f98a4c8425da4a914e028177 diff --git a/docs/cookbook/adding-a-package.md b/docs/cookbook/adding-a-package.md index 556a48493a..1859310965 100644 --- a/docs/cookbook/adding-a-package.md +++ b/docs/cookbook/adding-a-package.md @@ -32,10 +32,11 @@ In-package relative imports use explicit `.ts` specifiers in source (for example | File | Change | |---|---| | `tsconfig.base.json` | no edit for an existing group; for a new group, add a `./packages//*/src` candidate to the `@deepseek-ai/dsh-*` wildcard | -| `tsconfig.json` | add `{ "path": "./packages//" }` to `references` | -| `tsconfig.build.json` | add `{ "path": "./packages//" }` to `references` | +| `tsconfig.host.json` (host-side package) or `tsconfig.client.json` (client-side package) | add `{ "path": "./packages//" }` to `references` — exactly one aggregate, never both ([layout](../development.md#typescript-project-layout)) | | `knip.json` | only if the package has non-`*.spec.ts` entries (e.g. `*.e2e.ts` → add a per-workspace override like `packages/llm/llm-deepseek`) | +A `packages/client/*` package additionally extends `tsconfig.base.client.json` instead of `tsconfig.base.json`, and a client plugin package declares `dshClient` in package.json, exports `./client`, and calls the shared tsdown preset (`packages/client/tsdown.client.ts`) — see [packages/client/AGENTS.md](../../packages/client/AGENTS.md) for the client-side contract. + Covered automatically by globs or package-manifest discovery — no edits needed: root `package.json` workspaces, `scripts/publint-all.ts`, `tsdown.config.ts`, `vitest.config.ts`, `eslint.config.mjs`, `scripts/check-workspace-constraints.ts`. ## 3. Decide the package topology diff --git a/docs/cookbook/adding-a-package.zh.md b/docs/cookbook/adding-a-package.zh.md index 5f7e469223..7a820c2c88 100644 --- a/docs/cookbook/adding-a-package.zh.md +++ b/docs/cookbook/adding-a-package.zh.md @@ -32,10 +32,11 @@ package.json 不变式(由 `pnpm run constraints` / `scripts/check-workspace-c | 文件 | 变更 | |---|---| | `tsconfig.base.json` | 已有分组无需编辑;新分组需为 `@deepseek-ai/dsh-*` 通配符添加 `./packages//*/src` 候选路径 | -| `tsconfig.json` | 在 `references` 中添加 `{ "path": "./packages//" }` | -| `tsconfig.build.json` | 在 `references` 中添加 `{ "path": "./packages//" }` | +| `tsconfig.host.json`(host 侧包)或 `tsconfig.client.json`(client 侧包) | 在 `references` 中添加 `{ "path": "./packages//" }`——恰好一个聚合,绝不两个都加([布局](../development.zh.md#typescript-项目布局)) | | `knip.json` | 仅当包有非 `*.spec.ts` 入口时需要(如 `*.e2e.ts` → 添加 per-workspace override,参照 `packages/llm/llm-deepseek`) | +`packages/client/*` 包改为 extends `tsconfig.base.client.json`(而非 `tsconfig.base.json`);client 插件包还需在 package.json 声明 `dshClient`、导出 `./client`、调用共享 tsdown preset(`packages/client/tsdown.client.ts`)——client 侧契约见 [packages/client/AGENTS.md](../../packages/client/AGENTS.md)。 + 以下内容由 glob 或包 manifest 发现机制自动覆盖,无需手动编辑:根 `package.json` workspaces、`scripts/publint-all.ts`、`tsdown.config.ts`、`vitest.config.ts`、`eslint.config.mjs`、`scripts/check-workspace-constraints.ts`。 ## 3. 确定包拓扑 diff --git a/docs/cookbook/adding-a-vendored-package.i18n.yaml b/docs/cookbook/adding-a-vendored-package.i18n.yaml index b3ca7ca791..3c6c842176 100644 --- a/docs/cookbook/adding-a-vendored-package.i18n.yaml +++ b/docs/cookbook/adding-a-vendored-package.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write -adding-a-vendored-package.md: 1b82f2e582ca5cd040a7f3237505848dbb304fae -adding-a-vendored-package.zh.md: 7245682ef8b7d85ace2c626f8d47aa36f739506b +adding-a-vendored-package.md: 71ca9fccc9418348784dbb6668127242e4fb45d2 +adding-a-vendored-package.zh.md: b811aa5632acfeb059767efcb0532e03ee1becf4 diff --git a/docs/cookbook/adding-a-vendored-package.md b/docs/cookbook/adding-a-vendored-package.md index 1b82f2e582..71ca9fccc9 100644 --- a/docs/cookbook/adding-a-vendored-package.md +++ b/docs/cookbook/adding-a-vendored-package.md @@ -38,8 +38,7 @@ Local relative imports/exports in vendored TypeScript source use explicit `.ts` | File | Change | |---|---| | `tsconfig.base.json` | add `"": ["./vendor//src"]` to `paths` | -| `tsconfig.json` | add `{ "path": "./vendor/" }` to `references` | -| `tsconfig.build.json` | add `{ "path": "./vendor/" }` to `references` (before the `packages/*` entries) | +| `tsconfig.host.json` | add `{ "path": "./vendor/" }` to `references` (before the `packages/*` entries; vendored code enters the graph through the host aggregate only) | | `vendor/README.md` | add a manifest table row (dir, npm name, version, upstream repo, commit SHA) and log any local modifications | | `scripts/publint-all.ts` | only if the vendored package is itself published from here (vendored deps normally are not — skip) | @@ -57,4 +56,4 @@ pnpm run typecheck pnpm run build && pnpm run test && pnpm run constraints ``` -The source `paths` map is shared by build and root typecheck configs. The important isolation boundary is the project-reference graph: vendored source must be referenced through its own `vendor//tsconfig.json`, not pulled into a root strict program. +The source `paths` map lives once in `tsconfig.base.json` and serves every graph. The important isolation boundary is the project-reference graph: vendored source must be referenced through its own `vendor//tsconfig.json`, not pulled into an aggregate's strict program ([layout](../development.md#typescript-project-layout)). diff --git a/docs/cookbook/adding-a-vendored-package.zh.md b/docs/cookbook/adding-a-vendored-package.zh.md index 7245682ef8..b811aa5632 100644 --- a/docs/cookbook/adding-a-vendored-package.zh.md +++ b/docs/cookbook/adding-a-vendored-package.zh.md @@ -38,8 +38,7 @@ vendored TypeScript 源码中的本地相对导入/导出在复制后使用显 | 文件 | 修改内容 | |---|---| | `tsconfig.base.json` | 在 `paths` 中添加 `"": ["./vendor//src"]` | -| `tsconfig.json` | 在 `references` 中添加 `{ "path": "./vendor/" }` | -| `tsconfig.build.json` | 在 `references` 中添加 `{ "path": "./vendor/" }`(置于 `packages/*` 条目之前) | +| `tsconfig.host.json` | 在 `references` 中添加 `{ "path": "./vendor/" }`(置于 `packages/*` 条目之前;vendored 代码只经 host 聚合进图) | | `vendor/README.md` | 添加一行 manifest 表格行(dir、npm name、version、upstream repo、commit SHA)并记录所有本地修改 | | `scripts/publint-all.ts` | 仅当该 vendored 包本身从此仓库发布时才需要(vendored 依赖通常不发布——跳过) | @@ -57,4 +56,4 @@ pnpm run typecheck pnpm run build && pnpm run test && pnpm run constraints ``` -源码 `paths` 映射由构建配置和根类型检查配置共享。重要的隔离边界是 project-reference 图:vendored 源码必须通过其自身的 `vendor//tsconfig.json` 被引用,而非被拉入根目录的严格程序中。 +源码 `paths` 映射只在 `tsconfig.base.json` 存在一份,服务所有图。重要的隔离边界是 project-reference 图:vendored 源码必须通过其自身的 `vendor//tsconfig.json` 被引用,而非被拉入某个聚合的严格程序中([布局](../development.zh.md#typescript-项目布局))。 diff --git a/docs/development.i18n.yaml b/docs/development.i18n.yaml index 4ea0bafef3..40828094ce 100644 --- a/docs/development.i18n.yaml +++ b/docs/development.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write -development.md: 3559b09d86395707f222aad0a281c9db1246c24f -development.zh.md: 8664a3291c04a5338fdadbce8f24b160cc9ec0a8 +development.md: 4294038e40aa774a006874e6641ca63eea44beeb +development.zh.md: 1f07c95dd60d0554b945c29e6e3ba8bc6ca9841a diff --git a/docs/development.md b/docs/development.md index 3559b09d86..4294038e40 100644 --- a/docs/development.md +++ b/docs/development.md @@ -33,7 +33,26 @@ Run typecheck once after a fresh clone: pnpm run typecheck ``` -That first typecheck runs the package/vendor build graph and the root no-emit `tsconfig.json` graph for examples, tests, and scripts. The root graph uses the same source `paths` map but relies on project references so vendored code is checked under its own tsconfig settings. +That first typecheck runs the whole-repo `tsc -b` graph: it emits every package/vendor `lib/types` and checks examples, tests, and scripts through the two no-emit aggregates described below. + +## TypeScript project layout + +The repository's TypeScript configuration has exactly three roles; every tsconfig file plays one of them. + +| File | Role | Forms a program? | +|---|---|---| +| `tsconfig.json` | Solution root: `extends` base, `files: []`, references to the two aggregates. The whole-repo `tsc -b tsconfig.json` graph, the tsserver discovery entry, and — through the inherited `paths` — the resolution config for tsx running `examples/` and `scripts/` (their nearest tsconfig is this file). | No | +| `tsconfig.host.json` | Host aggregate: host-side packages (via references), examples, tests, scripts, website. Excludes `packages/client`. | Yes | +| `tsconfig.client.json` | Client aggregate: `packages/client/*` packages and their tests, `apps/web`. | Yes | +| `tsconfig.base.json` | Shared compilerOptions and the source `paths` map. Also the resolution facade the vitest configs point vite-tsconfig-paths at: it has no `include`, so its `paths` apply to every importer. | No | +| `tsconfig.base.client.json` | Browser compiler shape (`jsx`, DOM libs, `types: []`) extended by the client aggregate and every `packages/client/*` package. | No | + +Host and client stay two aggregate programs because both sides declaration-merge the cordis `Context` interface under the same keys with different services; one program seeing both merges reports a collision. The collision exists only inside a `ts.Program` — module resolution never triggers it — which is why the solution may reference both aggregates and one paths facade may span both sides. Two disciplines follow: + +- `tsconfig.base.json` never gains `include` or `files`: they would leak into every extending package project and narrow the facade's match-all scope. +- A script that builds a repo-wide `ts.Program` seeds `tsconfig.host.json` or `tsconfig.client.json` explicitly — never the root solution, because flattening both aggregates into one program collides the `Context` merges. Program-backed generators and gates (`scripts/ts-project.ts` consumers, doc-typecheck standalone mode) are host-only by decision; the client side gains program-backed tooling only with a concrete need. + +Static analysis and tests resolve workspace imports through the base `paths` map to `src` and must pass on a clean tree; gates that consume built `lib/` output declare that dependency explicitly. Decision record: [solution-root note](../.agents/notes/implemented/process/2026-07-22-tsconfig-solution-root-two-aggregates.md); the tsc-first emit pipeline is the [ts-build-config note](../.agents/notes/implemented/process/2026-06-17-ts-build-config.md). If a relevant local check consumes built package output, build once first: @@ -59,7 +78,7 @@ DEEPSEEK_BASE_URL=https://... # optional lefthook is configured in `lefthook.yml` as a fast local checkpoint: - `pre-commit` runs staged-file ESLint fixes, checks the staged diff for whitespace errors, and runs the vendor manifest guard. -- `pre-push` runs only the incremental repository typecheck. +- `pre-push` runs only the incremental repository typecheck (`tsc -b` over the root solution, covering both the host and client aggregates). The vendor manifest guard checks that changes under `vendor/*/src` are staged with the matching `vendor/README.md` manifest update. See `vendor/README.md` before editing vendored code. @@ -80,7 +99,7 @@ pnpm run test # unit tests pnpm run test:coverage # unit tests with per-file coverage gates pnpm run test:e2e # real-API tests; self-skips without DEEPSEEK_API_KEY pnpm run check:all # comprehensive opt-in gate set; not wired to Git hooks -pnpm run typecheck # build package/vendor outputs, then typecheck examples, tests, and scripts +pnpm run typecheck # tsc -b over the root solution: emits package/vendor lib/types, checks both aggregates pnpm run lint # eslint . pnpm run lint:fix # eslint . --fix pnpm run doc-typecheck # compile checked TypeScript snippets in Markdown docs diff --git a/docs/development.zh.md b/docs/development.zh.md index 8664a3291c..1f07c95dd6 100644 --- a/docs/development.zh.md +++ b/docs/development.zh.md @@ -33,7 +33,26 @@ pnpm exec lefthook install --force pnpm run typecheck ``` -首次类型检查会执行 package/vendor 的构建图,以及根目录下用于示例、测试和脚本的 no-emit `tsconfig.json` 项目图。根图使用同一份源码 `paths` 映射,但依赖 project references,因此 vendor 代码在它自己的 tsconfig 设置下被检查。 +首次类型检查会执行全仓 `tsc -b tsconfig.json` 图:发射每个 package/vendor 的 `lib/types`,并通过下述两个 no-emit 聚合检查示例、测试和脚本。 + +## TypeScript 项目布局 + +仓库的 TypeScript 配置只有三种角色;每个 tsconfig 文件恰好扮演其中一种。 + +| 文件 | 角色 | 是否构成 program? | +|---|---|---| +| `tsconfig.json` | solution 根:`extends` base、`files: []`、引用两个聚合。全仓 `tsc -b tsconfig.json` 图、tsserver 发现入口,并经继承的 `paths` 充当 tsx 运行 `examples/` 与 `scripts/` 时的解析配置(它们最近的 tsconfig 就是此文件)。 | 否 | +| `tsconfig.host.json` | host 聚合:host 侧各包(经 references)、示例、测试、脚本、website。排除 `packages/client`。 | 是 | +| `tsconfig.client.json` | client 聚合:`packages/client/*` 各包及其测试、`apps/web`。 | 是 | +| `tsconfig.base.json` | 共享 compilerOptions 与源码 `paths` 映射。同时是各 vitest 配置让 vite-tsconfig-paths 指向的解析门面:它没有 `include`,因此其 `paths` 适用于任何 importer。 | 否 | +| `tsconfig.base.client.json` | 浏览器编译形状(`jsx`、DOM lib、`types: []`),由 client 聚合和每个 `packages/client/*` 包 extends。 | 否 | + +host 与 client 保持两个聚合 program,是因为两侧在相同键下以不同服务对 cordis `Context` 接口做声明合并;单一 program 同时看到两份合并会报冲突。这种冲突只存在于 `ts.Program` 内部——模块解析永远不会触发它——所以 solution 可以同时引用两个聚合,一个 paths 门面也可以横跨两侧。由此推出两条纪律: + +- `tsconfig.base.json` 永不添加 `include` 或 `files`:它们会泄漏进每个 extends 它的包项目,并收窄门面的全匹配范围。 +- 构造全仓 `ts.Program` 的脚本显式种子 `tsconfig.host.json` 或 `tsconfig.client.json`——永不种子根 solution,因为把两个聚合展平进一个 program 会撞上 `Context` 合并冲突。基于 program 的生成器与门禁(`scripts/ts-project.ts` 的消费者、doc-typecheck standalone 模式)按决策仅覆盖 host 侧;client 侧只在出现真实需求时再获得基于 program 的工具。 + +静态分析和测试通过 base 的 `paths` 映射把工作区 import 解析到 `src`,且必须在干净树上通过;消费构建产物 `lib/` 的门禁显式声明该依赖。决策记录:[solution-root note](../.agents/notes/implemented/process/2026-07-22-tsconfig-solution-root-two-aggregates.md);tsc-first 发射管线见 [ts-build-config note](../.agents/notes/implemented/process/2026-06-17-ts-build-config.md)。 如果相关的本地检查需要使用构建后的包产物,请先构建一次: @@ -59,7 +78,7 @@ DEEPSEEK_BASE_URL=https://... # optional lefthook 在 `lefthook.yml` 中配置,作为快速的本地检查点: - `pre-commit` 运行对暂存文件的 ESLint 修复,检查暂存 diff 中的空白错误,并运行 vendor manifest(元数据清单)守卫; -- `pre-push` 只运行仓库增量类型检查。 +- `pre-push` 只运行仓库增量类型检查(对根 solution 执行 `tsc -b`,覆盖 host 与 client 两个聚合)。 vendor manifest 守卫检查 `vendor/*/src` 下的改动是否连同对应的 `vendor/README.md` manifest 更新一起暂存。请在编辑 vendor 代码前先阅读 `vendor/README.md`。 @@ -80,7 +99,7 @@ pnpm run test # unit tests pnpm run test:coverage # unit tests with per-file coverage gates pnpm run test:e2e # real-API tests; self-skips without DEEPSEEK_API_KEY pnpm run check:all # comprehensive opt-in gate set; not wired to Git hooks -pnpm run typecheck # build package/vendor outputs, then typecheck examples, tests, and scripts +pnpm run typecheck # tsc -b over the root solution: emits package/vendor lib/types, checks both aggregates pnpm run lint # eslint . pnpm run lint:fix # eslint . --fix pnpm run doc-typecheck # compile checked TypeScript snippets in Markdown docs diff --git a/docs/testing.md b/docs/testing.md index 85601ab6c9..85798cee3e 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -29,6 +29,10 @@ An e2e assertion re-runs the command or re-reads the file externally; a keyword - A guard only guards if the regression actually fails it. For a plugin without `inject` (bundle/composition plugins), a Loader smoke stays green under a broken export shape — add an explicit `expect('default' in mod).toBe(false)` plus an `unwrapExports` round-trip assertion, and prove it: introduce the regression, watch red, revert. - "Real entry path" means the published artifact: a package `bin` runs built `lib/bin.js` under plain `node`, exposing failures tsx masks (settle races, module resolution, swallowed load failures). The same applies to non-index runtime entries (the worker-thread sibling `lib/worker.cjs`) and singleton modules shared across bundles (`packages/ui/jsonrpc/tests/built-scope-carrier.e2e.ts`). Keep the built-artifact smokes green (`packages/ui/*/tests/built-bin.e2e.ts`, `packages/code-runtime/code-runtime-worker/tests/built-lib.e2e.ts`), and assert a genuinely-missing config exits non-zero. +## Test resolution: source plane only + +- Every vitest config points vite-tsconfig-paths at `tsconfig.base.json`; bare workspace imports resolve to `src` ([layout](development.md#typescript-project-layout)), never through package `exports` to built `lib/` — stale artifacts there load a second copy of module singletons. Built artifacts are consumed only explicitly: `lib`-mode subprocesses and the built smokes below. + ## Test subprocess launch modes - CI and build-having test lanes run every example or Cordis-config subprocess from built `lib/` through the shared dual-mode launcher. Do not hand-write `--import tsx` for these subprocesses. diff --git a/packages/AGENTS.md b/packages/AGENTS.md index 4fc767173f..bb7fdfa839 100644 --- a/packages/AGENTS.md +++ b/packages/AGENTS.md @@ -19,6 +19,7 @@ These package-specific rules supplement the repo-wide [conventions](../AGENTS.md Naming notes: +- **Package tsconfig shape:** extends `tsconfig.base.json` (client: `tsconfig.base.client.json`), `rootDir: src`, `outDir: lib/types`, a `references` entry per workspace dependency plus `support/invariants`; registered in exactly one aggregate — host packages in `tsconfig.host.json`, client in `tsconfig.client.json` ([layout](../docs/development.md#typescript-project-layout)). - `src/types.ts` contains only types — no runtime code. - Tests live at package level under `tests/`, not `src/__tests__/`. - A package's README and JSDoc are part of the change: altered behavior (config keys, defaults, error codes, wire fields) updates them in the same commit. `doc-sync` gates what it can; apply [dsh-prose-standard](../.agents/skills/dsh-prose-standard/SKILL.md) for complete, concise prose and verify accuracy against code. diff --git a/scripts/doc-budgets.manifest.json b/scripts/doc-budgets.manifest.json index 010f767c3e..7e4d154174 100644 --- a/scripts/doc-budgets.manifest.json +++ b/scripts/doc-budgets.manifest.json @@ -1,11 +1,11 @@ { - "AGENTS.md": 1600, + "AGENTS.md": 1680, "docs/AGENTS.md": 1150, "docs/architecture.md": 1800, "docs/cordis-primer.md": 600, "docs/defensive-patterns.md": 550, - "docs/testing.md": 960, + "docs/testing.md": 1020, "examples/AGENTS.md": 310, - "packages/AGENTS.md": 650, + "packages/AGENTS.md": 660, "packages/README.md": 760 } From 28d753391f3601c02b0a867506ace71430f86aa6 Mon Sep 17 00:00:00 2001 From: imccyu <276526105+imccyu@users.noreply.github.com> Date: Thu, 23 Jul 2026 00:42:20 +0800 Subject: [PATCH 04/11] docs(ts): point zh cookbook layout links at the canonical anchor --- docs/cookbook/adding-a-package.zh.md | 2 +- docs/cookbook/adding-a-vendored-package.zh.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/cookbook/adding-a-package.zh.md b/docs/cookbook/adding-a-package.zh.md index 7a820c2c88..94f0702859 100644 --- a/docs/cookbook/adding-a-package.zh.md +++ b/docs/cookbook/adding-a-package.zh.md @@ -32,7 +32,7 @@ package.json 不变式(由 `pnpm run constraints` / `scripts/check-workspace-c | 文件 | 变更 | |---|---| | `tsconfig.base.json` | 已有分组无需编辑;新分组需为 `@deepseek-ai/dsh-*` 通配符添加 `./packages//*/src` 候选路径 | -| `tsconfig.host.json`(host 侧包)或 `tsconfig.client.json`(client 侧包) | 在 `references` 中添加 `{ "path": "./packages//" }`——恰好一个聚合,绝不两个都加([布局](../development.zh.md#typescript-项目布局)) | +| `tsconfig.host.json`(host 侧包)或 `tsconfig.client.json`(client 侧包) | 在 `references` 中添加 `{ "path": "./packages//" }`——恰好一个聚合,绝不两个都加([布局](../development.md#typescript-project-layout)) | | `knip.json` | 仅当包有非 `*.spec.ts` 入口时需要(如 `*.e2e.ts` → 添加 per-workspace override,参照 `packages/llm/llm-deepseek`) | `packages/client/*` 包改为 extends `tsconfig.base.client.json`(而非 `tsconfig.base.json`);client 插件包还需在 package.json 声明 `dshClient`、导出 `./client`、调用共享 tsdown preset(`packages/client/tsdown.client.ts`)——client 侧契约见 [packages/client/AGENTS.md](../../packages/client/AGENTS.md)。 diff --git a/docs/cookbook/adding-a-vendored-package.zh.md b/docs/cookbook/adding-a-vendored-package.zh.md index b811aa5632..c340630aeb 100644 --- a/docs/cookbook/adding-a-vendored-package.zh.md +++ b/docs/cookbook/adding-a-vendored-package.zh.md @@ -56,4 +56,4 @@ pnpm run typecheck pnpm run build && pnpm run test && pnpm run constraints ``` -源码 `paths` 映射只在 `tsconfig.base.json` 存在一份,服务所有图。重要的隔离边界是 project-reference 图:vendored 源码必须通过其自身的 `vendor//tsconfig.json` 被引用,而非被拉入某个聚合的严格程序中([布局](../development.zh.md#typescript-项目布局))。 +源码 `paths` 映射只在 `tsconfig.base.json` 存在一份,服务所有图。重要的隔离边界是 project-reference 图:vendored 源码必须通过其自身的 `vendor//tsconfig.json` 被引用,而非被拉入某个聚合的严格程序中([布局](../development.md#typescript-project-layout))。 From 21287abd78c3248909d560dbcb4bf67e64625ab3 Mon Sep 17 00:00:00 2001 From: imccyu <276526105+imccyu@users.noreply.github.com> Date: Thu, 23 Jul 2026 00:52:19 +0800 Subject: [PATCH 05/11] docs(ts): bilingual pair for the solution-root Agent Note --- ...fig-solution-root-two-aggregates.i18n.yaml | 6 +++ ...2-tsconfig-solution-root-two-aggregates.md | 2 + ...sconfig-solution-root-two-aggregates.zh.md | 45 +++++++++++++++++++ 3 files changed, 53 insertions(+) create mode 100644 .agents/notes/implemented/process/2026-07-22-tsconfig-solution-root-two-aggregates.i18n.yaml create mode 100644 .agents/notes/implemented/process/2026-07-22-tsconfig-solution-root-two-aggregates.zh.md diff --git a/.agents/notes/implemented/process/2026-07-22-tsconfig-solution-root-two-aggregates.i18n.yaml b/.agents/notes/implemented/process/2026-07-22-tsconfig-solution-root-two-aggregates.i18n.yaml new file mode 100644 index 0000000000..d191386725 --- /dev/null +++ b/.agents/notes/implemented/process/2026-07-22-tsconfig-solution-root-two-aggregates.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write +2026-07-22-tsconfig-solution-root-two-aggregates.md: 19c229693b98ff3825caf935fa647ab85aff0f56 +2026-07-22-tsconfig-solution-root-two-aggregates.zh.md: becc43de1ef2f6a53b0f6c2285eb64d9b42604f1 diff --git a/.agents/notes/implemented/process/2026-07-22-tsconfig-solution-root-two-aggregates.md b/.agents/notes/implemented/process/2026-07-22-tsconfig-solution-root-two-aggregates.md index 3ad397a7dc..19c229693b 100644 --- a/.agents/notes/implemented/process/2026-07-22-tsconfig-solution-root-two-aggregates.md +++ b/.agents/notes/implemented/process/2026-07-22-tsconfig-solution-root-two-aggregates.md @@ -2,6 +2,8 @@ Status: implemented +English | [中文](2026-07-22-tsconfig-solution-root-two-aggregates.zh.md) + ## Problem The GUI split introduced a second aggregate program (`tsconfig.client.json`, [layering RFC](../architecture/2026-07-19-gui-layering-and-rpc-protocol.md)) while the root `tsconfig.json` kept doubling as the host aggregate, and `tsconfig.build.json` remained a third, hand-maintained full emit graph. That triple bookkeeping produced four concrete asymmetries: diff --git a/.agents/notes/implemented/process/2026-07-22-tsconfig-solution-root-two-aggregates.zh.md b/.agents/notes/implemented/process/2026-07-22-tsconfig-solution-root-two-aggregates.zh.md new file mode 100644 index 0000000000..becc43de1e --- /dev/null +++ b/.agents/notes/implemented/process/2026-07-22-tsconfig-solution-root-two-aggregates.zh.md @@ -0,0 +1,45 @@ +# Agent Note: 以 solution 根文件统辖两个聚合 program + +Status: implemented + +[English](2026-07-22-tsconfig-solution-root-two-aggregates.md) | 中文 + +## 问题 + +GUI 拆分引入了第二个聚合 program(`tsconfig.client.json`,见[分层 RFC](../architecture/2026-07-19-gui-layering-and-rpc-protocol.md)),根 `tsconfig.json` 则继续兼任宿主侧聚合,`tsconfig.build.json` 还是第三份手工维护的全量 emit 图。三处账本并行,造成四个具体的不对称: + +- 类型检查与构建的 references 列表逐渐脱节(`packages/goal/command-goal` 在类型检查图里,构建图里却没有)。 +- lefthook 的 pre-push 钩子只运行 `tsc -b tsconfig.json`,客户端侧的类型破坏因此通过本地检查点,直到 CI 才暴露。 +- tsserver 只发现名为 `tsconfig.json` 的配置,客户端测试文件不在任何可发现的配置链上,回落到推断项目(inferred project),既没有 paths,lib/jsx 也不对。 +- 各 vitest 配置指向三个不同的解析来源(`tsconfig.vitest.json`、根配置,外加一处手写别名)。 + +## 决策 + +一个 solution 根文件,两个检查单元,一对共享 base,不再单设 build 或 vitest 配置: + +| 文件 | 角色 | 是否构成 program? | +|---|---|---| +| `tsconfig.json` | solution 根文件:`extends` base、`files: []`、两条 references;同时是全仓 `tsc -b tsconfig.json` 图、tsserver 入口,以及 get-tsconfig 消费方(tsx 运行 `examples/`、`scripts/`、文档围栏代码块)就近命中的配置,其裸 workspace 导入经继承来的 `paths` 解析 | 否 | +| `tsconfig.base.json` | 共享 compilerOptions 与源码 `paths` 映射;兼任 vite-tsconfig-paths 的解析门面(不含 `include`,因此对每个导入方都生效) | 否 | +| `tsconfig.base.client.json` | 浏览器侧编译形态(`jsx: react-jsx`、DOM lib、`types: []`),由客户端聚合与每个 `packages/client/*` 包共享 | 否 | +| `tsconfig.host.json` | 原根聚合原样迁入:宿主各包、examples、测试、scripts、website;排除 `packages/client` | 是 | +| `tsconfig.client.json` | 客户端各包及其测试;通过 `extends` 继承 `tsconfig.base.client.json` | 是 | + +整个方案立足的原则:**cordis `Context` 的声明合并冲突只存在于同一个 `ts.Program` 内部,从不发生在模块解析中。** solution 文件不构成 program,因此从一个根文件同时引用两个聚合不会让两侧的声明合并相撞;vite-tsconfig-paths 只读取 `paths` 与 `include`、丢弃全部类型信息,因此一个门面可以横跨两侧。唯一会爆炸的做法是把两侧压平进同一个 program,由此推出两条派生纪律:`tsconfig.base.json` 永远不得添加 `include`/`files`(否则会泄漏进每个继承它的包,并收窄门面范围);每个全仓级 `ts.Program` 消费方(`scripts/ts-project.ts`、doc-typecheck 独立模式)都显式以 `tsconfig.host.json` 或 `tsconfig.client.json` 为种子,绝不使用根 solution。基于 program 的生成器与语义门禁有意只留在宿主侧;客户端侧只有在真实需求出现时才引入基于 program 的门禁。 + +各命令收敛到一张图,且显式写出配置名:`typecheck` = `tsc -b tsconfig.json`,`build` = `tsc -b tsconfig.json && tsdown`,lefthook pre-push 保持 `tsc -b tsconfig.json --pretty false` 不变(经由 solution,这同一行命令现已覆盖两侧)。`tsconfig.build.json` 与 `tsconfig.vitest.json` 删除;所有 vitest 配置都把 vite-tsconfig-paths 指向 `tsconfig.base.json`。 + +solution 根文件刻意 `extends` base:`examples/` 与 `scripts/` 没有更近的 tsconfig,tsx(get-tsconfig)通过根文件解析它们的 workspace 导入。`extends` 把 `paths` 映射带回根文件,`files: []` 则让它始终不构成 program。这不影响两者的*类型检查*:examples、scripts 与 website 的文件由宿主聚合纳入。 + +## 考虑过的替代方案 + +- **把 `tsconfig.build.json` 改名为 `tsconfig.host.json`**——不予采纳:构建图是包含全部客户端包的全量 emit 图,不是宿主图;`tsconfig.host.json` 这个名字对应的是原根聚合,而构建图本身已被 solution 吸收。 +- **让 vitest 指向根 solution**——不予采纳:solution 既没有 `paths` 也没有 `include`,解析结果将取决于插件沿 references 走多远;且客户端聚合的 include 只收测试、不收 src,传递的 src→src 导入会失去映射,回落到 `exports`,加载出模块单例的第二份副本。 +- **保留 `tsconfig.vitest.json` 作为专用门面**——仅保留为后备方案:若 vite-tsconfig-paths 处理不了无 include 的配置再启用;base 文件已经携带 paths 映射,而无 include 的配置处处生效,严格宽于该门面手工维护的 include 列表。 + +## 后果 + +- `docs/development.md#typescript-project-layout` 是权威描述;根 `AGENTS.md` 以约定形式收录上述两条纪律。 +- [ts-build-config Agent Note](2026-06-17-ts-build-config.md) 继续拥有 tsc 先行的构建流水线(tsc 负责输出,tsdown 负责打包,`.ts` 说明符配合 `rewriteRelativeImportExtensions`);其原先「单一根类型检查项目」的形态由本文取代。 +- 新增一个包只登记进恰好一个聚合的 references(宿主包进 `tsconfig.host.json`,客户端包进 `tsconfig.client.json`);构建图无需另行登记。 +- 构建门禁依赖类型检查门禁:两者现在驱动同一张 `tsc -b` 图,并发运行会在同一批 `.tsbuildinfo` 文件上竞态。 From 933fa9677556bf2801eff68a4c463ad55a4ace77 Mon Sep 17 00:00:00 2001 From: imccyu <276526105+imccyu@users.noreply.github.com> Date: Thu, 23 Jul 2026 00:54:18 +0800 Subject: [PATCH 06/11] docs(i18n): re-record the two cookbook pairs after the zh link fix The zh-side link retarget (e43438f39) changed the zh blobs without re-recording; verify-translation-pairing --write refreshed both records. --- docs/cookbook/adding-a-package.i18n.yaml | 2 +- docs/cookbook/adding-a-vendored-package.i18n.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/cookbook/adding-a-package.i18n.yaml b/docs/cookbook/adding-a-package.i18n.yaml index c71c37cb00..addfd26360 100644 --- a/docs/cookbook/adding-a-package.i18n.yaml +++ b/docs/cookbook/adding-a-package.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write adding-a-package.md: 1859310965538b35a353ee05c94b01d1093a3e43 -adding-a-package.zh.md: 7a820c2c8892d429f98a4c8425da4a914e028177 +adding-a-package.zh.md: 94f0702859258febf8991fee0726a3b7893d0855 diff --git a/docs/cookbook/adding-a-vendored-package.i18n.yaml b/docs/cookbook/adding-a-vendored-package.i18n.yaml index 3c6c842176..98c5ee7696 100644 --- a/docs/cookbook/adding-a-vendored-package.i18n.yaml +++ b/docs/cookbook/adding-a-vendored-package.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write adding-a-vendored-package.md: 71ca9fccc9418348784dbb6668127242e4fb45d2 -adding-a-vendored-package.zh.md: b811aa5632acfeb059767efcb0532e03ee1becf4 +adding-a-vendored-package.zh.md: c340630aebeda0ec293a835cdfc8d15d71cd7801 From de5c68eb1a02d5e9b280ab8da593bbe116dcdbf7 Mon Sep 17 00:00:00 2001 From: imccyu <276526105+imccyu@users.noreply.github.com> Date: Thu, 23 Jul 2026 01:13:21 +0800 Subject: [PATCH 07/11] docs(notes): retire current-state references to the deleted build graph Three notes stated tsconfig.build.json as present-tense fact; the aggregates (tsconfig.host.json / tsconfig.client.json) now own the explicit reference lists and the root solution owns the emit graph. Historical narration in dated notes is left untouched. --- .../architecture/2026-06-20-package-hierarchy.md | 4 ++-- .../implemented/process/2026-06-11-tsdown-over-dumble.md | 2 +- .../process/2026-06-20-discover-package-inventory.md | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.agents/notes/implemented/architecture/2026-06-20-package-hierarchy.md b/.agents/notes/implemented/architecture/2026-06-20-package-hierarchy.md index 85c62b7e17..1ba3b94e8b 100644 --- a/.agents/notes/implemented/architecture/2026-06-20-package-hierarchy.md +++ b/.agents/notes/implemented/architecture/2026-06-20-package-hierarchy.md @@ -53,9 +53,9 @@ packages/ The package list had been enumerated in five places. The uniform depth-2 layout lets most of them be derived instead: -- `tsconfig.base.json` maps every package through a single `@deepseek-ai/dsh-*` `paths` wildcard listing one candidate per group, in place of per-package entries. Root `tsconfig.json` reuses that source map and carries the explicit project references that keep package/vendor typecheck boundaries intact. (One subtlety this introduced: a path candidate contains `/*/`, which a naive regex comment-stripper mistakes for a block comment — `scripts/doc-typecheck.ts` reads the JSONC config through TypeScript's parser rather than stripping comments by hand for exactly this reason.) +- `tsconfig.base.json` maps every package through a single `@deepseek-ai/dsh-*` `paths` wildcard listing one candidate per group, in place of per-package entries. The aggregate configs (`tsconfig.host.json`, `tsconfig.client.json`) reuse that source map and carry the explicit project references that keep package/vendor typecheck boundaries intact. (One subtlety this introduced: a path candidate contains `/*/`, which a naive regex comment-stripper mistakes for a block comment — `scripts/doc-typecheck.ts` reads the JSONC config through TypeScript's parser rather than stripping comments by hand for exactly this reason.) - `scripts/publint-all.ts` derives its list by reading the hierarchy (`packages//`), resolving the `TODO(package-inventory)`. -- `tsconfig.build.json`'s project `references` stay an explicit list — TypeScript project references have no wildcard form. Generating these from a manifest is left to a follow-up (see [discover package inventories](../../proposed/process/2026-06-20-discover-package-inventory.md)). +- The aggregates' project `references` stay explicit lists — TypeScript project references have no wildcard form. Generating these from a manifest is left to a follow-up (see [discover package inventories](../../proposed/process/2026-06-20-discover-package-inventory.md)). ### Guardrails added diff --git a/.agents/notes/implemented/process/2026-06-11-tsdown-over-dumble.md b/.agents/notes/implemented/process/2026-06-11-tsdown-over-dumble.md index 4075d4738a..0629acabb4 100644 --- a/.agents/notes/implemented/process/2026-06-11-tsdown-over-dumble.md +++ b/.agents/notes/implemented/process/2026-06-11-tsdown-over-dumble.md @@ -15,7 +15,7 @@ Replace dumble with **tsdown** (rolldown-based, ~2.5M downloads/week, VoidZero-b - Root `tsdown.config.ts` with `workspace: ['vendor/*', 'packages/*/*']` (explicit globs keep bundling to vendored Cordis and the TypeScript package tree; `workspace: true` would also discover example manifests and non-bundled workspace members). - Shared shape: entry `lib/types/index.js`, `outDir: 'lib'`, ESM, `platform: node`, `target: es2024`, `fixedExtension: false` (keeps `.js` for `"type": "module"` packages), `dts: false` (tsc -b owns declarations), `clean: false` (lib/ also holds TSC's `lib/types` intermediate tree). The entry was originally `src/index.ts`; the [TSC-first build Agent Note](2026-06-17-ts-build-config.md) later moved tsdown to bundling TSC-emitted JS so TypeScript transform behavior comes from one compiler. - Two per-package overrides in vendor/ (ours, like the regenerated tsconfigs; logged in vendor/README.md): schemastery (dual `.mjs`/`.cjs` via `outExtensions`), logger-console (two single-entry passes so the shared base class is inlined into each entry instead of a hash-named chunk, matching upstream's published shape). -- `scripts/build.ts` deleted; `pnpm run build` = `tsc -b tsconfig.build.json && tsdown`. +- `scripts/build.ts` deleted; `pnpm run build` = `tsc -b && tsdown` (the root solution owns the emit graph). ## Alternatives considered diff --git a/.agents/notes/proposed/process/2026-06-20-discover-package-inventory.md b/.agents/notes/proposed/process/2026-06-20-discover-package-inventory.md index fa544d6ddb..f9286aeefd 100644 --- a/.agents/notes/proposed/process/2026-06-20-discover-package-inventory.md +++ b/.agents/notes/proposed/process/2026-06-20-discover-package-inventory.md @@ -6,13 +6,13 @@ Status: proposed Package and gate inventories are repeated across TypeScript project references, package docs, CI prose, and Knip overrides. Most restate package layout, manifest data, or aggregate command contents. Each new package therefore creates avoidable synchronization points. -The [package hierarchy](../../implemented/architecture/2026-06-20-package-hierarchy.md) already removed several of these by hand: `scripts/publint-all.ts` now derives its list from the `packages//` layout, and the two `tsconfig` `paths` maps collapsed to one `@deepseek-ai/dsh-*` wildcard. What remains is the inventory that cannot be globbed away — chiefly `tsconfig.build.json`'s project `references`, which TypeScript requires as an explicit array (no wildcard form). +The [package hierarchy](../../implemented/architecture/2026-06-20-package-hierarchy.md) already removed several of these by hand: `scripts/publint-all.ts` now derives its list from the `packages//` layout, and the two `tsconfig` `paths` maps collapsed to one `@deepseek-ai/dsh-*` wildcard. What remains is the inventory that cannot be globbed away — chiefly the aggregate configs' (`tsconfig.host.json`, `tsconfig.client.json`) project `references`, which TypeScript requires as explicit arrays (no wildcard form). Static lists are appropriate when they encode policy; they are needless friction when they duplicate manifest data or layout facts that already exist in `package.json`, workspace globs, or the package hierarchy. ## Proposal -Make the remaining package/gate inventories discoverable. A single canonical source — the `packages//` hierarchy plus package manifests — should drive `tsconfig.build.json`'s `references`, the module graph, and any other full-package list, with a generate-and-verify step (the existing `gen-module-graph` / `gen-cordis-catalog` pattern: a generator writes the artifact, a `--check` mode in `hygiene`/`doc-sync` fails on a stale committed copy). Module graph generation already reads package manifests. `doc-sync` should be the one command that defines and prints its sub-gates, with docs linking to that command rather than restating a second list. +Make the remaining package/gate inventories discoverable. A single canonical source — the `packages//` hierarchy plus package manifests — should drive the aggregates' `references`, the module graph, and any other full-package list, with a generate-and-verify step (the existing `gen-module-graph` / `gen-cordis-catalog` pattern: a generator writes the artifact, a `--check` mode in `hygiene`/`doc-sync` fails on a stale committed copy). Module graph generation already reads package manifests. `doc-sync` should be the one command that defines and prints its sub-gates, with docs linking to that command rather than restating a second list. The hierarchy does not need to encode every fact about a package, but it should encode the broad maintenance policy: core/product packages, integrations, capability seams, and support/test/example packages should not all require a hand-maintained exception list before scripts can tell them apart. @@ -20,7 +20,7 @@ One cataloged item needs no generator at all: folding the e2e entry glob into kn ## Acceptance criteria -- `tsconfig.build.json` project `references` are generated from the hierarchy (a generator emits them; a `--check` gate fails when the committed copy is stale), rather than hand-maintained. +- Aggregate-config project `references` are generated from the hierarchy (a generator emits them; a `--check` gate fails when the committed copy is stale), rather than hand-maintained. - Adding a package does not require editing a static package list for any gate. - Docs describe the source of truth rather than repeating generated inventories. - CI invokes the aggregate commands and lets those commands own their sub-gate lists. From 90b05c7a2b19c767700e632cdfbedf35980e4909 Mon Sep 17 00:00:00 2001 From: imccyu <276526105+imccyu@users.noreply.github.com> Date: Thu, 23 Jul 2026 04:01:06 +0800 Subject: [PATCH 08/11] docs(i18n): re-record the web-client architecture pair after stacking on #538 --- .../2026-07-19-gui-web-client-architecture.i18n.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.i18n.yaml index 85970cbb0e..1868da83b3 100644 --- a/.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write -2026-07-19-gui-web-client-architecture.md: 5a6ef46d6f6c19dcc8cc98f7c2159ea08d20f012 -2026-07-19-gui-web-client-architecture.zh.md: 005d1231ac874376291e884e88ae687df28fc912 +2026-07-19-gui-web-client-architecture.md: f21b840493b83c02d7abc3ba1c1bf90635166ec1 +2026-07-19-gui-web-client-architecture.zh.md: a50dc556cfc96b6d35feea6ef2b1aadae9f31c44 From 2e2ad49f97df24dbef80c812549db3c4ec1c53d7 Mon Sep 17 00:00:00 2001 From: imccyu <276526105+imccyu@users.noreply.github.com> Date: Thu, 23 Jul 2026 10:27:18 +0800 Subject: [PATCH 09/11] chore --- docs/cookbook/adding-a-package.zh.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cookbook/adding-a-package.zh.md b/docs/cookbook/adding-a-package.zh.md index 94f0702859..22f574a046 100644 --- a/docs/cookbook/adding-a-package.zh.md +++ b/docs/cookbook/adding-a-package.zh.md @@ -35,7 +35,7 @@ package.json 不变式(由 `pnpm run constraints` / `scripts/check-workspace-c | `tsconfig.host.json`(host 侧包)或 `tsconfig.client.json`(client 侧包) | 在 `references` 中添加 `{ "path": "./packages//" }`——恰好一个聚合,绝不两个都加([布局](../development.md#typescript-project-layout)) | | `knip.json` | 仅当包有非 `*.spec.ts` 入口时需要(如 `*.e2e.ts` → 添加 per-workspace override,参照 `packages/llm/llm-deepseek`) | -`packages/client/*` 包改为 extends `tsconfig.base.client.json`(而非 `tsconfig.base.json`);client 插件包还需在 package.json 声明 `dshClient`、导出 `./client`、调用共享 tsdown preset(`packages/client/tsdown.client.ts`)——client 侧契约见 [packages/client/AGENTS.md](../../packages/client/AGENTS.md)。 +`packages/client/*` 包改为 extends `tsconfig.base.client.json`(而非 `tsconfig.base.json`);client 插件包还需在 package.json 声明 `dshClient`、导出 `./client`、调用共享 tsdown preset(`packages/client/tsdown.client.ts`)——client 侧见 [packages/client/AGENTS.md](../../packages/client/AGENTS.md)。 以下内容由 glob 或包 manifest 发现机制自动覆盖,无需手动编辑:根 `package.json` workspaces、`scripts/publint-all.ts`、`tsdown.config.ts`、`vitest.config.ts`、`eslint.config.mjs`、`scripts/check-workspace-constraints.ts`。 From 3785e10754d79277ce0fc9045ef1059b1a5ad2f8 Mon Sep 17 00:00:00 2001 From: imccyu <276526105+imccyu@users.noreply.github.com> Date: Thu, 23 Jul 2026 10:37:15 +0800 Subject: [PATCH 10/11] refactor(client-runtime): home the snapshot-store engine in contract/ The store migration (#538) parked the engine in a store/ sibling domain; loader and sessions imported it directly, tripping verify-client-domain-graph (cross-domain shared surface must route through contract/). The engine is layer-0 shared infrastructure by definition, so it moves to contract/store.ts verbatim; the four importers and the spec follow mechanically. --- .../src/client/{store/index.ts => contract/store.ts} | 0 packages/client/runtime/src/client/index.ts | 6 +++--- packages/client/runtime/src/client/loader/index.ts | 2 +- packages/client/runtime/src/client/sessions/service.ts | 4 ++-- packages/client/runtime/src/client/sessions/session.ts | 2 +- packages/client/runtime/tests/store.spec.ts | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) rename packages/client/runtime/src/client/{store/index.ts => contract/store.ts} (100%) diff --git a/packages/client/runtime/src/client/store/index.ts b/packages/client/runtime/src/client/contract/store.ts similarity index 100% rename from packages/client/runtime/src/client/store/index.ts rename to packages/client/runtime/src/client/contract/store.ts diff --git a/packages/client/runtime/src/client/index.ts b/packages/client/runtime/src/client/index.ts index a403484280..77b51a68b5 100644 --- a/packages/client/runtime/src/client/index.ts +++ b/packages/client/runtime/src/client/index.ts @@ -12,7 +12,7 @@ import type { Context } from 'cordis' import type { ConnectionHandle, SessionId } from '@deepseek-ai/dsh-client-connection/client' import type { SnapshotSelectorHook } from '@deepseek-ai/dsh-client-ui-slots' -import type { SnapshotStore } from './store/index.ts' +import type { SnapshotStore } from './contract/store.ts' import { SlotsService } from './slots.ts' import { SessionsService } from './sessions/service.ts' import type { SessionListState } from './sessions/service.ts' @@ -28,10 +28,10 @@ export type { SessionBinding, SessionListState, SessionSummary } from './session // The snapshot-store engine lives here since the store migration (the data // layer owns its substrate; web-react is React glue only). The './client' // main export is the single serving door — no store subpath. -export { createSnapshotStore, defineStore, shallowEqual } from './store/index.ts' +export { createSnapshotStore, defineStore, shallowEqual } from './contract/store.ts' export type { EngineStoreHandle, EngineStoreInstance, ObservableSnapshot, SnapshotStore, -} from './store/index.ts' +} from './contract/store.ts' export type { AssistantBlock, AssistantMessageNode, ContextMessageNode, ConversationNode, ConversationSnapshot, PendingInteraction, RunningToolCall, SteeringMessageNode, diff --git a/packages/client/runtime/src/client/loader/index.ts b/packages/client/runtime/src/client/loader/index.ts index 1afa4b5eb5..81148a7dfc 100644 --- a/packages/client/runtime/src/client/loader/index.ts +++ b/packages/client/runtime/src/client/loader/index.ts @@ -17,7 +17,7 @@ * load one by one in inject topology. */ import type { Context } from 'cordis' -import { createSnapshotStore } from '../store/index.ts' +import { createSnapshotStore } from '../contract/store.ts' import type { BootPluginEntry, ClientLoader, LoaderStatus } from '../index.ts' export type { BootPluginEntry, ClientLoader, LoaderStatus } from '../index.ts' diff --git a/packages/client/runtime/src/client/sessions/service.ts b/packages/client/runtime/src/client/sessions/service.ts index eb4983c839..ec63b8f70d 100644 --- a/packages/client/runtime/src/client/sessions/service.ts +++ b/packages/client/runtime/src/client/sessions/service.ts @@ -16,8 +16,8 @@ import type { Context, Fiber } from 'cordis' import type { IApiClient, SessionId } from '@deepseek-ai/dsh-client-connection/client' import type { SessionCell } from '@deepseek-ai/dsh-client-ui-slots' -import type { SnapshotStore } from '../store/index.ts' -import { createSnapshotStore } from '../store/index.ts' +import type { SnapshotStore } from '../contract/store.ts' +import { createSnapshotStore } from '../contract/store.ts' import { SessionManager } from './manager.ts' import type { Session } from './session.ts' diff --git a/packages/client/runtime/src/client/sessions/session.ts b/packages/client/runtime/src/client/sessions/session.ts index 7330738de8..761aca96c2 100644 --- a/packages/client/runtime/src/client/sessions/session.ts +++ b/packages/client/runtime/src/client/sessions/session.ts @@ -7,7 +7,7 @@ import type { ContentBlock } from '@deepseek-ai/dsh-llm/types' import type { SessionEvent } from '@deepseek-ai/dsh-session/types' import type { HistoryEntry, IApiClient, MuxFrame, RpcError, RpcId, RpcResult, SessionId, ToolEventView } from '@deepseek-ai/dsh-client-connection/client' import { transportError } from '@deepseek-ai/dsh-client-connection/client' -import type { ObservableSnapshot } from '../store/index.ts' +import type { ObservableSnapshot } from '../contract/store.ts' import type { ConversationNode, ConversationSnapshot, OpenState, PendingInteraction, PromptError, RunningToolCall, } from './conversation.ts' diff --git a/packages/client/runtime/tests/store.spec.ts b/packages/client/runtime/tests/store.spec.ts index 22de90c0cf..3abd0483f2 100644 --- a/packages/client/runtime/tests/store.spec.ts +++ b/packages/client/runtime/tests/store.spec.ts @@ -1,5 +1,5 @@ import { afterEach, describe, expect, it, vi } from 'vitest' -import { createSnapshotStore, defineStore, shallowEqual } from '../src/client/store/index.ts' +import { createSnapshotStore, defineStore, shallowEqual } from '../src/client/contract/store.ts' interface State { a: { n: number } From c271cad32dc2779c419239161a8006a788b3243f Mon Sep 17 00:00:00 2001 From: imccyu <276526105+imccyu@users.noreply.github.com> Date: Thu, 23 Jul 2026 10:40:25 +0800 Subject: [PATCH 11/11] docs(i18n): re-record the adding-a-package pair after the zh wording tweak --- docs/cookbook/adding-a-package.i18n.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cookbook/adding-a-package.i18n.yaml b/docs/cookbook/adding-a-package.i18n.yaml index addfd26360..234db6273e 100644 --- a/docs/cookbook/adding-a-package.i18n.yaml +++ b/docs/cookbook/adding-a-package.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write adding-a-package.md: 1859310965538b35a353ee05c94b01d1093a3e43 -adding-a-package.zh.md: 94f0702859258febf8991fee0726a3b7893d0855 +adding-a-package.zh.md: 22f574a0469609e44f5c55957560ff0f04b9a053