docs: unify ADR/RFC trees into one lifecycle-organized RFC tree

Collapse docs/adr/ and docs/rfc/ into a single docs/rfc/ with proposed/,
implemented/, and rejected/ subfolders. Every file is renamed to
yyyy-mm-dd-topic-title.md, where the date is when the topic was first
proposed (from git history). ADRs and RFCs that covered exactly the same
topic are merged (property-based testing, session persistence); the
umbrella RFC 005 stays split across its three implemented decisions, and
RFC 006's deferred part-3 (API extractor reports) splits into its own
proposed RFC. All cross-references become machine-checkable relative
links instead of bare "ADR NNNN" / "RFC NNN" prose.

Add a verify-md-links doc-sync gate (scripts/verify-md-links.ts) that
checks every relative Markdown cross-link resolves, wired into doc-sync
alongside verify-md-wrap. This makes the reorganization self-verifying:
the same change that rewrote ~forty inter-doc links adds the check that
proves none dangle. Document the cross-link convention in a new
docs/AGENTS.md and record the gate as an implemented RFC.

doc-sync, typecheck, lint, and the full test suite (667) all pass.
This commit is contained in:
Tianyi Cui
2026-06-18 02:18:24 +08:00
parent 9a30c46d63
commit 7c400e9c02
78 changed files with 503 additions and 373 deletions

View File

@@ -88,7 +88,7 @@ describe('session-log invariants', () => {
it('rejects a message event appended outside any open turn (turn-enclosure)', async () => {
const { ctx } = await setup({ freeze: false })
const session = ctx.sessions.create()
// No turn open: every message-bearing event must be turn-enclosed (ADR 0017).
// No turn open: every message-bearing event must be turn-enclosed (the turn-enclosure RFC).
expect(() => session.append('user/message', { content: [{ type: 'text', text: 'hi' }], source: { kind: 'user' } }))
.toThrow(/outside any open turn/)
expect(() => session.append('context/message', { content: [{ type: 'text', text: 'ctx' }], source: { kind: 'user' } }))
@@ -99,7 +99,7 @@ describe('session-log invariants', () => {
const { ctx } = await setup({ freeze: false })
const session = ctx.sessions.create()
// usage and error are turn-scoped: outside a turn they would land past the
// commit boundary and be dropped on resume (ADR 0017).
// commit boundary and be dropped on resume (the turn-enclosure RFC).
expect(() => session.append('usage', { turn: 1, step: 1, usage: { inputTokens: 1, outputTokens: 1 } }))
.toThrow(/outside any open turn/)
expect(() => session.append('error', { turn: 1, step: 1, message: 'boom' }))
@@ -276,7 +276,7 @@ describe('dev-freeze', () => {
// A caller hands in a SHALLOW-frozen block whose nested array is still
// mutable. deepFreeze must descend into the already-frozen object and
// freeze the descendant, not short-circuit on the frozen container —
// otherwise dev-mode misses exactly the history mutation ADR 0012 catches.
// otherwise dev-mode misses exactly the history mutation the dev-invariants RFC catches.
// `append` snapshots `data`, so the freeze applies to the LOGGED clone, not
// the caller's input — read the event back and assert on its data.
const innerContent: { type: 'text'; text: string }[] = [{ type: 'text', text: 'inner' }]