Merge branch 'worktree-subagent-seam-pr1' into worktree-subagent-seam-pr2

This commit is contained in:
Tianyi Cui
2026-06-22 14:36:57 +08:00
112 changed files with 716 additions and 351 deletions

View File

@@ -33,19 +33,17 @@ jobs:
- name: Constraints
run: pnpm run constraints
# Before lint: the type-aware ESLint config resolves vendor packages via
# their built declarations (tsconfig.typecheck.json -> vendor/*/lib),
# which `pnpm run typecheck` emits. Lint on a fresh checkout would otherwise
# see unresolved types and erupt with no-unsafe-* errors.
# Before lint: root typecheck validates the package/vendor reference graph
# and refreshes TSC intermediates so type-aware ESLint sees the same project
# boundaries as the build.
- name: Typecheck (src + tests + examples)
run: pnpm run typecheck
- name: Lint
run: pnpm run lint
# Doc-sync gates (doc-sync-enforcement RFC). doc-typecheck compiles the fenced ts blocks in
# the docs and resolves vendor packages via their built declarations, which
# the typecheck step above emits — so it runs after typecheck. The cordis
# Doc-sync gates (doc-sync-enforcement RFC). doc-typecheck compiles the
# fenced ts blocks against the root project-reference graph. The cordis
# catalog freshness check, type-equiv check, and markdown wrap/link checks
# only read source. Same `doc-sync` script the pre-push hook runs
# (quality-gates RFC: one source of truth).
@@ -71,12 +69,13 @@ jobs:
run: pnpm run test:snapshot
# Before hygiene: publint validates the packed artifacts (lib/index.js),
# which only the tsdown bundling step emits.
# which only the tsdown bundling step emits, and verify-node-next-types
# validates the built declarations.
- name: Build (tsc -b + tsdown bundles)
run: pnpm run build
- name: Hygiene (knip + publint)
run: pnpm run knip && pnpm run publint
- name: Hygiene (knip + publint + constraints + NodeNext types)
run: pnpm run hygiene
- name: Demo smoke test
run: |

View File

