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:
Tianyi Cui
2026-06-13 18:39:20 +08:00
parent e98c1c5d42
commit 066f94c7e0
39 changed files with 348 additions and 1206 deletions

View File

@@ -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).

View File

@@ -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).

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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).

View File

@@ -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.

View File

@@ -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 |
|---|---|---|