Commit Graph
157 Commits
Author SHA1 Message Date
Chinesezjc a95171b084 fix(code-runtime-python): declare the MIT license the package gate requires
master added verify-dsh-package-licenses while this branch was open: every
repository-owned DSH package must declare "license": "MIT". This package
carried BSD-3-Clause from its creation, so the gate failed and took the
required "node 24 / static" lane down with it.
2026-08-17 17:33:55 +08:00
Chinesezjc b90b45158e Merge remote-tracking branch 'origin/master' into feat/code-runtime-python-protocol
Resolutions:

- docs/module-graph.md, docs/config-catalog.md: generated files. Regenerated
  with gen-module-graph and gen-config-catalog on the merged tree, then carried
  the new package's entries into the Chinese sides and re-recorded both
  pairings. Each side now differs from master by exactly the
  code-runtime-python rows.
- scripts/verify-package-readme-model-experience.ts, tsconfig.host.json: master
  renamed packages/bash -> packages/shell, packages/pty -> packages/terminal,
  code-runtime-worker -> code-runtime-worker-thread and agent-tool-mode ->
  agent-tool-presentation. Kept master's names and re-added this branch's
  code-runtime-python entry.

Adapted the package to conventions master introduced while the branch was open:
version 0.1.0-rc.6 with publishConfig.access "public" (the release-member rule
check-workspace-constraints now enforces), the invariants project reference
moved to packages/runtime-diagnostics/invariants, and the README companion link
retargeted to code-runtime-worker-thread.
2026-08-17 17:26:11 +08:00
imccyu 15148dbd9a release(dsh): 0.1.0-rc.6 2026-08-13 19:52:05 +08:00
imccyu abe560f81e release(dsh): 0.1.0-rc.5 2026-08-13 18:49:33 +08:00
imccyu 8c1e8d9890 build(release): publish the dsh family publicly
Every release member now declares publishConfig.access: public, so the scope no
longer mixes levels: the 221 packages/*/* and apps/* manifests join the vendored
framework and the native packages.

check-workspace-constraints drops the per-sequence expectation and holds every
release member to public, which is what stops a member from drifting back.

Access is a property of the package, not of a version: the dsh packages already
published as restricted become world-readable at their next publication.
2026-08-13 18:49:32 +08:00
imccyu 8a954b2eca release(dsh): 0.1.0-rc.3 2026-08-13 18:39:06 +08:00
imccyu 60b04b6ef7 release(dsh): 0.1.0-rc.2 2026-08-13 17:19:37 +08:00
imccyu 22ab3beac1 release(dsh): 0.1.0-rc.1 2026-08-13 16:22:33 +08:00
Tianyi Cui c905c4694e Adopt MIT for DSH packages 2026-08-13 13:07:24 +08:00
imccyu 3e8a1cfa33 release(dsh): 0.0.1-rc.5 2026-08-13 06:14:27 +08:00
imccyu a90d9af1b2 release(dsh): 0.0.1-rc.4 2026-08-13 05:16:02 +08:00
imccyu 1e99f20963 release(dsh): 0.0.1-rc.3 2026-08-13 03:24:38 +08:00
Tianyi Cui a2d0f7f411 refactor: apply repository naming contract
Apply the accepted pre-release package, service, type, directory, and role renames as one repository-wide change.
2026-08-13 00:54:38 +08:00
Chinesezjc 4b06c78075 Merge remote-tracking branch 'origin/master' into feat/code-runtime-python-protocol 2026-08-12 21:07:06 +08:00
xjt 4806d94715 docs(i18n): include complete proofreading corpus 2026-08-12 12:30:19 +08:00
Chinesezjc a9117995e1 fix(code-runtime-python): match the released root version
master cut 0.0.1-rc.2 while this branch was open. The version bump touched
every existing package but not this new one, so check-workspace-constraints
rejected the mismatch and took the required "all checks passed" job down
with it.
2026-08-12 02:13:54 +08:00
Chinesezjc c9750c5161 Merge remote-tracking branch 'origin/master' into feat/code-runtime-python-protocol 2026-08-12 01:57:16 +08:00
imccyu 5ca7be5dcb release(dsh): 0.0.1-rc.2 2026-08-11 22:52:39 +08:00
Chinesezjc 5d3afb192d Merge remote-tracking branch 'origin/master' into feat/code-runtime-python-protocol
Resolutions:

- docs/module-graph.md, docs/config-catalog.md: both are generated files.
  Regenerated with gen-module-graph and gen-config-catalog on the merged
  tree instead of hand-merging the conflict hunks.
- scripts/verify-package-readme-model-experience.ts: both sides appended
  registry entries; kept all four.

Adapted this package to conventions master introduced while the branch was
open: version 0.0.1-rc.1 with publishConfig and repository metadata, the
workspace: protocol for peer dependencies, and the @deepseek-ai/cordis
rescope in package.json and src/invariant.ts.
2026-08-11 15:53:38 +08:00
Turtle 0c708cb10d refactor: replace overloaded surface terminology 2026-08-11 15:23:05 +08:00
Chinesezjc ea1b494614 docs(code-runtime-python): drop forward references this layer does not own
Two comments described facts that belong to later layers of the stack:

- The workspace-constraints whitelist comment described a bootstrap the host
  spawns by path. This layer's py/ holds only protocol.py, the wire-vocabulary
  mirror, and nothing here spawns it. State what the whitelist entry actually
  covers: the Python source ships as-is rather than built.
- checkDoneValue's JSDoc claimed maxValueBytes "defaults to 32 KiB". This
  package defines no config and no default; maxValueBytes is a required boot
  frame field. Name it as the budget instead, so the prose cannot drift when
  the owning implementation picks a default.

Comment-only; the bound argument is unchanged.
2026-08-11 14:44:39 +08:00
imccyu b64c3ac1ba release(dsh): 0.0.1-rc.1 2026-08-11 03:20:36 +08:00
imccyu 2c85c484d3 build(release): reference workspace members through the workspace protocol
1504 hand-written ranges pointing at workspace members become workspace:^, so
pnpm pack substitutes each member's real version at publication: sibling
peerDependencies follow the family version instead of being pinned at ^0.0.1,
and a reference to a vendored package follows that package's own line. Without
this, publishing 0.0.2 ships peer ranges naming a version that does not exist,
and 0.0.1-rc.1 does not satisfy ^0.0.1 either.

It also retires ranges that had gone stale against the workspace: ^4.0.0-rc.6
for a 4.0.0-rc.7 checkout, ^3.17.0 for schemastery 3.18.0.

workspace:* stays where an exact published version is the point, which is how
the Landlock entry pins its platform packages.

A workspace constraint now requires the protocol, so a new package cannot
reintroduce a hand-written range. The same constraint caught packages/boot/cmdline
arriving on master without the publishable trio, which this change completes.
2026-08-11 00:17:09 +08:00
imccyu 97eb14a007 build(release): make the release set publishable under the private scope
Every package under packages/, apps/, and vendor/ drops "private": true and
declares publishConfig.access "restricted": the repository now states which
packages it publishes instead of deciding it at publish time. Each one also
declares its repository and directory, which is how a consumer of a private
package reaches its source.

The Landlock packages move to restricted with them. They have never been
published, so nothing anonymous depends on them today, and the whole
@deepseek-ai scope stays private.

The workspace constraint that required every package to be private now applies
to non-members only, and asserts the publishable trio on each release member.
2026-08-11 00:09:31 +08:00
imccyu ec601ca13d build(vendor): rescope the vendored Cordis packages into @deepseek-ai
Machine-produced by `pnpm run rescope-vendor --apply` plus the regeneration it
prints: `pnpm install` for the lockfile, `pnpm run gen-third-party-notices`,
`verify-translation-pairing --write` for the touched bilingual pairs,
`gen-doc-graphs`, and one typert snapshot whose ids embed character offsets.
`pnpm run rescope-vendor --check` verifies the result.

Renames nine vendored packages (cordis, cosmokit, schemastery and the six
@cordisjs plugins) and every reference that resolves them: manifest names and
dependency keys, module specifiers including declare-module merges, cordis.yml
plugin names, tsconfig paths, every Markdown fence, and `docs/` prose.
Directory names, upstream versions, and dependency ranges are unchanged, so
vendor/README.md still reads as an upstream snapshot; its manifest table gains
an upstream-name column so THIRD_PARTY_NOTICES keeps MIT attribution pointed
at each fork's origin.

The tutorial tier follows the rename end to end: its yaml fences named plugins
the Loader can no longer resolve, its `ts ignore-check` fences disagreed with
the compiled fences beside them, and its prose quoted both. The contracts that
told readers to keep upstream names — the root convention and the vendoring
cookbook's tree comment and manifest invariant — now say to rescope instead.

Two rules read `@deepseek-ai/` as "another workspace plugin": the client bundle
purity gate now names the vendored libraries a browser bundle inlines, and the
files where a bare `cordis` is an agent-preset id keep that product data.
2026-08-10 22:04:13 +08:00
Turtle a27efdef36 docs: make technical prose concrete 2026-08-10 16:34:20 +08:00
Tianyi Cui 25dcd7293c docs: purge chain-of-thought leakage from prose
Delete design-session citations (decision/audit/plan ordinals, stack
positions), change narration, review choreography, and reviewer-addressed
justification from comments, JSDoc, docs, READMEs, Agent Notes, tests, and
generator templates; restate every affected fact as current-state contract
prose. Fix generated docs at their sources and regenerate the catalogs and
cordis-surface regions; re-paste type-equiv blocks; update every bilingual
counterpart and re-record the pairs. Record the citation rule in the
committed-artifact-citations Agent Note.
2026-08-09 21:10:59 +08:00
Turtle dda02250f5 docs: reserve seam for complete capabilities 2026-08-09 17:26:57 +08:00
Turtle 27ac49e687 docs: define a seam as the (Service, Service provider, Consumer) trio 2026-08-09 17:11:16 +08:00
xjt a7af54a8d3 docs(i18n): standardize contract terminology 2026-08-09 11:33:14 +08:00
xjt bd659179f6 docs(i18n): reproofread updated Chinese documentation 2026-08-09 03:20:17 +08:00
Tianyi Cui aa0ca6c836 docs: anchor each subsystem page to its package group; make group READMEs thin tables
core.md read as a type grab-bag: LLM wire vocabulary up front, the agent/loop story buried, and no correspondence to packages/core. It now opens on the packages/core control spine — the package-by-package loop map with a Page column into session/system-prompt/tools/scope — and keeps only what the spine group declares plus the repo-wide patterns: the Agent handle with its delivery/cancellation/interception contracts, the SessionEvent envelope, branded ids, the …Map pattern. The conversation vocabulary (Message/ContentBlock, the model request, adapters — 17 type-equiv blocks) moves to llm-streaming.md, which now declares packages/llm end-to-end; the duplicate ContentBlockMap paste near its seam section folds into the moved section, and the manifest, LINK_MAP, README table rows, website label (Core data structures → Core), and inbound anchors follow.

Every packages/<group>/README pair is now a thin front door in one shape: a why-first intro (bash's seam-pattern-first paragraph rewritten as 'shell execution for the agent'), the package table, and a closing pointer to the owning docs/subsystems page — the bash-style table stays the load-bearing middle. Load-bearing trailing paragraphs relocate rather than vanish: the fs no-timeout rationale becomes a filesystem.md section (both languages), session's four sectioned tables merge into one 12-row table, examples' legacy-bin H2 collapses to a pointer at jsonrpc-demo's README, and design rationale that already lives in an Agent Note or subsystem page is now linked instead of restated. All 40 pair records re-recorded.
2026-08-09 01:32:39 +08:00
Chinesezjc 5dad49f4db docs(code-runtime-python): name the roster aliases by subset direction, align metering prose
Rename UnionCoversRoster/RosterCoversUnion to UnionSubsetOfRoster/RosterSubsetOfUnion so
the names read in the same direction as their extends clauses, share the python3 -I -B
flags between the two mirror probes, and align the README and Agent Note prose with the
checkDoneValue JSDoc: the escaped-size scan is the metering itself, not deferred work.
Regenerate docs/module-graph.md, which listed code-runtime-python twice.
2026-08-07 15:37:19 +08:00
Chinesezjc 32d6444a2c fix(code-runtime-python): align package files with the publication gate 2026-08-07 13:27:54 +08:00
Chinesezjc 203bfca0ea docs(code-runtime-python): trim metering prose and cover encodeJsonPlain depth
- Drop the review-history narrative from checkDoneValue's JSDoc (the "in the
  previous implementation … now avoids" clause); state the current contract only.
- Assert encodeJsonPlain on the same 100k-deep value the metering test uses:
  its headline contract is stack-safety (JSON.stringify would throw), but no
  test exercised the encoder on a deep value.
2026-08-07 13:27:54 +08:00
Chinesezjc 4674d8fa92 fix(code-runtime-python): verify union<->roster both ways, stop pycache writes, refresh metering prose
Address the latest review round:

- WireFrameShapesCoverUnions checked only union ⊆ roster, so removing a frame
  from a message union (e.g. dropping ReplyErr from ReplyMessage) left the check
  true while the public TS union diverged from the wire. Replace it with a
  bidirectional equivalence between MessageFrames and the roster's message-frame
  value types (nested Namespace/ErrorClass/DoneErrorField excluded): both a frame
  added to a union without a roster entry and a frame removed from a union now
  fail typecheck (both verified).
- The mirror e2e's python3 probes imported protocol.py without -B, writing
  py/__pycache__/*.pyc into the (un-ignored) source tree. Add -B to both.
- Refresh the metering prose (checkDoneValue JSDoc + README both sides + Agent
  Note both sides): the incremental-work list no longer says "per-key
  JSON.stringify" now that jsonStringBytesUpTo scans without stringifying;
  re-record the README and Agent Note i18n pairings.
2026-08-07 13:27:54 +08:00
Chinesezjc f9ab1edc68 fix(code-runtime-python): meter escaped string bytes without allocating, bind frame roster to the unions
Two review findings on checkDoneValue's metering and the wire-mirror binding:

- The string/key byte check used a decoded-length lower bound and then called
  JSON.stringify, which materializes the ~6x escaped copy before the over-budget
  check — the hundreds-of-MB spike the metered walk exists to avoid. Add
  jsonStringBytesUpTo, a non-allocating scan that computes the exact escaped
  UTF-8 size (matching JSON.stringify byte for byte, including surrogate pairs
  vs lone surrogates) and bails the instant it crosses the remaining budget; use
  it for both string values and object keys.
- The frame roster in WIRE_FRAME_FIELD_ROLES was hand-written, so a frame added
  to ChildToHost/ReplyMessage without a roster entry slipped past. Introduce
  WireFrameShapes (name -> interface) as the canonical roster the roles map is
  bound against, plus a WireFrameShapesCoverUnions compile-time assertion that
  every message-union member appears in it (verified: adding a frame to a union
  without a WireFrameShapes entry fails typecheck).
2026-08-07 13:27:54 +08:00
Chinesezjc 4de8c914c9 refactor(code-runtime-python): export PROTOCOL_FD and tidy the mirror binding
Address the remaining review findings on the wire-mirror layer:

- Export PROTOCOL_FD from protocol.ts as the TS-side source of truth the host
  wires, and assert the Python constant against it in the mirror e2e instead of
  a bare literal 3, so an fd drift on either side is caught.
- Correct the FrameFieldRoles JSDoc to point at the actual assertion site
  (WIRE_FRAME_FIELD_ROLES's satisfies clause, not WIRE_FRAME_FIELDS).
- Drop the redundant explicit type annotation on WIRE_FRAME_FIELDS (the trailing
  `as` cast already types it; Object.fromEntries returns an index signature).
- Refresh the mirror-test comment to describe the roles-map binding (a TS-side
  add/remove/rename/optionality-flip fails typecheck; a Python-side change fails
  the comparison).
2026-08-07 13:27:54 +08:00
Chinesezjc adba2e305a fix(code-runtime-python): make the wire-field binding exhaustive over interface keys
The array-based FrameFields<T> only checked that listed names were members of
the frame's keys, so a field added to a TS interface (e.g. LogMessage.seq?)
left the existing arrays a valid subset — typecheck passed, and since the
constant and Python both lacked the field the mirror test passed too. The
JSDoc's claim that runtime covered this was false.

Replace it with WIRE_FRAME_FIELD_ROLES, a per-frame map keyed by field name
(`Record<RequiredKeys<T>, 'required'> & Record<OptionalKeys<T>, 'optional'>`),
so every interface key MUST appear with a matching required/optional tag: an
added field, a removed field, a rename, or an optionality flip all fail
typecheck at the roles map (verified). WIRE_FRAME_FIELDS is projected from it
as the sorted arrays the mirror test still compares to the Python TypedDicts.

Also drop the `export` added to the promoted frame interfaces (Namespace,
ErrorClass, RunMessage, DoneErrorField, ReplyOk, ReplyErr) — nothing outside
protocol.ts imports them, so the barrel surface is unchanged and knip stays
clean.
2026-08-07 13:27:54 +08:00
Chinesezjc 4d49406bd5 fix(code-runtime-python): bind the wire-field mirror to TS required/optional keys
The previous mirror binding (FrameFields<keyof T>) only checked membership: it
could not see a TS-side optionality flip (truncated? -> truncated leaves keyof
unchanged) or a field added on one side, so the "depends on the TS
declaration" claim was overstated.

- Promote the inline frame shapes (Namespace, ErrorClass, DoneErrorField,
  RunMessage, and the two Reply variants) to named interfaces so every frame
  binds uniformly.
- Derive FrameFields from RequiredKeys<T>/OptionalKeys<T>, so `required` and
  `optional` each accept only that side's keys. An optionality flip or a rename
  now fails typecheck (verified: flipping LogMessage.truncated to required
  errors at the constant).
- Enumerate EVERY public TypedDict in py/protocol.py in the mirror e2e (not a
  name list taken from the TS side) and assert both the frame roster and each
  frame's required/optional sets by exact equality, so a frame or field present
  on only one side of the wire fails the test.
2026-08-07 13:27:54 +08:00
Chinesezjc be839a8e53 fix(code-runtime-python): count non-lossless bytes and bind the mirror gate to TS types
Two gaps from the previous round's fixes:

- checkDoneValue flagged a non-lossless number but skipped counting its encoded
  bytes, so a value over budget ONLY through that number classified as
  non-lossless instead of over-budget (e.g. [Infinity] at cap 3, whose encoding
  is 10 bytes). Count the scalar's bytes even when flagging, so the budget check
  wins as the JSDoc promises. Add cap-3 regression cases.

- The mirror e2e compared the Python TypedDict keys against a hand-written
  constant, so a field change on the TS side alone would not fail it, and the
  reply frames were not probed at all. Introduce WIRE_FRAME_FIELDS in
  protocol.ts, bound to each frame interface's key set via `satisfies` (a
  renamed/removed field breaks typecheck — verified), and drive the mirror test
  from it, now covering ReplyOk/ReplyErr too. The test therefore fails on
  one-sided drift from either language.
2026-08-07 13:27:54 +08:00
Chinesezjc 8a60b5f005 feat(code-runtime-python): make the TypedDict wire mirror an executable gate
Address the two standing review suggestions in this layer rather than deferring
them to PR #4:

- Extend tests/protocol-mirror.e2e.ts to read each py/protocol.py TypedDict's
  required/optional key set and assert it against the wire field names
  src/protocol.ts declares (global included, via functional TypedDict). The
  round-12 class of drift — a renamed/dropped field, or one side making a field
  optional the other requires — now fails a test instead of relying on review.
  Field types remain review-guarded (no mechanical TS/Python equivalent).
- Drop the forward references to PR #4's internal mechanisms from this layer's
  prose: the "256 MiB frame ceiling" figure and the "(index.ts)" fd-3 pinning
  citation become an abstract "host-side inbound frame-size cap" so the JSDoc,
  spec, README, and Agent Note describe only what this layer owns.

Update both README sides and the Agent Note (both languages) to state the
mirror is now executable, and re-record their i18n pairings.
2026-08-07 13:27:54 +08:00
Chinesezjc 146a9d9f61 fix(code-runtime-python): make checkDoneValue over-budget precedence order-independent
checkDoneValue returned non-lossless the instant it hit a non-finite/negative-
zero number, before finishing the budget metering. A value that is BOTH over-
budget and non-lossless then classified by member order: `["<huge>", 1e400]`
gave non-lossless while `[1e400, "<huge>"]` gave over-budget — the same value,
two verdicts — which would drive the consumer to emit invalid-output vs
output-limit non-deterministically, contradicting the JSDoc promise that an
over-budget value is rejected as over-budget regardless. Record the number
violation in a flag and let metering finish; return non-lossless only once the
whole value is confirmed within budget. Add a regression test asserting both
member orders classify as over-budget.
2026-08-07 13:27:54 +08:00
Chinesezjc 8cf253a470 docs(code-runtime-python): sync README metering claim with code and note
Both README sides still described checkDoneValue as "one bounded traversal /
一次有界遍历" — the same overclaim already retracted in the code JSDoc and the
Agent Note. Reword both to match: the walk bounds only the incremental
allocation it adds (escaped-string copy, enqueued children, per-key stringify);
the frame's own width is parsed upstream and capped by the host's fd-3 receive
buffer, not re-bounded here. Re-record README.i18n.yaml.
2026-08-07 13:27:54 +08:00
Chinesezjc 69796d214c fix(code-runtime-python): remove NUL bytes and sync the Agent Note metering claim
- Replace four raw U+0000 bytes in protocol.spec.ts string literals with the
  \0 escape so the source stays plain text (a bare NUL makes text tools treat
  the file as binary); the runtime value is unchanged, so the bytes:8 NUL-escape
  assertion still holds.
- Sync the Agent Note (both languages) with the corrected checkDoneValue
  contract: the walk bounds only the incremental allocation it would add, not
  the frame width, which is already parsed and capped upstream by the host's
  fd-3 receive buffer. Drop the "prevents a hundreds-of-MB allocation" overclaim
  that the code JSDoc already retracted. Re-record the note i18n pairing.
2026-08-07 13:27:54 +08:00
Chinesezjc b4487485c2 docs(code-runtime-python): correct ownValues allocation claim
ownValues' JSDoc claimed the generator avoids a "second full-breadth
allocation before a single value is examined", but for...in still materializes
the key-name enumeration when the loop starts — the same JS limitation the
checkDoneValue rewrite now acknowledges. What the generator genuinely saves is
the extra VALUE array Object.values/Object.entries would copy; state that
precisely rather than implying sublinear startup.
2026-08-07 13:27:54 +08:00
Chinesezjc ae8070d799 fix(code-runtime-python): stop overclaiming O(cap) object metering
checkDoneValue cannot bound object width sublinearly: JS has no lazy own-key
iterator (for...in materializes the key set), and done.value is already
JSON.parse'd before the check runs, so the frame's width is paid upstream. The
genuine width bound is the host's fixed 256 MiB fd-3 receive buffer (a later
stack layer). Reword the JSDoc and branch comments to claim only what holds —
the traversal caps the INCREMENTAL allocation the check would add (escaped
strings, enqueued children, per-key stringify) and refuses over-budget before
those secondary allocations — and drop the mid-count micro-check that JS cannot
honor. Replace the Proxy test (whose ownKeys allocated a 2M array, proving
nothing) with assertions that an over-budget string/array/object is refused
before its escaped copy or child enqueue.
2026-08-07 13:27:54 +08:00
Chinesezjc 9dc9113ed7 fix(code-runtime): adopt the base seam's DUNDER_MEMBER resolution
The base seam branch resolved its DUNDER_MEMBER inconsistency by keeping
/^__.+__$/ and asserting `____` (empty middle between two `__` pairs) does not
match. Drop this branch's earlier /^__.*__$/ stopgap so the seam file is
byte-identical to its base: the earlier change only existed because the base
was self-inconsistent, and the base now owns a coherent decision.
2026-08-07 13:27:54 +08:00
Chinesezjc 104cd5f975 fix(code-runtime-python): bound checkDoneValue object metering in O(cap)
The object branch counted every own key before applying the size bound, so a
forged done.value with millions of keys and a small cap forced an O(frame)
walk — contradicting the O(cap) guarantee the comment promised and able to
block the host event loop. Bail mid-count the instant the running minimum
encoding (braces + 4 bytes/entry + commas) crosses maxBytes, and drop the now
-redundant post-count check the loop subsumes. Add a Proxy-based test proving
a 2M-key object enumerates fewer than 1000 keys under a 64-byte cap.

Also correct the checkDoneValue JSDoc: per-scalar byte length is measured via
scalarJson (exact BigInt digits for beyond-safe integers), not JSON.stringify.
2026-08-07 13:27:54 +08:00
Chinesezjc 31506dec2d fix(code-runtime-python): correct Chinese translation quality
- Translate README.zh.md's Model Experience body and KV Cache line, which
  were left verbatim in English.
- Convert half-width punctuation to full-width across the README.zh.md
  Known Limitations bullets and the entire Agent Note Chinese side, per
  docs/i18n translation-rules.md Typography (MUST use ,。:()in Chinese prose).
- Re-record both README and Agent Note i18n.yaml pairing hashes.
- Reword the workspace-constraints extra-files comment: this layer's py/
  ships only the wire-protocol mirror; the spawned bootstrap arrives later.
2026-08-07 13:27:54 +08:00