@@ -139,14 +139,13 @@ pnpm run test:snapshot # ACP snapshot tests (examples/*/tests/**/*.snapshot.ts)
pnpm run test:snapshot:record # re-record fixtures + goldens against the real
# API (needs DEEPSEEK_API_KEY); accept-the-diff = re-record
# (or `pnpm run test:snapshot -u` to refresh goldens only)
pnpm run typecheck # tsc -b tsconfig.build.json (declarations) + tsc -p
# tsconfig.typecheck.json (tests/examples typecheck too)
pnpm run typecheck # tsc -b tsconfig.json
pnpm run lint # eslint .
pnpm run lint:fix # eslint . --fix
pnpm run build # tsc -b tsconfig.build.json && tsdown (JS bundles into lib/)
pnpm run build # tsc emits lib/types, then tsdown bundles runtime lib/index.*
pnpm run knip # dead-code / unused-dependency check
pnpm run publint # package.json publish-correctness check (every packages/*/* package)
pnpm run hygiene # knip + publint + workspace constraints
pnpm run hygiene # knip + publint + workspace constraints + NodeNext type-consumer check
pnpm run doc-typecheck # typecheck every ```ts block in README.md, docs/**/*.md,
# packages/*/*.md + packages/*/*/*.md (doc/code drift gate)
pnpm run gen-cordis-catalog # regenerate docs/cordis-catalog/events-and-services.md
@@ -161,6 +160,8 @@ pnpm run verify-package-paths # assert every packages/<path> cited in Markdown
pnpm run verify-rfc-classification # assert every RFC lives in a valid
# {lifecycle}/{class}/ folder and docs/rfc/README.md lists it
# under the matching heading (closed class set + index completeness)
pnpm run verify-node-next-types # assert built declarations typecheck for a
# standard external NodeNext ESM TypeScript consumer
pnpm run doc-sync # doc-typecheck + verify-cordis-catalog + verify-md-wrap + verify-md-links + verify-doc-refs + verify-package-paths + verify-rfc-classification + verify-type-equiv (CI runs this)
pnpm run demo:echo # run examples/echo-agent (no API key; type "echo hi" to
# see a tool call) — the mock skeleton
@@ -184,12 +185,12 @@ cordis.yml configs reference env vars with the `!!js` tag: `apiKey: !!js process
**Lean on with-key e2e tests — we are DeepSeek and model inference is cheap.** A no-key test (mock adapter, or an operation that never reaches the model) is great for determinism and CI, but it can only prove the plumbing, not that the agent actually *works* against a real model. Do not ration real-API tests to save tokens: write many of them, cover the real flows (a real prompt that writes a file, a multi-turn conversation, tool use, cancellation mid-stream), and run them frequently while developing — locally and whenever you have a key in the environment. **Especially smoke tests**: a cheap with-key smoke test that boots the real example, sends one real prompt, and checks the world (a file on disk, a non-empty assistant turn) catches whole classes of "green unit tests, broken product" failures that mocks structurally cannot — the very gap that let the ACP inject bug ship (see [docs/postmortem/0001](docs/postmortem/0001-acp-default-export-drops-inject.md)). The self-skip rule is ONLY so CI (which has no secrets) stays green and so a contributor without a key isn't blocked — it is not a signal that real-API tests are expensive or second-class. When in doubt, add the with-key test AND run it.
Dev/test/demo run **unbuilt** via tsx + the `paths` map in the root `tsconfig.json` (`vitest` resolves through `tsconfig.test.json`). Building is only needed for publishing/consumption outside the repo — with one exception: `pnpm run lint`'s type-aware rules resolve vendor packages through their built declarations (`tsconfig.typecheck.json``vendor/*/lib`), so run `pnpm run typecheck` once after a fresh clone (CI does the same) or lint reports unresolved-type `no-unsafe-*` errors.
Dev/test/demo run **unbuilt** via tsx + the source `paths` map in the root `tsconfig.json` (`vitest` resolves through that same root config). Building is only needed for publishing/consumption outside the repo. Non-published code (`examples`, tests, and scripts) is checked by root `tsconfig.json`, which sets `noEmit` and references the package/vendor graph so those sources stay checked under their own tsconfig boundaries.
## Conventions
- **Package naming**: every npm package in this repo is `@deepseek-ai/dsh-<name>` (vendored packages keep their upstream names and are `private: true`).
- **ESM everywhere** (`"type": "module"`); imports between workspace packages use package names, never relative paths across package boundaries. In-package imports use explicit `.ts` extensions (allowImportingTsExtensions).
- **ESM everywhere** (`"type": "module"`); imports between workspace packages use package names, never relative paths across package boundaries. In-package relative imports use explicit `.ts` extensions; `rewriteRelativeImportExtensions` turns those into `.js` in emitted JS, while declarations keep explicit `.ts` specifiers that NodeNext/Node16 TypeScript consumers can resolve to sibling `.d.ts` files. `lib/types/**/*.js` is a bundler-only intermediate, not a Node ESM entrypoint.
- **`cordis` is a peerDependency** (+ devDependency) of every harness package, mirroring upstream convention.
- **Registrations are effects**: anything a plugin contributes (adapter, tool, section, agent, event listener) goes through `ctx.effect()` / `ctx.on()` so disposal and HMR work. If you write a registry, `register()` must return the disposer.
- **Typed events via declaration merging**: services declare their events in `declare module 'cordis' { interface Events { … } }`, and their ctx key in `interface Context`. Extensible unions use the merge-extensible-map pattern (see `ContentBlockMap`, `MessageSourceMap`).

View File

@@ -5,29 +5,33 @@ The file-by-file checklist for a new `@deepseek-ai/dsh-<name>` package. (Verifie
## 1. Create the package
```
packages/<name>/
packages/<group>/<pkg>/
package.json # copy from packages/core/tools, adjust name/description/deps
tsconfig.json # extends ../../tsconfig.base.json, rootDir src, outDir lib,
# references: vendor/cosmokit, vendor/cordis (+ vendor/schemastery
# if you use Config, + ../<dep> for each dsh dependency)
tsconfig.json # extends ../../../tsconfig.base.json, rootDir src,
# outDir lib/types, references: ../../../vendor/cosmokit,
# ../../../vendor/cordis (+ ../../../vendor/schemastery if
# you use Config, + ../../<group>/<dep> for each dsh dep)
src/index.ts # service default export or plugin (name/inject/apply/Config)
tests/<x>.spec.ts
README.md # service API, events, extension points, design notes
```
package.json invariants (enforced by `pnpm run constraints` / `scripts/check-workspace-constraints.ts`): `private: true`, `version: 0.0.1`, `type: module`, `cordis` in BOTH peerDependencies and devDependencies (same range). Mirror every dsh peer dependency in devDependencies. `schemastery` goes in `dependencies` (it is a runtime validator), matching agent-loop.
Choose an existing group when one matches the package's role (`core`, `llm`, `bash`, `session-persistence`, `ui`, `util`, or `support`). A new group is allowed, but it is a pure container: no `package.json`, no source files, and packages still sit exactly one level below it.
package.json invariants (enforced by `pnpm run constraints` / `scripts/check-workspace-constraints.ts`): `private: true`, `version: 0.0.1`, `type: module`, `main: "lib/index.js"`, `types: "lib/types/index.d.ts"`, `exports["."].types: "./lib/types/index.d.ts"`, `exports["."].default: "./lib/index.js"`, `cordis` in BOTH peerDependencies and devDependencies (same range). Mirror every dsh peer dependency in devDependencies. `schemastery` goes in `dependencies` (it is a runtime validator), matching agent-loop. The `files` list is precise: `lib/index.js`, `lib/types/**/*.d.ts`, `lib/types/**/*.d.ts.map`, and `src`; do not publish `lib/types` JS or JS-map intermediates or stale root declaration files. CLI app packages with a package `bin` include `lib/bin.js` immediately after `lib/index.js` in `files`.
In-package relative imports use explicit `.ts` specifiers in source (for example, `export * from './types.ts'`). The compiler rewrites those to `.js` in emitted JS and leaves explicit `.ts` specifiers in declarations, which standard NodeNext/Node16 TypeScript consumers resolve to the sibling `.d.ts` files.
## 2. Register it in the root configs
| File | Change |
|---|---|
| `tsconfig.base.json` | add `"@deepseek-ai/dsh-<name>": ["./packages/<name>/src"]` to `paths` |
| `tsconfig.typecheck.json` | same entry (this file overrides the map wholesale) |
| `tsconfig.build.json` | add `{ "path": "./packages/<name>" }` to `references` |
| `scripts/publint-all.ts` | add `'packages/<name>'` to the array |
| `tsconfig.base.json` | no edit for an existing group; for a new group, add a `./packages/<group>/*/src` candidate to the `@deepseek-ai/dsh-*` wildcard |
| `tsconfig.json` | add `{ "path": "./packages/<group>/<pkg>" }` to `references` |
| `tsconfig.build.json` | add `{ "path": "./packages/<group>/<pkg>" }` to `references` |
| `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`) |
Covered automatically by globs — no edits needed: root `package.json` workspaces, `tsdown.config.ts`, `vitest.config.ts`, `eslint.config.mjs`.
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
@@ -39,7 +43,7 @@ For a swappable capability, split interface / implementation / consumer into sep
pnpm install # registers the workspace
pnpm run constraints && pnpm run typecheck && pnpm run lint
pnpm run test:coverage # 100% per-file over src (types.ts exempt)
pnpm run build && pnpm run knip && pnpm run publint
pnpm run build && pnpm run hygiene
```
Test expectations: every registry/registration needs an HMR-safety test (register from a child fiber, dispose it, assert cleanup). Excessive tests are welcome — see AGENTS.md.

View File

@@ -12,13 +12,13 @@ vendor/<dir>/
README.md LICENSE # if upstream ships them
```
`tsconfig.json` mirrors the other vendored packages — `rootDir: src`, `outDir: lib`, the strictness relaxations upstream code needs, and a `references` entry for every other vendored package it imports:
`tsconfig.json` mirrors the other vendored packages — `rootDir: src`, `outDir: lib/types`, the strictness relaxations upstream code needs, and a `references` entry for every other vendored package it imports:
```jsonc
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src", "outDir": "lib",
"rootDir": "src", "outDir": "lib/types",
"noUncheckedIndexedAccess": false, "exactOptionalPropertyTypes": false,
"noImplicitOverride": false, "noUnusedLocals": false, "noUnusedParameters": false
},
@@ -27,19 +27,21 @@ vendor/<dir>/
}
```
`package.json` invariants: `"private": true` (vendored packages are never published), keep upstream's `name`/`version`/`exports`/`type`, and list its cordis deps in `peerDependencies` (matching the upstream manifest). Transitive upstream deps must themselves be vendored or already present — vendoring one package often means vendoring its dependency tree (e.g. `@cordisjs/plugin-http` pulls `@cordisjs/fetch-file`).
`package.json` invariants: `"private": true` (vendored packages are never published), keep upstream's `name`/`version`/`exports`/`type`, point declaration metadata at `lib/types`, publish `.d.ts` and `.d.ts.map` declaration outputs, and list its cordis deps in `peerDependencies` (matching the upstream manifest). Transitive upstream deps must themselves be vendored or already present — vendoring one package often means vendoring its dependency tree (e.g. `@cordisjs/plugin-http` pulls `@cordisjs/fetch-file`).
Local relative imports/exports in vendored TypeScript source use explicit `.ts` specifiers after copying. This is a repo-local build-shape divergence from upstream: `rewriteRelativeImportExtensions` emits `.js` runtime imports while declarations keep explicit `.ts` specifiers that NodeNext/Node16 TypeScript consumers can resolve.
## 2. Register it in the root configs
| File | Change |
|---|---|
| `tsconfig.base.json` | add `"<npm-name>": ["./vendor/<dir>/src"]` to `paths` |
| `tsconfig.typecheck.json` | add `"<npm-name>": ["./vendor/<dir>/lib"]` — this file points at built declarations, not src. If the package's `types` entry isn't `lib/index.d.ts`, point at that built file instead (e.g. `logger-console` maps to `./vendor/logger-console/lib/shared`, matching its `"types": "lib/shared.d.ts"`). |
| `tsconfig.json` | add `{ "path": "./vendor/<dir>" }` to `references` |
| `tsconfig.build.json` | add `{ "path": "./vendor/<dir>" }` to `references` (before the `packages/*` entries) |
| `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) |
Covered automatically by globs — no edits needed: root `package.json` workspaces (`vendor/*`), `tsdown.config.ts`, `vitest.config.ts`, `eslint.config.mjs`. A per-package `vendor/<dir>/tsdown.config.ts` is needed ONLY if the build shape diverges from the root default (dual ESM/CJS or multiple entries — see `vendor/schemastery` and `vendor/logger-console`).
Covered automatically by globs — no edits needed: root `package.json` workspaces (`vendor/*`), `tsdown.config.ts`, `vitest.config.ts`, `eslint.config.mjs`. A per-package `vendor/<dir>/tsdown.config.ts` is needed ONLY if the build shape diverges from the root default (dual ESM/CJS or multiple entries — see `vendor/schemastery` and `vendor/logger-console`); its entry should read the JS emitted under `lib/types`.
## 3. Mind the manifest guard
@@ -49,8 +51,8 @@ Covered automatically by globs — no edits needed: root `package.json` workspac
```sh
pnpm install # registers the workspace
pnpm run typecheck # the base→lib path split means: run once after a fresh add
pnpm run typecheck
pnpm run build && pnpm run test && pnpm run constraints
```
Note the `tsconfig` two-map split (called out in [AGENTS.md](../../AGENTS.md) § Secrets/.env): `lint`'s type-aware rules resolve vendored packages through their built `lib/` declarations, so run `pnpm run typecheck` (which builds them) once after adding the package or lint reports unresolved-type errors.
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/<dir>/tsconfig.json`, not pulled into a root strict program.

View File

@@ -31,7 +31,7 @@ Run typecheck once after a fresh clone:
pnpm run typecheck
```
That first typecheck builds declaration output used by type-aware linting for vendored packages. Without it, `pnpm run lint` can report unresolved-type `no-unsafe-*` errors even when source code is fine.
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.
If you are preparing to push from a fresh clone or worktree, also build once:
@@ -39,7 +39,7 @@ If you are preparing to push from a fresh clone or worktree, also build once:
pnpm run build
```
`pnpm run hygiene` includes `publint`, which validates package entrypoints against the built `lib/*.js` files. A fresh worktree has no bundled JS until `pnpm run build` runs.
`pnpm run hygiene` includes `publint`, which validates package entrypoints against the built `lib/*.js` files, and `verify-node-next-types`, which validates built declarations against a temporary NodeNext consumer. A fresh worktree has no bundled JS or declarations until `pnpm run build` runs.
## Environment variables
@@ -76,10 +76,10 @@ The GitHub workflow runs these gates on each pull request:
- `pnpm run test:coverage`
- `pnpm run test:snapshot`
- `pnpm run build`
- `pnpm run knip && pnpm run publint`
- `pnpm run hygiene`
- an echo-agent smoke test that checks the demo's tool call, tool result, and JSONL output
`pnpm run hygiene` is the local shorthand for `pnpm run knip && pnpm run publint && pnpm run constraints`; CI splits `pnpm run constraints` into its own earlier step, then runs `pnpm run knip && pnpm run publint` after `pnpm run build`.
`pnpm run hygiene` is the local shorthand for `pnpm run knip && pnpm run publint && pnpm run constraints && pnpm run verify-node-next-types`; CI also runs `pnpm run constraints` as an earlier fail-fast step, then runs the full hygiene script after `pnpm run build`.
## Daily commands
@@ -89,7 +89,7 @@ Use these from the repo root:
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 typecheck # build declarations, then typecheck source, tests, and examples
pnpm run typecheck # build package/vendor outputs, then typecheck examples, tests, and scripts
pnpm run lint # eslint .
pnpm run lint:fix # eslint . --fix
pnpm run doc-typecheck # compile checked TypeScript snippets in Markdown docs
@@ -100,8 +100,9 @@ pnpm run verify-type-equiv # fail if a ```ts type-equiv doc block drifts from i
pnpm run doc-sync # doc-typecheck, cordis-catalog freshness, markdown wrap/link, and type-equiv verification
pnpm run gen-module-graph # regenerate docs/module-graph.md from package peerDeps
pnpm run verify-module-graph # fail if docs/module-graph.md is stale
pnpm run build # build declarations and JS bundles
pnpm run hygiene # knip, publint, and workspace constraints
pnpm run build # emit lib/types intermediates, then bundle lib/index.* runtime files
pnpm run verify-node-next-types # fail if built declarations are not NodeNext-consumable
pnpm run hygiene # knip, publint, workspace constraints, and NodeNext declaration check
```
When changing package public behavior, update the relevant README or JSDoc in the same change. `pnpm run doc-sync` catches checked TypeScript snippets, cordis events/services catalog drift, and hard-wrapped markdown prose, but broader prose/API sync still needs review.

View File

@@ -126,6 +126,7 @@ Do NOT write one for a mechanical or local choice (a variable name, a one-file r
| [tsdown for JS bundling instead of dumble](implemented/process/2026-06-11-tsdown-over-dumble.md) | 2026-06-11 |
| [Doc-sync enforcement](implemented/process/2026-06-11-doc-sync-enforcement.md) | 2026-06-11 |
| [pnpm as the package manager instead of Yarn 4](implemented/process/2026-06-16-pnpm-over-yarn.md) | 2026-06-16 |
| [TSC-first build and one tsconfig](implemented/process/2026-06-17-ts-build-config.md) | 2026-06-17 |
| [Markdown cross-link validity linting](implemented/process/2026-06-18-markdown-cross-link-lint.md) | 2026-06-18 |
| [Core-data-structures catalog and the `ts type-equiv` drift gate](implemented/process/2026-06-20-core-data-structures-catalog.md) | 2026-06-20 |
| [Generated cordis events + services catalog](implemented/process/2026-06-20-generated-cordis-catalog.md) | 2026-06-20 |

View File

@@ -51,7 +51,7 @@ 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` and `tsconfig.typecheck.json` each map every package through a single `@deepseek-ai/dsh-*` `paths` wildcard listing one candidate per group, in place of 18 per-package entries. (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 `paths` map via the TypeScript JSONC API 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. 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.)
- `scripts/publint-all.ts` derives its list by reading the hierarchy (`packages/<group>/<pkg>`), 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)).

View File

@@ -12,10 +12,10 @@ AGENTS.md promises that docs and code stay strictly in sync, but the promise was
Two gates, mirroring the existing `scripts/` style (tsx ESM, one job each):
1. **`doc-typecheck`** extracts every fenced ` ```ts ` block from `README.md`, `docs/**`, and `packages/*/README.md`, writes them to a temp project, and compiles with `tsc --noEmit`. The temp tsconfig copies only resolution-relevant options and the workspace `paths` map from `tsconfig.typecheck.json` (vendor → built `lib`, harness → `src`) — resolving vendor to `lib` is essential, or tsc type-checks raw vendor source and floods the run. A block that is a deliberate sketch opts out with an explicit ` ```ts ignore-check ` info string; the script reports the opt-out ratio and fails if it exceeds half, so the escape hatch can't quietly become the norm.
1. **`doc-typecheck`** extracts every fenced ` ```ts ` block from `README.md`, `docs/**`, and `packages/*/README.md`, writes them to a temp project extending the root `tsconfig.json`, and compiles it with `tsc -b`. The temp project reuses the source `paths` map and the root project references, so documentation examples see source while vendored code remains checked under its own tsconfig settings. A block that is a deliberate sketch opts out with an explicit ` ```ts ignore-check ` info string; the script reports the opt-out ratio and fails if it exceeds half, so the escape hatch can't quietly become the norm.
2. **`verify-event-taxonomy`** extracts the event names from the `interface Events` blocks across `packages/*/src` and from the taxonomy table in `docs/architecture.md`, and asserts the two sets match exactly. Verify, don't generate: the table keeps its hand-written Mode/Purpose columns; only the set of names is checked. (Landing this surfaced three events the table had been missing — `tools/change`, `llm/adapter-change`, `system-prompt/change`.) **Superseded** by [the generated cordis catalog](2026-06-20-generated-cordis-catalog.md): this gate and its `architecture.md` table are retired in favor of a fully-generated `docs/cordis-catalog/events-and-services.md` and its `verify-cordis-catalog` freshness gate. The other gates here (`doc-typecheck`, and the `verify-md-wrap` amendment below) are unaffected.
Both run via a shared `doc-sync` package.json script that the lefthook pre-push hook and CI both invoke ([mechanical quality gates](2026-06-11-quality-gates.md): hooks and CI call the same scripts, so the gate fires locally before a push — not only after it). They run after `pnpm run typecheck` (which emits the vendor `lib/` that doc-typecheck resolves against). API-extractor golden reports ([the deferred API-extractor-reports proposal](../../proposed/process/2026-06-11-api-extractor-reports.md)) were deliberately **deferred** — low value for an internal monorepo where reviewers already see the source diff, and a heavy, finicky dependency.
Both run via a shared `doc-sync` package.json script that the lefthook pre-push hook and CI both invoke ([mechanical quality gates](2026-06-11-quality-gates.md): hooks and CI call the same scripts, so the gate fires locally before a push — not only after it). They run after `pnpm run typecheck`, which validates the package/vendor build graph that doc-typecheck references. API-extractor golden reports ([the deferred API-extractor-reports proposal](../../proposed/process/2026-06-11-api-extractor-reports.md)) were deliberately **deferred** — low value for an internal monorepo where reviewers already see the source diff, and a heavy, finicky dependency.
**Amendment (2026-06-17):** a third gate, **`verify-md-wrap`**, was later folded into `doc-sync`. It parses each in-scope Markdown file (`README.md`, `docs/**`, `packages/*/README.md`, plus `AGENTS.md` / `packages/AGENTS.md`) with `mdast-util-from-markdown` + GFM and fails on any `paragraph` node spanning more than one source line, enforcing the AGENTS.md "Markdown is not hard-wrapped" convention. Same verify-don't-generate principle: it reports hard-wraps and never rewrites, so it adds no formatting churn. `doc-sync` is now three gates.

