docs: unwrap hard-wrapped Markdown to one line per paragraph
Hard line breaks mid-paragraph make docs harder to edit and diff — a one-word change reflows and re-diffs the whole paragraph. Reflow all tracked non-vendor Markdown (plus vendor/AGENTS.md) so each prose paragraph is a single line; soft-wrapping is the editor's job. Fenced code, tables, and list structure are preserved (wrapped list items fold to one line per bullet). Documents the convention in AGENTS.md.
This commit is contained in:
@@ -4,31 +4,17 @@ Status: accepted (2026-06-11)
|
||||
|
||||
## Context
|
||||
|
||||
DeepSeek Code is built on the Cordis framework. Cordis core was at 4.0.0-rc.6
|
||||
(a release candidate) when this repo started; the harness depends on framework
|
||||
internals (fiber lifecycle, effect disposal, waterfall dispatch) whose exact
|
||||
behavior matters to the agent loop's correctness guarantees.
|
||||
DeepSeek Code is built on the Cordis framework. Cordis core was at 4.0.0-rc.6 (a release candidate) when this repo started; the harness depends on framework internals (fiber lifecycle, effect disposal, waterfall dispatch) whose exact behavior matters to the agent loop's correctness guarantees.
|
||||
|
||||
## Decision
|
||||
|
||||
Copy the needed Cordis packages (core, loader, include, group, timer, hmr,
|
||||
logger-console) and the cordiverse foundation libraries (cosmokit,
|
||||
schemastery) into `vendor/` as source, flattened, keeping their original npm
|
||||
names so workspace resolution is transparent. Truly third-party dependencies
|
||||
(js-yaml, chokidar, @standard-schema/spec, …) stay on npm.
|
||||
Copy the needed Cordis packages (core, loader, include, group, timer, hmr, logger-console) and the cordiverse foundation libraries (cosmokit, schemastery) into `vendor/` as source, flattened, keeping their original npm names so workspace resolution is transparent. Truly third-party dependencies (js-yaml, chokidar, @standard-schema/spec, …) stay on npm.
|
||||
|
||||
`vendor/README.md` is the manifest: upstream repo + commit SHA per package and
|
||||
an exhaustive local-modification log. A pre-commit guard
|
||||
(`scripts/check-vendor-manifest.sh`) rejects vendored-source changes that
|
||||
don't update the manifest in the same commit.
|
||||
`vendor/README.md` is the manifest: upstream repo + commit SHA per package and an exhaustive local-modification log. A pre-commit guard (`scripts/check-vendor-manifest.sh`) rejects vendored-source changes that don't update the manifest in the same commit.
|
||||
|
||||
## Consequences
|
||||
|
||||
- The harness fully owns its framework layer: auditable, patchable, pinned —
|
||||
an RC upstream can't break us, and we can fix framework bugs in-tree.
|
||||
- Upstream sync is manual (documented procedure in the manifest). The
|
||||
modification log keeps the diff surface known.
|
||||
- Vendored packages keep upstream code style; lint/strictness gates exclude
|
||||
them (their tsconfigs relax our newer compiler flags locally).
|
||||
- One local patch exists from day one: hmr's locale-YAML imports removed (the
|
||||
runtime YAML import hook isn't vendored).
|
||||
- The harness fully owns its framework layer: auditable, patchable, pinned — an RC upstream can't break us, and we can fix framework bugs in-tree.
|
||||
- Upstream sync is manual (documented procedure in the manifest). The modification log keeps the diff surface known.
|
||||
- Vendored packages keep upstream code style; lint/strictness gates exclude them (their tsconfigs relax our newer compiler flags locally).
|
||||
- One local patch exists from day one: hmr's locale-YAML imports removed (the runtime YAML import hook isn't vendored).
|
||||
|
||||
@@ -4,35 +4,21 @@ Status: accepted (2026-06-11)
|
||||
|
||||
## Context
|
||||
|
||||
The product principle (see the 微内核Harness实现思路 design doc) is
|
||||
"everything is a plugin": hooks, /goal, /loop, dynamic workflows, compaction,
|
||||
sandboxing, permissions, UI, persistence, MCP, skills must all be writable as
|
||||
plugins without modifying the core. Candidate mechanisms considered: a
|
||||
purpose-built middleware stack (koa-compose style), an explicit phase state
|
||||
machine plugins can insert into, or Cordis's native event system.
|
||||
The product principle (see the 微内核Harness实现思路 design doc) is "everything is a plugin": hooks, /goal, /loop, dynamic workflows, compaction, sandboxing, permissions, UI, persistence, MCP, skills must all be writable as plugins without modifying the core. Candidate mechanisms considered: a purpose-built middleware stack (koa-compose style), an explicit phase state machine plugins can insert into, or Cordis's native event system.
|
||||
|
||||
## Decision
|
||||
|
||||
Pure Cordis event taxonomy. The loop's extension seams are typed events with
|
||||
deliberate dispatch modes:
|
||||
Pure Cordis event taxonomy. The loop's extension seams are typed events with deliberate dispatch modes:
|
||||
|
||||
- **waterfall** (around-middleware) where plugins mutate or veto:
|
||||
`agent/request`, `agent/step-result`, `agent/turn-continuation`,
|
||||
`tools/execute`, `llm/stream`, `llm/generate`, `system-prompt/assemble`.
|
||||
- **emit** (sync fire-and-forget) for notifications: turn/step boundaries,
|
||||
stream chunks, lifecycle, errors.
|
||||
- **waterfall** (around-middleware) where plugins mutate or veto: `agent/request`, `agent/step-result`, `agent/turn-continuation`, `tools/execute`, `llm/stream`, `llm/generate`, `system-prompt/assemble`.
|
||||
- **emit** (sync fire-and-forget) for notifications: turn/step boundaries, stream chunks, lifecycle, errors.
|
||||
- **parallel** (awaited) for the one durability checkpoint: `session/flush`.
|
||||
|
||||
The event vocabulary lives in interface packages (dsh-agent declares the
|
||||
agent/* events); `@deepseek-ai/dsh-agent-loop` is the only concrete plugin and
|
||||
is itself swappable — nothing outside it may depend on it.
|
||||
The event vocabulary lives in interface packages (dsh-agent declares the agent/* events); `@deepseek-ai/dsh-agent-loop` is the only concrete plugin and is itself swappable — nothing outside it may depend on it.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Every MVP feature maps to a listener (the "plugin sanity checklist" in
|
||||
docs/architecture.md is the proof obligation, kept current).
|
||||
- Every MVP feature maps to a listener (the "plugin sanity checklist" in docs/architecture.md is the proof obligation, kept current).
|
||||
- HMR and disposal come free: listeners and registrations are Cordis effects.
|
||||
- Waterfall semantics (call `next()` or short-circuit) are non-obvious and
|
||||
must be taught — documented in AGENTS.md and covered by composition tests.
|
||||
- The loop must be defensive: plugin exceptions are contained at turn level,
|
||||
steering from any seam is never stranded (regression-tested).
|
||||
- Waterfall semantics (call `next()` or short-circuit) are non-obvious and must be taught — documented in AGENTS.md and covered by composition tests.
|
||||
- The loop must be defensive: plugin exceptions are contained at turn level, steering from any seam is never stranded (regression-tested).
|
||||
|
||||
@@ -4,35 +4,19 @@ Status: accepted (2026-06-11)
|
||||
|
||||
## Context
|
||||
|
||||
The MVP requires strict event-based tracing with fully replayable sessions
|
||||
(严格的基于事件的trace、logging系统,session完全可回放). Two models were
|
||||
considered: a mutable message array with events fired as notifications
|
||||
(simpler, but state and log can diverge), or event-sourcing where the log IS
|
||||
the state.
|
||||
The MVP requires strict event-based tracing with fully replayable sessions (严格的基于事件的trace、logging系统,session完全可回放). Two models were considered: a mutable message array with events fired as notifications (simpler, but state and log can diverge), or event-sourcing where the log IS the state.
|
||||
|
||||
## Decision
|
||||
|
||||
A `Session` is an append-only log of typed `SessionEvent`s — the single
|
||||
source of truth. The LLM message history is *derived* from the log
|
||||
(`deriveMessages()`); raw stream chunks are logged for token-level replay
|
||||
fidelity while the assembled `assistant/message` event is authoritative for
|
||||
derivation. Replay/fork = seed a new session with an existing log.
|
||||
A `Session` is an append-only log of typed `SessionEvent`s — the single source of truth. The LLM message history is *derived* from the log (`deriveMessages()`); raw stream chunks are logged for token-level replay fidelity while the assembled `assistant/message` event is authoritative for derivation. Replay/fork = seed a new session with an existing log.
|
||||
|
||||
Appends are synchronous (the hot path never blocks on I/O); `session/event`
|
||||
is a sync notification; persistence plugins buffer write-behind and drain at
|
||||
the awaited `session/flush` checkpoint fired at every turn end.
|
||||
Appends are synchronous (the hot path never blocks on I/O); `session/event` is a sync notification; persistence plugins buffer write-behind and drain at the awaited `session/flush` checkpoint fired at every turn end.
|
||||
|
||||
Ordering contract: the loop appends to the session *before* emitting the
|
||||
corresponding Cordis event, and the `agent/step-result` waterfall runs before
|
||||
the `assistant/message` append so the log records what tool dispatch actually
|
||||
used (post-review fix; regression-tested).
|
||||
Ordering contract: the loop appends to the session *before* emitting the corresponding Cordis event, and the `agent/step-result` waterfall runs before the `assistant/message` append so the log records what tool dispatch actually used (post-review fix; regression-tested).
|
||||
|
||||
## Consequences
|
||||
|
||||
- Replay, trace, and telemetry are structurally guaranteed, not bolted on.
|
||||
- Persistence stays a plugin concern; the in-memory store ships in dsh-session.
|
||||
- The event vocabulary is merge-extensible (plugins add e.g. compaction
|
||||
events); it carries a TODO(review) marker until the first persistence
|
||||
plugin and real adapter exercise it.
|
||||
- Derivation cost grows with log length — compaction (future plugin) is the
|
||||
intended mitigation, not log mutation.
|
||||
- The event vocabulary is merge-extensible (plugins add e.g. compaction events); it carries a TODO(review) marker until the first persistence plugin and real adapter exercise it.
|
||||
- Derivation cost grows with log length — compaction (future plugin) is the intended mitigation, not log mutation.
|
||||
|
||||
@@ -4,34 +4,16 @@ Status: accepted (2026-06-11)
|
||||
|
||||
## Context
|
||||
|
||||
The harness needs one internal language for messages that the loop, session
|
||||
log, and all plugins speak. Options: mirror the DeepSeek/OpenAI
|
||||
chat-completions shape (zero mapping for the first provider, awkward for rich
|
||||
content), adopt Anthropic's Messages block structure verbatim (battle-tested,
|
||||
but our canonical types would mirror a third-party API we don't target
|
||||
first), or own a vocabulary.
|
||||
The harness needs one internal language for messages that the loop, session log, and all plugins speak. Options: mirror the DeepSeek/OpenAI chat-completions shape (zero mapping for the first provider, awkward for rich content), adopt Anthropic's Messages block structure verbatim (battle-tested, but our canonical types would mirror a third-party API we don't target first), or own a vocabulary.
|
||||
|
||||
## Decision
|
||||
|
||||
Own it: messages are arrays of typed content blocks (`text`, `reasoning`,
|
||||
`tool-call`, `tool-result`, `image`), with the union derived from the
|
||||
merge-extensible `ContentBlockMap` so plugins add block types via declaration
|
||||
merging. The same merge-extensible-map pattern types every "stringly" field
|
||||
(`MessageSource`, `FinishReason`, `TurnTrigger`, `TurnEndReason`). Streaming
|
||||
is a raw chunk protocol; `BlockAssembler` is the single shared assembly
|
||||
implementation. Adapters translate to provider wire formats — mapping cost
|
||||
lives in adapters, where it belongs.
|
||||
Own it: messages are arrays of typed content blocks (`text`, `reasoning`, `tool-call`, `tool-result`, `image`), with the union derived from the merge-extensible `ContentBlockMap` so plugins add block types via declaration merging. The same merge-extensible-map pattern types every "stringly" field (`MessageSource`, `FinishReason`, `TurnTrigger`, `TurnEndReason`). Streaming is a raw chunk protocol; `BlockAssembler` is the single shared assembly implementation. Adapters translate to provider wire formats — mapping cost lives in adapters, where it belongs.
|
||||
|
||||
In-session context injection (`context/message`, `steering/message`) renders
|
||||
as tagged user-role envelopes (the system-reminder pattern) rather than a new
|
||||
role, so adapters carry zero burden. TODO(review): revisit once the DeepSeek
|
||||
V4 adapter exists.
|
||||
In-session context injection (`context/message`, `steering/message`) renders as tagged user-role envelopes (the system-reminder pattern) rather than a new role, so adapters carry zero burden. TODO(review): revisit once the DeepSeek V4 adapter exists.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Reasoning, prefill, cache hints, and multimodal content all have a home
|
||||
without provider contortions.
|
||||
- Every adapter pays a translation cost; the streaming protocol carries a
|
||||
TODO(review) marker until the first real adapter validates it.
|
||||
- IDs that cross package boundaries are branded (`CallId`, `SessionId`,
|
||||
`AgentId`) — nominal typing at zero runtime cost.
|
||||
- Reasoning, prefill, cache hints, and multimodal content all have a home without provider contortions.
|
||||
- Every adapter pays a translation cost; the streaming protocol carries a TODO(review) marker until the first real adapter validates it.
|
||||
- IDs that cross package boundaries are branded (`CallId`, `SessionId`, `AgentId`) — nominal typing at zero runtime cost.
|
||||
|
||||
@@ -4,32 +4,16 @@ Status: accepted (2026-06-11)
|
||||
|
||||
## Context
|
||||
|
||||
Tool parameters must reach the model as standard JSON Schema (the wire
|
||||
format), and tool authors deserve typed `execute(args)` without casts. The
|
||||
repo already vendors schemastery (used for plugin Config), so reusing it was
|
||||
the obvious candidate. The user also explicitly preferred per-property
|
||||
`required: true` booleans over JSON Schema's separate `required` array.
|
||||
Tool parameters must reach the model as standard JSON Schema (the wire format), and tool authors deserve typed `execute(args)` without casts. The repo already vendors schemastery (used for plugin Config), so reusing it was the obvious candidate. The user also explicitly preferred per-property `required: true` booleans over JSON Schema's separate `required` array.
|
||||
|
||||
## Decision
|
||||
|
||||
A small custom DSL in dsh-tools: `SchemaSpec` (per-property specs with
|
||||
`required: true` booleans), type-level `InferArgs<S>` mapping a spec to the
|
||||
argument type (required keys non-optional, others genuinely optional via `?`),
|
||||
a runtime `schemaSpecToJsonSchema()` converter, and `defineTool()` tying them
|
||||
together. Raw JSON-Schema `ToolDefinition`s remain accepted by
|
||||
`ToolRegistry.register()` — that's how MCP-sourced tools arrive.
|
||||
A small custom DSL in dsh-tools: `SchemaSpec` (per-property specs with `required: true` booleans), type-level `InferArgs<S>` mapping a spec to the argument type (required keys non-optional, others genuinely optional via `?`), a runtime `schemaSpecToJsonSchema()` converter, and `defineTool()` tying them together. Raw JSON-Schema `ToolDefinition`s remain accepted by `ToolRegistry.register()` — that's how MCP-sourced tools arrive.
|
||||
|
||||
Schemastery was evaluated and rejected for this use: it targets validation /
|
||||
transformation against StandardSchema, not JSON Schema *generation*, so it
|
||||
would add indirection without producing the wire format cleanly.
|
||||
Schemastery was evaluated and rejected for this use: it targets validation / transformation against StandardSchema, not JSON Schema *generation*, so it would add indirection without producing the wire format cleanly.
|
||||
|
||||
## Consequences
|
||||
|
||||
- First-party tool authors get zero-cast typed args; the type gymnastics cost
|
||||
stays inside the core package (sanctioned by the AGENTS.md type-safety
|
||||
policy).
|
||||
- The DSL is deliberately small (string/number/boolean/object/array, enum,
|
||||
default, nested properties/items). Gaps vs full JSON Schema (unions,
|
||||
formats, constraints) are accepted until real tools demand them.
|
||||
- The InferArgs mapping is regression-tested at the type level (expectTypeOf)
|
||||
after an early optionality bug shipped and was caught by review.
|
||||
- First-party tool authors get zero-cast typed args; the type gymnastics cost stays inside the core package (sanctioned by the AGENTS.md type-safety policy).
|
||||
- The DSL is deliberately small (string/number/boolean/object/array, enum, default, nested properties/items). Gaps vs full JSON Schema (unions, formats, constraints) are accepted until real tools demand them.
|
||||
- The InferArgs mapping is regression-tested at the type level (expectTypeOf) after an early optionality bug shipped and was caught by review.
|
||||
|
||||
@@ -4,28 +4,14 @@ Status: accepted (2026-06-11)
|
||||
|
||||
## Context
|
||||
|
||||
On the wire, tool schemas travel in a dedicated `tools` field of the model
|
||||
request, not in prompt text. Architecturally, though, "what the model is told
|
||||
it can do" is one coherent concern: prompt sections and the tool list are
|
||||
assembled from the same plugin contributions and consumed at the same moment.
|
||||
The alternative — the loop querying the tool registry separately from the
|
||||
prompt service — splits one concern across two seams.
|
||||
On the wire, tool schemas travel in a dedicated `tools` field of the model request, not in prompt text. Architecturally, though, "what the model is told it can do" is one coherent concern: prompt sections and the tool list are assembled from the same plugin contributions and consumed at the same moment. The alternative — the loop querying the tool registry separately from the prompt service — splits one concern across two seams.
|
||||
|
||||
## Decision
|
||||
|
||||
`PromptAssembly { sections, tools }`: the system-prompt service collects
|
||||
ordered text sections AND tool schemas (the tool registry auto-contributes a
|
||||
provider). The loop consumes one assembly per step; adapters map `sections`
|
||||
to the provider's system slot and `tools` to the wire `tools` field. The
|
||||
`system-prompt/assemble` waterfall is therefore a single interception point
|
||||
for everything the model is told up front — tool filtering (ToolSearch /
|
||||
progressive disclosure) is an assembly rewrite, same as prompt edits.
|
||||
`PromptAssembly { sections, tools }`: the system-prompt service collects ordered text sections AND tool schemas (the tool registry auto-contributes a provider). The loop consumes one assembly per step; adapters map `sections` to the provider's system slot and `tools` to the wire `tools` field. The `system-prompt/assemble` waterfall is therefore a single interception point for everything the model is told up front — tool filtering (ToolSearch / progressive disclosure) is an assembly rewrite, same as prompt edits.
|
||||
|
||||
## Consequences
|
||||
|
||||
- One waterfall governs the model's standing context; plugins like plan mode
|
||||
can swap prompt text and visible tools in one listener.
|
||||
- The assembly interface is merge-extensible for future slots (no untyped
|
||||
`extras` bag — extension is declaration merging).
|
||||
- Slight conceptual surprise (schemas in a "prompt" service) is documented
|
||||
here and in the package README.
|
||||
- One waterfall governs the model's standing context; plugins like plan mode can swap prompt text and visible tools in one listener.
|
||||
- The assembly interface is merge-extensible for future slots (no untyped `extras` bag — extension is declaration merging).
|
||||
- Slight conceptual surprise (schemas in a "prompt" service) is documented here and in the package README.
|
||||
|
||||
@@ -4,33 +4,20 @@ Status: accepted (2026-06-11)
|
||||
|
||||
## Context
|
||||
|
||||
This codebase is developed primarily by coding agents. Agents follow enforced
|
||||
gates far more reliably than prose conventions, and "a lot of work" is not a
|
||||
cost argument when agents do the labor. Early evidence: tests that didn't
|
||||
typecheck shipped (vitest doesn't typecheck) and were only caught by a review.
|
||||
This codebase is developed primarily by coding agents. Agents follow enforced gates far more reliably than prose conventions, and "a lot of work" is not a cost argument when agents do the labor. Early evidence: tests that didn't typecheck shipped (vitest doesn't typecheck) and were only caught by a review.
|
||||
|
||||
## Decision
|
||||
|
||||
Every AGENTS.md promise gets a command that exits non-zero, wired into git
|
||||
hooks and CI both calling the same package.json scripts:
|
||||
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).
|
||||
- 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), yarn constraints
|
||||
(workspace rules: private, cordis peer+dev, uniform version, ESM).
|
||||
- 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.
|
||||
- Max-strict TypeScript (`noUncheckedIndexedAccess`, `exactOptionalPropertyTypes`, …); tests and examples typecheck in CI via `tsconfig.typecheck.json` (vendored packages resolve as built declarations).
|
||||
- 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), yarn constraints (workspace rules: private, cordis peer+dev, uniform version, ESM).
|
||||
- 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
|
||||
|
||||
- Conventions survive agent turnover; violations fail fast and locally.
|
||||
- The gates themselves are code to maintain; config changes are reviewed like
|
||||
any change.
|
||||
- 100%-coverage pressure can produce assertion-free tests — mutation testing
|
||||
is the planned counterweight (see RFC 002).
|
||||
- The gates themselves are code to maintain; config changes are reviewed like any change.
|
||||
- 100%-coverage pressure can produce assertion-free tests — mutation testing is the planned counterweight (see RFC 002).
|
||||
|
||||
@@ -4,49 +4,21 @@ Status: accepted (2026-06-11)
|
||||
|
||||
## Context
|
||||
|
||||
The initial build used **dumble**, the cordiverse zero-config esbuild wrapper
|
||||
that upstream Cordis itself builds with — maximum alignment with the vendored
|
||||
packages' conventions (it reads each package.json and infers entries/formats
|
||||
from the `exports` field). But dumble is a liability as a load-bearing tool in
|
||||
this repo: v0.2.x, ~530 npm downloads/week, effectively one maintainer, and we
|
||||
were invoking it through a custom orchestration script (`scripts/build.ts`)
|
||||
because it has no workspace mode.
|
||||
The initial build used **dumble**, the cordiverse zero-config esbuild wrapper that upstream Cordis itself builds with — maximum alignment with the vendored packages' conventions (it reads each package.json and infers entries/formats from the `exports` field). But dumble is a liability as a load-bearing tool in this repo: v0.2.x, ~530 npm downloads/week, effectively one maintainer, and we were invoking it through a custom orchestration script (`scripts/build.ts`) because it has no workspace mode.
|
||||
|
||||
Build output currently matters only for `yarn build` + publint (nothing
|
||||
publishes yet; dev/test/demo run unbuilt via tsx), so the switching cost is at
|
||||
its lowest now and only grows once packages publish.
|
||||
Build output currently matters only for `yarn build` + publint (nothing publishes yet; dev/test/demo run unbuilt via tsx), so the switching cost is at its lowest now and only grows once packages publish.
|
||||
|
||||
## Decision
|
||||
|
||||
Replace dumble with **tsdown** (rolldown-based, ~2.5M downloads/week,
|
||||
VoidZero-backed, actively released):
|
||||
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 yarn 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).
|
||||
- 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).
|
||||
- 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 yarn 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).
|
||||
- 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; `yarn build` = `tsc -b && 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).
|
||||
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 ADR.
|
||||
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 ADR.
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
# Architecture Decision Records
|
||||
|
||||
Short, immutable records of the *why* behind decisions that shape this
|
||||
codebase. Code and docs say what the system does; ADRs say why it does it
|
||||
that way and what we gave up.
|
||||
Short, immutable records of the *why* behind decisions that shape this codebase. Code and docs say what the system does; ADRs say why it does it that way and what we gave up.
|
||||
|
||||
Format: one file per decision, numbered, with Status / Context / Decision /
|
||||
Consequences. An ADR is never edited into a different decision — supersede it
|
||||
with a new one and cross-link.
|
||||
Format: one file per decision, numbered, with Status / Context / Decision / Consequences. An ADR is never edited into a different decision — supersede it with a new one and cross-link.
|
||||
|
||||
| # | Title | Status |
|
||||
|---|---|---|
|
||||
|
||||
Reference in New Issue
Block a user