View File

@@ -12,10 +12,10 @@ This codebase is developed primarily by coding agents. Agents follow enforced ga
Every AGENTS.md promise gets a command that exits non-zero, wired into git hooks and CI both calling the same package.json scripts:
- Max-strict TypeScript (`noUncheckedIndexedAccess`, `exactOptionalPropertyTypes`, …); tests and examples typecheck in CI via `tsconfig.typecheck.json` (vendored packages resolve as built declarations).
- Max-strict TypeScript (`noUncheckedIndexedAccess`, `exactOptionalPropertyTypes`, …); examples, tests, and scripts typecheck in CI via the root no-emit `tsconfig.json` while package/vendor code stays behind its own project-reference boundary.
- ESLint strict-type-checked + @stylistic (the house style, enforced); vendored code excluded.
- Per-file 100% coverage on `packages/*/src` (v8); unreachable defensive guards carry `/* v8 ignore */ ` with stated reasons instead of deletion.
- knip (dead code/deps), publint (package correctness), workspace constraints (workspace rules: private, cordis peer+dev, uniform version, ESM).
- knip (dead code/deps), publint (package correctness), workspace constraints (workspace rules: private, cordis peer+dev, uniform version, ESM), and a NodeNext consumer typecheck for built package declarations.
- lefthook pre-commit (lint staged, typecheck, vendor-manifest guard) and pre-push (tests, hygiene); CI runs the full matrix on node 24/26 plus a demo smoke test driving the echo-agent end to end.
## Consequences

View File

@@ -15,12 +15,12 @@ Build output currently matters only for `pnpm run build` + publint (nothing publ
Replace dumble with **tsdown** (rolldown-based, ~2.5M downloads/week, VoidZero-backed, actively released):
- Root `tsdown.config.ts` with `workspace: ['vendor/*', 'packages/*']` (explicit globs, not `workspace: true`, which would also pick up `examples/*` — they have package.json files but are not pnpm workspaces).
- Shared shape: entry `src/index.ts`, `outDir: 'lib'`, ESM, `platform: node`, `target: es2024`, `fixedExtension: false` (keeps `.js` for `"type": "module"` packages), `dts: false` (tsc -b owns declarations), `clean: false` (lib/ holds tsc's .d.ts output).
- 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 RFC](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 && tsdown`.
- `scripts/build.ts` deleted; `pnpm run build` = `tsc -b tsconfig.build.json && tsdown`.
Alternatives considered: **direct esbuild script** (most established engine, zero wrapper risk, but hand-maintains the per-package spec table tsdown's workspace mode gives us); **pkgroll** (closest drop-in philosophically, but 78k dl/wk and Rollup-based — strictly weaker maintenance story than tsdown); **keep dumble** (perfect upstream alignment, unacceptable bus factor).
## Consequences
Output file lists are byte-for-byte-list identical to dumble's (verified by snapshot diff at migration time); externals still come from each package's dependencies/peerDependencies. We give up dumble's exports-field inference — new packages with non-default shapes need a per-package `tsdown.config.ts` instead of just package.json fields. Future option: tsdown could also absorb declaration bundling (isolatedDeclarations) if `tsc -b` ever becomes the bottleneck; that would be a new RFC.
Runtime bundle outputs still follow the dumble-era public entry shape (`lib/index.js`, plus package-specific variants such as `schemastery`'s `lib/index.mjs`/`lib/index.cjs` and `logger-console`'s `lib/browser.js`); declarations now live under `lib/types` per the [TSC-first build RFC](2026-06-17-ts-build-config.md). Externals still come from each package's dependencies/peerDependencies. We give up dumble's exports-field inference — new packages with non-default shapes need a per-package `tsdown.config.ts` instead of just package.json fields. Future option: tsdown could also absorb declaration bundling (isolatedDeclarations) if `tsc -b` ever becomes the bottleneck; that would be a new RFC.

View File

@@ -0,0 +1,73 @@
# RFC: TSC-first build and one tsconfig
Status: implemented (accepted 2026-06-20)
<!-- XXX: legacy ADR/RFC body format, not yet normalized to a unified RFC template. -->
## Context
The current TypeScript build and typecheck setup had these issues:
- `build` used `tsc` to transform `.ts` to `.d.ts` files for packages under `packages/<group>/<pkg>` and `vendor/*`, and then used `tsdown` to transform `.ts` to bundled `.js` files. This made two tools do TypeScript transform.
- `typecheck` tended to validate packages, vendor source, examples, tests, and scripts through one root typecheck config.
The goal is to make build and typecheck use matching tsconfig boundaries and TypeScript resolution/transform behavior. Build should generate `.js`, `.d.ts`, `.js.map`, and `.d.ts.map` through one compiler and config, so publish output and type validation stay consistent.
Validation found several concrete technical issues and possible routes:
- `tsdown` uses `oxc` to transform TypeScript, which is not the same behavior as `tsc`.
- Bundled `.d.ts` emitted by `tsdown` conflicts with Cordis' internal relative module augmentation shape.
- The tsc output is affected by `allowImportingTsExtensions`, so we need to ensure that generated `.js` files do not import `.ts` files and generated `.d.ts` files keep explicit relative specifiers that NodeNext/Node16 accepts. Therefore, in-package relative imports use explicit `.ts` specifiers in TypeScript source and `rewriteRelativeImportExtensions` rewrites those specifiers to `.js` in emitted JS.
- Bundled `.js` emitted by `tsdown` is not the same behavior as per-file `.js` emitted by `tsc -b`, such as decorator transform behavior.
- `vendor/*/src`, examples, tests, and scripts cannot all be plain-included in one root strict program.
- Directly typechecking `vendor/*/src` under the root strict config triggers many type errors outside this project's ownership.
- Package dependencies under `packages/*/*` on `vendor` are resolved to the `vendor/*/lib` for different tsconfig strictness.
## Decision
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 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.
The command orchestration shape is:
```sh
pnpm run build:
tsc -b tsconfig.build.json
tsdown
pnpm run verify-node-next-types:
tsx scripts/verify-node-next-types.ts
pnpm run typecheck:
tsc -b tsconfig.json
```
`pnpm run demo:*` still runs `src` directly through tsx and root paths, without a compile step.
## Consequences
Build responsibilities are clearer:
- Each module under `packages/<group>/<pkg>` 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.*`.
- `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.
- `lib/index.*` is the publish runtime output and is generated by the bundler, currently `tsdown`.
- `pnpm run verify-node-next-types` scans built declarations for relative specifiers without file extensions, then typechecks a temporary external ESM consumer with `moduleResolution: "NodeNext"` against the built `types`/`exports` surface, so declaration specifier regressions fail before publish.
- The `typecheck` command uses `tsconfig.json`. Examples, tests, and scripts are checked by the root no-emit project, while packages and vendor modules keep the same emit behavior as `build`. Package and vendor source stays behind project-reference boundaries.
The Cordis vendor copy now has one more type-structure divergence from upstream. During upstream sync, that divergence must be reapplied or explicitly retired.

View File

@@ -36,7 +36,7 @@ export default tseslint.config(
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.typecheck.json'],
project: ['./packages/*/*/tsconfig.json', './tsconfig.json'],
tsconfigRootDir: import.meta.dirname,
},
},
@@ -81,13 +81,13 @@ export default tseslint.config(
// --- tests: same rules, minus the friction that fights test ergonomics --
{
files: ['packages/*/*/tests/**/*.ts'],
files: ['packages/*/*/tests/**/*.ts', 'examples/*/tests/**/*.ts'],
extends: [
...tseslint.configs.strictTypeChecked,
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.typecheck.json'],
project: ['./tsconfig.json'],
tsconfigRootDir: import.meta.dirname,
},
},

View File

@@ -13,7 +13,8 @@
],
"scripts": {
"build": "tsc -b tsconfig.build.json && tsdown",
"typecheck": "tsc -b tsconfig.build.json && tsc -p tsconfig.typecheck.json",
"clean:build": "rm -rf .typecheck packages/*/*/lib vendor/*/lib *.tsbuildinfo",
"typecheck": "tsc -b tsconfig.json",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"test": "vitest run",
@@ -30,13 +31,14 @@
"verify-package-paths": "tsx scripts/verify-package-paths.ts",
"verify-rfc-classification": "tsx scripts/verify-rfc-classification.ts",
"verify-type-equiv": "tsx scripts/verify-type-equiv.ts",
"verify-node-next-types": "tsx scripts/verify-node-next-types.ts",
"gen-cordis-catalog": "tsx scripts/gen-cordis-catalog.ts",
"verify-cordis-catalog": "tsx scripts/gen-cordis-catalog.ts --check",
"gen-module-graph": "tsx scripts/gen-module-graph.ts",
"verify-module-graph": "tsx scripts/gen-module-graph.ts --check",
"constraints": "tsx scripts/check-workspace-constraints.ts",
"doc-sync": "pnpm run doc-typecheck && pnpm run verify-cordis-catalog && pnpm run verify-md-wrap && pnpm run verify-md-links && pnpm run verify-doc-refs && pnpm run verify-package-paths && pnpm run verify-rfc-classification && pnpm run verify-type-equiv",
"hygiene": "pnpm run knip && pnpm run publint && pnpm run constraints",
"hygiene": "pnpm run knip && pnpm run publint && pnpm run constraints && pnpm run verify-node-next-types",
"demo:echo": "node --expose-internals --import tsx packages/ui/stdio-agent/src/bin.ts examples/echo-agent/cordis.yml",
"demo:coding": "node --expose-internals --import tsx packages/ui/stdio-agent/src/bin.ts examples/coding-agent/cordis.yml",
"demo:acp": "node --import tsx packages/ui/acp-agent/src/bin.ts examples/acp-agent/cordis.yml",

View File

@@ -90,5 +90,5 @@ Each package has its own `README.md` with purpose, service API, events, extensio
- **Declaration merging for events and ctx**: services declare their events in `declare module 'cordis' { interface Events { ... } }` and their ctx key in `interface Context`.
- **Waterfall semantics**: `ctx.waterfall` listeners receive `(...args, next)` and MUST call `next()` to delegate; returning without it short-circuits (the veto mechanism).
- **Extensible unions**: `ContentBlockMap`, `MessageSourceMap`, `FinishReasonMap`, `TurnTriggerMap`, `TurnEndReasonMap`, and `SessionEventMap` use the merge-extensible-map pattern so plugins can add variants via declaration merging.
- **ESM everywhere**; imports use package names across package boundaries, `.ts` extensions within a package.
- **ESM everywhere**; imports use package names across package boundaries and explicit `.ts` relative specifiers within a package.
- **Tests**: vitest, colocated under `packages/<group>/<pkg>/tests/*.spec.ts`. Every registry needs an HMR-safety test. Err on the side of more tests.

View File

@@ -5,17 +5,19 @@
"private": true,
"type": "module",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"types": "lib/types/index.d.ts",
"exports": {
".": {
"types": "./lib/index.d.ts",
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"
},
"files": [
"lib",
"lib/index.js",
"lib/types/**/*.d.ts",
"lib/types/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",

View File

@@ -2,7 +2,7 @@
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib"
"outDir": "lib/types"
},
"include": [
"src"

View File

@@ -5,17 +5,19 @@
"private": true,
"type": "module",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"types": "lib/types/index.d.ts",
"exports": {
".": {
"types": "./lib/index.d.ts",
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"
},
"files": [
"lib",
"lib/index.js",
"lib/types/**/*.d.ts",
"lib/types/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",

View File

@@ -2,7 +2,7 @@
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib"
"outDir": "lib/types"
},
"include": [
"src"

View File

@@ -5,17 +5,19 @@
"private": true,
"type": "module",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"types": "lib/types/index.d.ts",
"exports": {
".": {
"types": "./lib/index.d.ts",
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"
},
"files": [
"lib",
"lib/index.js",
"lib/types/**/*.d.ts",
"lib/types/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",

View File

@@ -2,7 +2,7 @@
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib"
"outDir": "lib/types"
},
"include": [
"src"

View File

@@ -5,17 +5,19 @@
"private": true,
"type": "module",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"types": "lib/types/index.d.ts",
"exports": {
".": {
"types": "./lib/index.d.ts",
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"
},
"files": [
"lib",
"lib/index.js",
"lib/types/**/*.d.ts",
"lib/types/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",

View File

@@ -2,7 +2,7 @@
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib"
"outDir": "lib/types"
},
"include": [
"src"

View File

@@ -5,17 +5,19 @@
"private": true,
"type": "module",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"types": "lib/types/index.d.ts",
"exports": {
".": {
"types": "./lib/index.d.ts",
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"
},
"files": [
"lib",
"lib/index.js",
"lib/types/**/*.d.ts",
"lib/types/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",

View File

@@ -2,7 +2,7 @@
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib"
"outDir": "lib/types"
},
"include": [
"src"

View File

@@ -5,17 +5,19 @@
"private": true,
"type": "module",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"types": "lib/types/index.d.ts",
"exports": {
".": {
"types": "./lib/index.d.ts",
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"
},
"files": [
"lib",
"lib/index.js",
"lib/types/**/*.d.ts",
"lib/types/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",

View File

@@ -2,7 +2,7 @@
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib"
"outDir": "lib/types"
},
"include": [
"src"

View File

@@ -5,17 +5,19 @@
"private": true,
"type": "module",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"types": "lib/types/index.d.ts",
"exports": {
".": {
"types": "./lib/index.d.ts",
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"
},
"files": [
"lib",
"lib/index.js",
"lib/types/**/*.d.ts",
"lib/types/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",

View File

@@ -2,7 +2,7 @@
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib"
"outDir": "lib/types"
},
"include": [
"src"

View File

@@ -5,17 +5,19 @@
"private": true,
"type": "module",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"types": "lib/types/index.d.ts",
"exports": {
".": {
"types": "./lib/index.d.ts",
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"
},
"files": [
"lib",
"lib/index.js",
"lib/types/**/*.d.ts",
"lib/types/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",

View File

@@ -2,7 +2,7 @@
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib"
"outDir": "lib/types"
},
"include": [
"src"

View File

@@ -5,17 +5,19 @@
"private": true,
"type": "module",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"types": "lib/types/index.d.ts",
"exports": {
".": {
"types": "./lib/index.d.ts",
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"
},
"files": [
"lib",
"lib/index.js",
"lib/types/**/*.d.ts",
"lib/types/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",

View File

@@ -2,7 +2,7 @@
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib"
"outDir": "lib/types"
},
"include": [
"src"

View File

@@ -5,17 +5,19 @@
"private": true,
"type": "module",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"types": "lib/types/index.d.ts",
"exports": {
".": {
"types": "./lib/index.d.ts",
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"
},
"files": [
"lib",
"lib/index.js",
"lib/types/**/*.d.ts",
"lib/types/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",

View File

@@ -2,7 +2,7 @@
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib"
"outDir": "lib/types"
},
"include": [
"src"

View File

@@ -5,17 +5,19 @@
"private": true,
"type": "module",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"types": "lib/types/index.d.ts",
"exports": {
".": {
"types": "./lib/index.d.ts",
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"
},
"files": [
"lib",
"lib/index.js",
"lib/types/**/*.d.ts",
"lib/types/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",

View File

@@ -2,7 +2,7 @@
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib"
"outDir": "lib/types"
},
"include": [
"src"

View File

@@ -5,17 +5,19 @@
"private": true,
"type": "module",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"types": "lib/types/index.d.ts",
"exports": {
".": {
"types": "./lib/index.d.ts",
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"
},
"files": [
"lib",
"lib/index.js",
"lib/types/**/*.d.ts",
"lib/types/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",

View File

@@ -2,7 +2,7 @@
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib"
"outDir": "lib/types"
},
"include": [
"src"

View File

@@ -5,17 +5,19 @@
"private": true,
"type": "module",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"types": "lib/types/index.d.ts",
"exports": {
".": {
"types": "./lib/index.d.ts",
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"
},
"files": [
"lib",
"lib/index.js",
"lib/types/**/*.d.ts",
"lib/types/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",

View File

@@ -2,7 +2,7 @@
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib"
"outDir": "lib/types"
},
"include": [
"src"

View File

@@ -5,17 +5,19 @@
"private": true,
"type": "module",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"types": "lib/types/index.d.ts",
"exports": {
".": {
"types": "./lib/index.d.ts",
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"
},
"files": [
"lib",
"lib/index.js",
"lib/types/**/*.d.ts",
"lib/types/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",

View File

@@ -2,7 +2,7 @@
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib"
"outDir": "lib/types"
},
"include": [
"src"

View File

@@ -5,17 +5,19 @@
"private": true,
"type": "module",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"types": "lib/types/index.d.ts",
"exports": {
".": {
"types": "./lib/index.d.ts",
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"
},
"files": [
"lib",
"lib/index.js",
"lib/types/**/*.d.ts",
"lib/types/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",

View File

@@ -2,7 +2,7 @@
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib"
"outDir": "lib/types"
},
"include": [
"src"

View File

@@ -5,17 +5,19 @@
"private": true,
"type": "module",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"types": "lib/types/index.d.ts",
"exports": {
".": {
"types": "./lib/index.d.ts",
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"
},
"files": [
"lib",
"lib/index.js",
"lib/types/**/*.d.ts",
"lib/types/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",

View File

@@ -2,7 +2,7 @@
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib"
"outDir": "lib/types"
},
"include": [
"src"

View File

@@ -5,17 +5,19 @@
"private": true,
"type": "module",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"types": "lib/types/index.d.ts",
"exports": {
".": {
"types": "./lib/index.d.ts",
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"
},
"files": [
"lib",
"lib/index.js",
"lib/types/**/*.d.ts",
"lib/types/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",

View File

@@ -2,7 +2,7 @@
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib"
"outDir": "lib/types"
},
"include": [
"src"

View File

@@ -5,17 +5,19 @@
"private": true,
"type": "module",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"types": "lib/types/index.d.ts",
"exports": {
".": {
"types": "./lib/index.d.ts",
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"
},
"files": [
"lib",
"lib/index.js",
"lib/types/**/*.d.ts",
"lib/types/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",

View File

@@ -2,7 +2,7 @@
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib"
"outDir": "lib/types"
},
"include": [
"src"

View File

@@ -5,17 +5,19 @@
"private": true,
"type": "module",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"types": "lib/types/index.d.ts",
"exports": {
".": {
"types": "./lib/index.d.ts",
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"
},
"files": [
"lib",
"lib/index.js",
"lib/types/**/*.d.ts",
"lib/types/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",

View File

@@ -2,7 +2,7 @@
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib"
"outDir": "lib/types"
},
"include": [
"src"

View File

@@ -5,17 +5,19 @@
"private": true,
"type": "module",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"types": "lib/types/index.d.ts",
"exports": {
".": {
"types": "./lib/index.d.ts",
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"
},
"files": [
"lib",
"lib/index.js",
"lib/types/**/*.d.ts",
"lib/types/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",

View File

@@ -2,7 +2,7 @@
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib"
"outDir": "lib/types"
},
"include": [
"src"

View File

@@ -5,17 +5,19 @@
"private": true,
"type": "module",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"types": "lib/types/index.d.ts",
"exports": {
".": {
"types": "./lib/index.d.ts",
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"
},
"files": [
"lib",
"lib/index.js",
"lib/types/**/*.d.ts",
"lib/types/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",

View File

@@ -2,7 +2,7 @@
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib"
"outDir": "lib/types"
},
"include": [
"src"

View File

@@ -5,24 +5,27 @@
"private": true,
"type": "module",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"types": "lib/types/index.d.ts",
"bin": {
"dsh-acp-agent": "lib/bin.js"
},
"exports": {
".": {
"types": "./lib/index.d.ts",
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./bin": {
"types": "./lib/bin.d.ts",
"types": "./lib/types/bin.d.ts",
"default": "./lib/bin.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"
},
"files": [
"lib",
"lib/index.js",
"lib/bin.js",
"lib/types/**/*.d.ts",
"lib/types/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",

View File

@@ -2,7 +2,7 @@
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib"
"outDir": "lib/types"
},
"include": [
"src"

View File

@@ -3,11 +3,12 @@ import { defineConfig } from 'tsdown'
/**
* acp-agent ships TWO entries: the plugin (`index`) and the CLI `bin` (`bin`),
* the latter referenced by package.json `bin`/`exports["./bin"]`. The root
* tsdown builds only `src/index.ts`, so this override adds `bin.ts`.
* Declarations come from `tsc -b` (dts: false), matching every package.
* tsdown builds only `lib/types/index.js`, so this override adds
* `lib/types/bin.js`. Declarations come from `tsc -b` (dts: false),
* matching every package.
*/
export default defineConfig({
entry: ['src/index.ts', 'src/bin.ts'],
entry: ['lib/types/index.js', 'lib/types/bin.js'],
outDir: 'lib',
format: ['esm'],
platform: 'node',

View File

@@ -5,17 +5,19 @@
"private": true,
"type": "module",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"types": "lib/types/index.d.ts",
"exports": {
".": {
"types": "./lib/index.d.ts",
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"
},
"files": [
"lib",
"lib/index.js",
"lib/types/**/*.d.ts",
"lib/types/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",

View File

@@ -2,7 +2,7 @@
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib"
"outDir": "lib/types"
},
"include": [
"src"

View File

@@ -5,24 +5,27 @@
"private": true,
"type": "module",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"types": "lib/types/index.d.ts",
"bin": {
"dsh-stdio-agent": "lib/bin.js"
},
"exports": {
".": {
"types": "./lib/index.d.ts",
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./bin": {
"types": "./lib/bin.d.ts",
"types": "./lib/types/bin.d.ts",
"default": "./lib/bin.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"
},
"files": [
"lib",
"lib/index.js",
"lib/bin.js",
"lib/types/**/*.d.ts",
"lib/types/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",

View File

@@ -2,7 +2,7 @@
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib"
"outDir": "lib/types"
},
"include": [
"src"

View File

@@ -3,11 +3,12 @@ import { defineConfig } from 'tsdown'
/**
* stdio-agent ships TWO entries: the plugin (`index`) and the CLI `bin`
* (`bin`), the latter referenced by package.json `bin`/`exports["./bin"]`.
* The root tsdown builds only `src/index.ts`, so this override adds `bin.ts`.
* Declarations come from `tsc -b` (dts: false), matching every package.
* The root tsdown builds only `lib/types/index.js`, so this override adds
* `lib/types/bin.js`. Declarations come from `tsc -b` (dts: false),
* matching every package.
*/
export default defineConfig({
entry: ['src/index.ts', 'src/bin.ts'],
entry: ['lib/types/index.js', 'lib/types/bin.js'],
outDir: 'lib',
format: ['esm'],
platform: 'node',

View File

@@ -5,17 +5,19 @@
"private": true,
"type": "module",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"types": "lib/types/index.d.ts",
"exports": {
".": {
"types": "./lib/index.d.ts",
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"
},
"files": [
"lib",
"lib/index.js",
"lib/types/**/*.d.ts",
"lib/types/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",

View File

@@ -2,7 +2,7 @@
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib"
"outDir": "lib/types"
},
"include": [
"src"

View File

@@ -33,6 +33,18 @@ interface PackageManifest {
version?: string
private?: boolean
type?: string
main?: string
types?: string
bin?: string | Record<string, string>
exports?: Record<
string,
| {
types?: string
default?: string
}
| undefined
>
files?: string[]
peerDependencies?: Record<string, string>
devDependencies?: Record<string, string>
}
@@ -73,6 +85,29 @@ function workspaceManifests(): WorkspaceManifest[] {
return manifests
}
const dshPackageFiles = [
'lib/index.js',
'lib/types/**/*.d.ts',
'lib/types/**/*.d.ts.map',
'src',
] as const
const dshBinPackageFiles = [
'lib/index.js',
'lib/bin.js',
'lib/types/**/*.d.ts',
'lib/types/**/*.d.ts.map',
'src',
] as const
function sameStringList(actual: readonly string[] | undefined, expected: readonly string[]): boolean {
return !!actual && actual.length === expected.length && actual.every((value, index) => value === expected[index])
}
function expectedDshPackageFiles(manifest: PackageManifest): readonly string[] {
return manifest.bin ? dshBinPackageFiles : dshPackageFiles
}
function checkWorkspace({ dir, manifest }: WorkspaceManifest): string[] {
const errors: string[] = []
const label = manifest.name ?? dir
@@ -100,6 +135,22 @@ function checkWorkspace({ dir, manifest }: WorkspaceManifest): string[] {
if (manifest.type !== 'module') {
errors.push(`${label}: package.json must set "type": "module"`)
}
if (manifest.main !== 'lib/index.js') {
errors.push(`${label}: package.json must set "main": "lib/index.js"`)
}
if (manifest.types !== 'lib/types/index.d.ts') {
errors.push(`${label}: package.json must set "types": "lib/types/index.d.ts"`)
}
if (manifest.exports?.['.']?.types !== './lib/types/index.d.ts') {
errors.push(`${label}: package.json exports["."].types must be "./lib/types/index.d.ts"`)
}
if (manifest.exports?.['.']?.default !== './lib/index.js') {
errors.push(`${label}: package.json exports["."].default must be "./lib/index.js"`)
}
const expectedFiles = expectedDshPackageFiles(manifest)
if (!sameStringList(manifest.files, expectedFiles)) {
errors.push(`${label}: package.json files must be ${JSON.stringify(expectedFiles)}`)
}
}
return errors.map(error => `${relative(root, join(root, dir, 'package.json'))}: ${error}`)

View File

@@ -3,12 +3,12 @@
* Markdown so documentation can't drift from the API it documents.
*
* Every ```ts block in README.md, docs/** and packages/* /README.md is
* extracted to a temp file and compiled with `tsc --noEmit` against the
* workspace sources (resolved through the same `paths` map vitest uses, so no
* build is required first). A block that is a deliberate sketch rather than
* compilable code opts out with an explicit ` ```ts ignore-check ` info string
* — the opt-out is visible in the source, and this script reports the ratio so
* the escape hatch can't quietly become the norm. A third info string,
* extracted to a temp typecheck project and compiled against the workspace
* sources through the same project-reference boundaries used by repo
* typecheck. A block that is a deliberate sketch rather than compilable code
* opts out with an explicit ` ```ts ignore-check ` info string — the opt-out
* is visible in the source, and this script reports the ratio so the escape
* hatch can't quietly become the norm. A third info string,
* doc-typecheck.ts recognizes two more fence variants and skips both (each is a
* separately-checked category, not an unchecked sketch, so neither counts in the
* opt-out ratio): ` ```ts type-equiv ` is a verbatim source-type paste that
@@ -88,16 +88,9 @@ function extractBlocks(absPath: string): Block[] {
return blocks
}
/**
* Read the workspace `paths` map from tsconfig.typecheck.json (JSONC). This map
* resolves vendored packages to their BUILT declarations (`lib`) and harness
* packages to source (`src`) — the same resolution `pnpm run lint`/`typecheck` use.
* Resolving vendor to `lib` (not `src`) is essential: otherwise tsc type-checks
* raw vendor source and floods the run with unrelated errors. Requires the
* vendor `lib/` to exist (a fresh clone runs `pnpm run build` first; CI does too).
*/
function workspacePaths(): Record<string, string[]> {
const file = join(root, 'tsconfig.typecheck.json')
/** Reuse the repo typecheck graph references from a temp project one directory below root. */
function workspaceReferences(): { path: string }[] {
const file = join(root, 'tsconfig.json')
// Parse with TypeScript's own JSONC reader, not a hand-rolled comment strip:
// a regex strip mistakes the `/*/` in a wildcard path candidate
// (`./packages/core/*/src`) for a block comment and corrupts the map.
@@ -106,27 +99,24 @@ function workspacePaths(): Record<string, string[]> {
throw new Error(`doc-typecheck: cannot read ${file}: ${ts.flattenDiagnosticMessageText(result.error.messageText, '\n')}`)
}
// `config` is typed `any` by the TS API; narrow it to the one field we read.
const config = result.config as { compilerOptions: { paths: Record<string, string[]> } }
return config.compilerOptions.paths
const { references } = result.config as { compilerOptions: { paths: Record<string, string[]> }; references: { path: string }[] }
return references.map(({ path }) => {
const relativeToTemp = path.startsWith('./') ? `../${path.slice(2)}` : `../${path}`
return { path: relativeToTemp }
})
}
/** The standalone tsconfig for the temp project (copies base resolution, no
* composite/declaration settings that would fight `--noEmit`). */
/** The standalone tsconfig for the temp typecheck project. */
function tempTsconfig(): string {
return JSON.stringify({
extends: '../tsconfig.json',
compilerOptions: {
target: 'es2024',
module: 'esnext',
moduleResolution: 'bundler',
allowImportingTsExtensions: true,
strict: true,
noEmit: true,
skipLibCheck: true,
types: ['node'],
baseUrl: root,
ignoreDeprecations: '6.0',
paths: workspacePaths(),
noUnusedLocals: false,
noUnusedParameters: false,
tsBuildInfoFile: './tsconfig.tsbuildinfo',
},
include: ['block-*.ts'],
references: workspaceReferences(),
})
}
@@ -164,11 +154,12 @@ try {
})
try {
execFileSync('node_modules/.bin/tsc', ['-p', join(tmp, 'tsconfig.json')], { cwd: root, stdio: 'pipe' })
execFileSync('node_modules/.bin/tsc', ['-b', join(tmp, 'tsconfig.json')], { cwd: root, stdio: 'pipe' })
} catch (error: unknown) {
const out = (error as { stdout?: Buffer }).stdout?.toString() ?? ''
const failed = error as { stdout?: Buffer; stderr?: Buffer }
const out = `${failed.stdout?.toString() ?? ''}${failed.stderr?.toString() ?? ''}`
// Rewrite "block-N.ts(line,col)" to the real "file:fenceLine" for triage.
const remapped = out.replace(/block-(\d+)\.ts\((\d+),(\d+)\)/g, (_m, idx: string, ln: string, col: string) => {
const remapped = out.replace(/(?:[^\s:()]*[/\\])?block-(\d+)\.ts\((\d+),(\d+)\)/g, (_m, idx: string, ln: string, col: string) => {
const block = fileForBlock.get(`block-${idx}.ts`)
if (!block) return `block-${idx}.ts(${ln},${col})`
return `${block.file} (block at line ${block.line}, +${ln}:${col})`

View File

@@ -0,0 +1,160 @@
/**
* Verify that built package declarations are consumable by a standard external
* TypeScript ESM project using NodeNext resolution.
*
* Run after `pnpm run build` has emitted declaration files under package
* `lib/types` directories.
*/
import { execFileSync } from 'node:child_process'
import { existsSync, globSync, mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from 'node:fs'
import { dirname, resolve } from 'node:path'
const root = resolve(import.meta.dirname, '..')
interface ExportTarget {
types?: string
}
interface PackageManifest {
name?: string
types?: string
exports?: Record<string, ExportTarget | string | null>
}
interface WorkspacePackage {
dir: string
name: string
manifest: PackageManifest
}
function readPackage(path: string): WorkspacePackage | null {
const manifest = JSON.parse(readFileSync(path, 'utf8')) as PackageManifest
if (!manifest.name) return null
return { dir: dirname(path), name: manifest.name, manifest }
}
function workspacePackages(): WorkspacePackage[] {
return [
...globSync('vendor/*/package.json', { cwd: root }),
...globSync('packages/*/*/package.json', { cwd: root }),
]
.map(path => readPackage(resolve(root, path)))
.filter(pkg => pkg !== null)
.sort((a, b) => a.name.localeCompare(b.name))
}
const declarationSpecifierPattern = /(?:from\s*|import\s*\(\s*|import\s+|declare\s+module\s*)["'](\.{0,2}(?:\/[^"']*)?)["']/g
const hasExtension = /\.[^/.]+$/
function relativeSpecifiersMissingExtensions(): string[] {
const errors: string[] = []
const files = [
...globSync('vendor/*/lib/types/**/*.d.ts', { cwd: root }),
...globSync('packages/*/*/lib/types/**/*.d.ts', { cwd: root }),
].sort()
for (const file of files) {
const text = readFileSync(resolve(root, file), 'utf8')
for (const match of text.matchAll(declarationSpecifierPattern)) {
const specifier = match[1]
if (!specifier) continue
const isRelative = specifier === '.' || specifier.startsWith('./') || specifier.startsWith('../')
if (isRelative && !hasExtension.test(specifier)) errors.push(`${file}: ${specifier}`)
}
}
return errors
}
function publicSpecifiers(pkg: WorkspacePackage): string[] {
const specifiers = new Set<string>()
if (pkg.manifest.types) specifiers.add(pkg.name)
for (const [key, target] of Object.entries(pkg.manifest.exports ?? {})) {
if (key.includes('*') || key === './package.json') continue
if (typeof target !== 'object' || target === null || !target.types) continue
specifiers.add(key === '.' ? pkg.name : `${pkg.name}/${key.slice(2)}`)
}
return [...specifiers].sort()
}
function linkPackage(pkg: WorkspacePackage, nodeModules: string): void {
const parts = pkg.name.split('/')
const link = resolve(nodeModules, ...parts)
mkdirSync(dirname(link), { recursive: true })
symlinkSync(pkg.dir, link, 'dir')
}
const packages = workspacePackages()
const badSpecifiers = relativeSpecifiersMissingExtensions()
if (badSpecifiers.length > 0) {
console.error('verify-node-next-types: declaration files still contain relative specifiers without file extensions.')
console.error(badSpecifiers.join('\n'))
process.exit(1)
}
const missingOutputs = packages
.filter(pkg => pkg.manifest.types && !existsSync(resolve(pkg.dir, pkg.manifest.types)))
.map(pkg => `${pkg.name}: missing ${pkg.manifest.types}`)
if (missingOutputs.length > 0) {
console.error('verify-node-next-types: build outputs are missing; run `pnpm run build` first.')
console.error(missingOutputs.join('\n'))
process.exit(1)
}
const tmp = mkdtempSync(resolve(root, '.node-next-types-'))
let failed = false
try {
const nodeModules = resolve(tmp, 'node_modules')
mkdirSync(nodeModules, { recursive: true })
for (const pkg of packages) linkPackage(pkg, nodeModules)
const rootTypes = resolve(root, 'node_modules/@types/node')
if (existsSync(rootTypes)) {
const typesDir = resolve(nodeModules, '@types')
mkdirSync(typesDir, { recursive: true })
symlinkSync(rootTypes, resolve(typesDir, 'node'), 'dir')
}
writeFileSync(resolve(tmp, 'package.json'), `${JSON.stringify({ type: 'module', private: true }, null, 2)}\n`)
writeFileSync(resolve(tmp, 'tsconfig.json'), `${JSON.stringify({
compilerOptions: {
target: 'es2024',
module: 'NodeNext',
moduleResolution: 'NodeNext',
strict: true,
// Third-party SDK declarations can have their own lib-check noise under a
// symlinked temp install. The explicit scan above owns our regression:
// relative specifiers without file extensions in built declarations.
skipLibCheck: true,
preserveSymlinks: true,
noEmit: true,
types: ['node'],
},
include: ['index.ts'],
}, null, 2)}\n`)
const imports = packages.flatMap(publicSpecifiers)
.map((specifier, index) => `import * as mod${index} from ${JSON.stringify(specifier)};\nvoid mod${index};`)
.join('\n')
writeFileSync(resolve(tmp, 'index.ts'), `${imports}\n`)
execFileSync(resolve(root, 'node_modules/.bin/tsc'), ['-p', resolve(tmp, 'tsconfig.json'), '--pretty', 'false'], {
cwd: root,
stdio: 'pipe',
})
console.log(`verify-node-next-types: ${packages.length} workspace package declaration surface(s) compile under NodeNext.`)
} catch (error: unknown) {
failed = true
const output = error as { stdout?: Buffer; stderr?: Buffer }
console.error('verify-node-next-types: NodeNext consumer typecheck failed.\n')
console.error(`${output.stdout?.toString() ?? ''}${output.stderr?.toString() ?? ''}`)
} finally {
rmSync(tmp, { recursive: true, force: true })
}
if (failed) process.exit(1)

View File

@@ -4,12 +4,14 @@
"module": "esnext",
"moduleResolution": "bundler",
"declaration": true,
"emitDeclarationOnly": true,
"sourceMap": true,
"declarationMap": true,
"composite": true,
"incremental": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowImportingTsExtensions": true,
"rewriteRelativeImportExtensions": true,
"verbatimModuleSyntax": false,
"strict": true,
"noUncheckedIndexedAccess": true,
@@ -19,11 +21,9 @@
"noUnusedLocals": true,
"noUnusedParameters": true,
"types": ["node"],
// Source-level resolution for the build graph: without this, a fresh
// checkout's first `tsc -b` resolves sibling vendor plugins through their
// package.json types (vendor/*/lib/*.d.ts) which don't exist yet — TS2307
// until a second run. Derived configs that want lib resolution
// (tsconfig.typecheck.json) override this map wholesale.
// Source-level resolution for every repo-local graph. Project references,
// not declaration path aliases, keep each package/vendor source compiled
// under its own tsconfig boundary.
"paths": {
"cordis": ["./vendor/cordis/src"],
"cosmokit": ["./vendor/cosmokit/src"],

View File

@@ -1,4 +1,50 @@
{
"extends": "./tsconfig.base.json",
"files": []
"compilerOptions": {
"noEmit": true,
"rewriteRelativeImportExtensions": false
},
"include": [
"examples/*/src/**/*.ts",
"examples/*/start.ts",
"examples/*/tests/**/*.ts",
"packages/*/*/tests/**/*.ts",
"scripts/**/*.ts"
],
"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/llm/llm" },
{ "path": "./packages/core/session" },
{ "path": "./packages/session-persistence/session-persistence" },
{ "path": "./packages/session-persistence/session-persistence-jsonl" },
{ "path": "./packages/session-persistence/session-persistence-sqlite" },
{ "path": "./packages/core/system-prompt" },
{ "path": "./packages/core/agent" },
{ "path": "./packages/core/tools" },
{ "path": "./packages/core/agent-loop" },
{ "path": "./packages/core/agent-core" },
{ "path": "./packages/bash/bash" },
{ "path": "./packages/llm/llm-deepseek" },
{ "path": "./packages/llm/llm-pi-ai" },
{ "path": "./packages/bash/bash-local" },
{ "path": "./packages/bash/tool-bash" },
{ "path": "./packages/support/invariants" },
{ "path": "./packages/ui/acp" },
{ "path": "./packages/ui/acp-agent" },
{ "path": "./packages/ui/stdio-agent" },
{ "path": "./packages/support/ui-stdio" },
{ "path": "./packages/support/llm-replay" },
{ "path": "./packages/subagent/subagent" },
{ "path": "./packages/support/subagent-mock" },
{ "path": "./packages/subagent/tool-subagent" }
]
}

View File

@@ -1,10 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": true,
"emitDeclarationOnly": false,
"composite": false,
"types": ["node"]
},
"include": ["vendor/*/src", "packages/*/*/src", "packages/*/*/tests", "examples"]
}

View File

@@ -1,32 +0,0 @@
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"noEmit": true,
"emitDeclarationOnly": false,
"composite": false,
"incremental": false,
"types": ["node"],
"paths": {
"cordis": ["./vendor/cordis/lib"],
"cosmokit": ["./vendor/cosmokit/lib"],
"schemastery": ["./vendor/schemastery/lib"],
"@cordisjs/plugin-loader": ["./vendor/loader/lib"],
"@cordisjs/plugin-include": ["./vendor/include/lib"],
"@cordisjs/plugin-group": ["./vendor/group/lib"],
"@cordisjs/plugin-timer": ["./vendor/timer/lib"],
"@cordisjs/plugin-hmr": ["./vendor/hmr/lib"],
"@cordisjs/plugin-logger-console": ["./vendor/logger-console/lib/shared"],
"@deepseek-ai/dsh-*": [
"./packages/core/*/src",
"./packages/llm/*/src",
"./packages/bash/*/src",
"./packages/subagent/*/src",
"./packages/session-persistence/*/src",
"./packages/ui/*/src",
"./packages/util/*/src",
"./packages/support/*/src"
]
}
},
"include": ["packages/*/*/src", "packages/*/*/tests", "examples", "scripts"]
}

View File

@@ -2,9 +2,9 @@ import { defineConfig } from 'tsdown'
/**
* JS bundling for all workspace packages (vendor and the packages hierarchy).
* Declarations are NOT produced here — `tsc -b tsconfig.build.json` owns
* .d.ts output (composite project references); hence `dts: false` and
* `clean: false` (lib/ already holds tsc's declarations).
* TypeScript source is compiled first by `tsc -b tsconfig.build.json`; tsdown
* reads only the emitted JS under lib/types and writes lib/index.* runtime
* bundles. Declarations are NOT produced here, hence `dts: false`.
*
* Per-package shape overrides live in `<package>/tsdown.config.ts`
* (schemastery: dual ESM+CJS; logger-console: extra browser entry).
@@ -14,7 +14,7 @@ export default defineConfig({
// package.json), but only vendor and the packages hierarchy are pnpm
// workspaces.
workspace: ['vendor/*', 'packages/*/*'],
entry: ['src/index.ts'],
entry: ['lib/types/index.js'],
outDir: 'lib',
format: ['esm'],
platform: 'node',

7
vendor/README.md vendored
View File

@@ -31,9 +31,10 @@ Intentionally **not** vendored (verified unused by this set): `reggol`, `@cordis
Keep this log exhaustive — every divergence from upstream must be listed.
1. **`hmr/src/index.ts`**: removed the `./locales/en-US.yml` / `./locales/zh-CN.yml` imports, the `.i18n({...})` call on the `Config` schema, and the `src/locales/` directory. Rationale: those imports require a runtime YAML loader hook (`@cordisjs/unyaml`) that we do not vendor; the i18n texts only localize config descriptions.
2. **All `package.json` files**: regenerated — added `private: true`, added `src` to `files` and a `./src/*` export where missing, removed upstream `devDependencies`/`scripts`/`repository` fields. Dependency and peer-dependency ranges preserved, except `hmr` declares `esbuild` as a direct dev dependency because its source imports the `BuildFailure` type and pnpm's strict workspace resolution requires the owner package to name that dependency.
3. **All `tsconfig.json` files**: regenerated to extend the repo-root `tsconfig.base.json` and declare project references.
4. **`schemastery/tsdown.config.ts` and `logger-console/tsdown.config.ts`**: ours, not upstream files — per-package build-shape overrides (dual ESM+CJS output; separate node/browser entries) for the repo-root tsdown build. Like the regenerated tsconfigs, they are not part of the upstream sync surface.
2. **All `package.json` files**: regenerated — added `private: true`, added precise `files` entries for bundled runtime files and `lib/types/**/*.d.ts` / `.d.ts.map`, preserved `src` in `files` only for packages whose previous file list already shipped it, added a `./src/*` export where missing, pointed declaration metadata at `lib/types`, and removed upstream `devDependencies`/`scripts`/`repository` fields. Dependency and peer-dependency ranges preserved, except `hmr` declares `esbuild` as a direct dev dependency because its source imports the `BuildFailure` type and pnpm's strict workspace resolution requires the owner package to name that dependency.
3. **All `tsconfig.json` files**: regenerated to extend the repo-root `tsconfig.base.json`, emit TypeScript intermediates to `lib/types`, and declare project references.
4. **Vendored TypeScript source internal specifiers**: changed local relative imports/exports from upstream's specifier shape to explicit `.ts` specifiers so TypeScript rewrites emitted JS to `.js` while declarations keep explicit, NodeNext-safe `.ts` specifiers. This includes `loader/src/config/isolate.ts` using `declare module './entry.ts'`.
5. **`schemastery/tsdown.config.ts` and `logger-console/tsdown.config.ts`**: ours, not upstream files — per-package build-shape overrides (dual ESM+CJS output; separate node/browser entries) for the repo-root tsdown build. They read the JS emitted under `lib/types` and then write the publish runtime entries under `lib/`. Like the regenerated tsconfigs, they are not part of the upstream sync surface.
## Sync procedure

View File

@@ -6,18 +6,20 @@
"sideEffects": false,
"type": "module",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"types": "lib/types/index.d.ts",
"bin": "bin.js",
"exports": {
".": {
"types": "./lib/index.d.ts",
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"
},
"files": [
"lib",
"lib/index.js",
"lib/types/**/*.d.ts",
"lib/types/**/*.d.ts.map",
"bin.js"
],
"author": "Shigma <shigma10826@gmail.com>",

View File

@@ -1,10 +1,10 @@
import { Dict } from 'cosmokit'
import { EventsService } from './events'
import { LoggerService } from './logger'
import { ReflectService } from './reflect'
import { InjectKey, RegistryService } from './registry'
import { getTraceable, symbols } from './utils'
import { Fiber } from './fiber'
import { EventsService } from './events.ts'
import { LoggerService } from './logger.ts'
import { ReflectService } from './reflect.ts'
import { InjectKey, RegistryService } from './registry.ts'
import { getTraceable, symbols } from './utils.ts'
import { Fiber } from './fiber.ts'
/**
* Public shape of a Cordis context.

View File

@@ -1,7 +1,7 @@
import { defineProperty, Promisify } from 'cosmokit'
import { Context } from './context'
import { Fiber, FiberState } from './fiber'
import { DisposableList, symbols } from './utils'
import { Context } from './context.ts'
import { Fiber, FiberState } from './fiber.ts'
import { DisposableList, symbols } from './utils.ts'
/** Return whether an event result should stop a bail-style dispatch. */
export function isBailed(value: any) {
@@ -25,7 +25,7 @@ export type ThisType<F> = F extends (this: infer T, ...args: any) => any ? T : n
*/
export type DispatchMode = 'emit' | 'parallel' | 'serial' | 'bail' | 'waterfall'
declare module './context' {
declare module './context.ts' {
export interface Context {
/* eslint-disable max-len */
parallel<K extends keyof Events>(name: K, ...args: Parameters<Events[K]>): Promise<void>

View File

@@ -1,11 +1,11 @@
import { Awaitable, defineProperty, Dict, isNullable } from 'cosmokit'
import { Context } from './context'
import { Plugin } from './registry'
import { buildOuterStack, composeError, DisposableList, getTraceable, isConstructor, isObject, symbols } from './utils'
import { Impl } from './reflect'
import { Context } from './context.ts'
import { Plugin } from './registry.ts'
import { buildOuterStack, composeError, DisposableList, getTraceable, isConstructor, isObject, symbols } from './utils.ts'
import { Impl } from './reflect.ts'
import { StandardSchemaV1 } from '@standard-schema/spec'
declare module './context' {
declare module './context.ts' {
export interface Context extends Pick<Fiber, 'effect'> {
fiber: Fiber
}

View File

@@ -1,14 +1,14 @@
/** Core context type and root context implementation. */
export * from './context'
export * from './context.ts'
/** Event bus, dispatch modes, and event augmentation types. */
export * from './events'
export * from './events.ts'
/** Plugin fiber lifecycle, effects, and config validation helpers. */
export * from './fiber'
export * from './fiber.ts'
/** Logger facade, logger service, message, exporter, and formatting types. */
export * from './logger'
export * from './logger.ts'
/** Plugin registry, dependency injection, and plugin entrypoint types. */
export * from './registry'
export * from './registry.ts'
/** Base service class and service lifecycle symbols. */
export * from './service'
export * from './service.ts'
/** Shared internal helpers used by context, services, and plugin fibers. */
export * from './utils'
export * from './utils.ts'

View File

@@ -1,9 +1,9 @@
import { defineProperty, hyphenate } from 'cosmokit'
import { Context } from './context'
import { Fiber } from './fiber'
import { createCallable, joinPrototype, symbols, Tracker } from './utils'
import { Context } from './context.ts'
import { Fiber } from './fiber.ts'
import { createCallable, joinPrototype, symbols, Tracker } from './utils.ts'
declare module './context' {
declare module './context.ts' {
interface Intercept {
logger: LoggerService.Intercept
}

View File

@@ -1,9 +1,9 @@
import { defineProperty, Dict, isNullable } from 'cosmokit'
import { Context } from './context'
import { getTraceable, symbols, withProps } from './utils'
import { Fiber, FiberState } from './fiber'
import { Context } from './context.ts'
import { getTraceable, symbols, withProps } from './utils.ts'
import { Fiber, FiberState } from './fiber.ts'
declare module './context' {
declare module './context.ts' {
interface Context {
get<K extends string & keyof this>(name: K, strict?: boolean): undefined | this[K]
get(name: string, strict?: boolean): any

View File

@@ -1,8 +1,8 @@
import { defineProperty, Dict } from 'cosmokit'
import { StandardSchemaV1 } from '@standard-schema/spec'
import { Context } from './context'
import { Fiber } from './fiber'
import { buildOuterStack, DisposableList, symbols, withProps } from './utils'
import { Context } from './context.ts'
import { Fiber } from './fiber.ts'
import { buildOuterStack, DisposableList, symbols, withProps } from './utils.ts'
function isApplicable(object: Plugin) {
return object && typeof object === 'object' && typeof object.apply === 'function'
@@ -140,7 +140,7 @@ type GetPluginConfig<P> =
? S
: GetPluginParameters<P>[0]
declare module './context' {
declare module './context.ts' {
export interface Context {
inject(deps: Inject, callback: Plugin.Function<void>): Fiber & PromiseLike<Fiber>
plugin<P extends Plugin>(plugin: P, ...args: Spread<GetPluginConfig<P>>): Fiber & PromiseLike<Fiber>

View File

@@ -1,6 +1,6 @@
import { defineProperty } from 'cosmokit'
import { Context } from './context'
import { createCallable, joinPrototype, symbols, Tracker } from './utils'
import { Context } from './context.ts'
import { createCallable, joinPrototype, symbols, Tracker } from './utils.ts'
/**
* Base class for services that expose a named API on `ctx`.

View File

@@ -1,5 +1,5 @@
import { defineProperty } from 'cosmokit'
import type { Context, Service } from '.'
import type { Context, Service } from './index.ts'
/** Ordered collection of disposable values with O(1) deletion by value. */
export class DisposableList<T extends WeakKey> {

View File

@@ -2,7 +2,7 @@
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib",
"outDir": "lib/types",
"noImplicitAny": false,
"noImplicitThis": false,
"strictFunctionTypes": false,

View File

@@ -5,17 +5,19 @@
"private": true,
"type": "module",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"types": "lib/types/index.d.ts",
"exports": {
".": {
"types": "./lib/index.d.ts",
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"
},
"files": [
"lib",
"lib/index.js",
"lib/types/**/*.d.ts",
"lib/types/**/*.d.ts.map",
"src"
],
"author": "Shigma <shigma10826@gmail.com>",

View File

@@ -1,4 +1,4 @@
import { isNullable } from './misc'
import { isNullable } from './misc.ts'
/** Return true when every item in `array2` is present in `array1`. */
export function contain(array1: readonly any[], array2: readonly any[]) {

View File

@@ -1,10 +1,10 @@
/** Array set and normalization helpers. */
export * from './array'
export * from './array.ts'
/** Runtime type, binary, clone, and equality helpers. */
export * from './types'
export * from './types.ts'
/** Shared utility types and object helpers. */
export * from './misc'
export * from './misc.ts'
/** String case, path, and property formatting helpers. */
export * from './string'
export * from './string.ts'
/** Time constants, parsing, and formatting helpers. */
export * from './time'
export * from './time.ts'

View File

@@ -1,4 +1,4 @@
import { isNullable } from './misc'
import { isNullable } from './misc.ts'
type GlobalConstructorNames = keyof {
[K in keyof typeof globalThis as typeof globalThis[K] extends abstract new (...args: any) => any ? K : never]: K

View File

@@ -2,7 +2,7 @@
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib",
"outDir": "lib/types",
"noUncheckedIndexedAccess": false,
"exactOptionalPropertyTypes": false,
"noImplicitOverride": false,

View File

@@ -5,17 +5,19 @@
"private": true,
"type": "module",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"types": "lib/types/index.d.ts",
"exports": {
".": {
"types": "./lib/index.d.ts",
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"
},
"files": [
"lib",
"lib/index.js",
"lib/types/**/*.d.ts",
"lib/types/**/*.d.ts.map",
"src"
],
"author": "Shigma <shigma10826@gmail.com>",

View File

@@ -2,7 +2,7 @@
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib",
"outDir": "lib/types",
"noUncheckedIndexedAccess": false,
"exactOptionalPropertyTypes": false,
"noImplicitOverride": false,

View File

@@ -5,17 +5,19 @@
"private": true,
"type": "module",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"types": "lib/types/index.d.ts",
"exports": {
".": {
"types": "./lib/index.d.ts",
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"
},
"files": [
"lib",
"lib/index.js",
"lib/types/**/*.d.ts",
"lib/types/**/*.d.ts.map",
"src"
],
"author": "Shigma <shigma10826@gmail.com>",

View File

@@ -2,7 +2,7 @@
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib",
"outDir": "lib/types",
"noUncheckedIndexedAccess": false,
"exactOptionalPropertyTypes": false,
"noImplicitOverride": false,

View File

@@ -5,17 +5,19 @@
"private": true,
"type": "module",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"types": "lib/types/index.d.ts",
"exports": {
".": {
"types": "./lib/index.d.ts",
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"
},
"files": [
"lib",
"lib/index.js",
"lib/types/**/*.d.ts",
"lib/types/**/*.d.ts.map",
"src"
],
"author": "Shigma <shigma10826@gmail.com>",

View File

@@ -2,7 +2,7 @@
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib",
"outDir": "lib/types",
"noImplicitAny": false,
"noUncheckedIndexedAccess": false,
"exactOptionalPropertyTypes": false,

View File

@@ -5,17 +5,19 @@
"private": true,
"type": "module",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"types": "lib/types/index.d.ts",
"exports": {
".": {
"types": "./lib/index.d.ts",
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"
},
"files": [
"lib",
"lib/index.js",
"lib/types/**/*.d.ts",
"lib/types/**/*.d.ts.map",
"src"
],
"author": "Shigma <shigma10826@gmail.com>",

View File

@@ -2,7 +2,7 @@
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib",
"outDir": "lib/types",
"noImplicitAny": false,
"noUncheckedIndexedAccess": false,
"exactOptionalPropertyTypes": false,

View File

@@ -5,10 +5,10 @@
"private": true,
"type": "module",
"main": "lib/index.js",
"types": "lib/shared.d.ts",
"types": "lib/types/shared.d.ts",
"exports": {
".": {
"types": "./lib/shared.d.ts",
"types": "./lib/types/shared.d.ts",
"node": "./lib/index.js",
"default": "./lib/browser.js"
},
@@ -16,7 +16,10 @@
"./package.json": "./package.json"
},
"files": [
"lib",
"lib/index.js",
"lib/browser.js",
"lib/types/**/*.d.ts",
"lib/types/**/*.d.ts.map",
"src"
],
"author": "Shigma <shigma10826@gmail.com>",

Some files were not shown because too many files have changed in this diff Show More