From 0ba4e7d8f21240383ad1aa02b81c3888c02a59b4 Mon Sep 17 00:00:00 2001 From: huangruiteng <14976749+huangruiteng@users.noreply.github.com> Date: Sat, 26 Sep 2026 00:27:05 +0800 Subject: [PATCH 1/2] docs(rfc): propose monorepo distribution split and top-level module budget Add the Monorepo Distribution Split v0 RFC (English + zh-CN mirror) and its index entry. The RFC keeps one repository, splits the installed wheel into loopx-core / loopx-workspace / packages/ capability distributions, regroups loopx/ top-level modules behind compatibility shims, and pins the top-level module count with an architecture test. Proposal only; no runtime change. Tracking: #5072 Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> Co-authored-by: Cursor --- docs/architecture/rfcs/README.md | 10 + .../rfcs/monorepo-distribution-split-v0.md | 333 ++++++++++++++++++ .../monorepo-distribution-split-v0.zh-CN.md | 233 ++++++++++++ 3 files changed, 576 insertions(+) create mode 100644 docs/architecture/rfcs/monorepo-distribution-split-v0.md create mode 100644 docs/architecture/rfcs/monorepo-distribution-split-v0.zh-CN.md diff --git a/docs/architecture/rfcs/README.md b/docs/architecture/rfcs/README.md index a00d21731..ed104e587 100644 --- a/docs/architecture/rfcs/README.md +++ b/docs/architecture/rfcs/README.md @@ -116,6 +116,16 @@ failure leaves the generated files untouched. ## Control-Plane Kernel, State, And Migration +- [Monorepo Distribution Split v0](monorepo-distribution-split-v0.md) + ([中文版](monorepo-distribution-split-v0.zh-CN.md)) + - **RFC status:** Draft. + - **Delivery on `main`:** Proposal only; tracking [#5072](https://github.com/loopx-project/loopx/issues/5072). + - **Current boundary:** Keeps one repository; splits the installed wheel into + `loopx-core`, `loopx-workspace` and `packages/` capability distributions, + regroups `loopx/` top-level modules with compatibility shims, and pins the + top-level module count with an architecture test. No kernel semantics, + licence or schema change; multi-repository split remains a non-goal. + - [Automatic Execution Admission v0](automatic-execution-admission-v0.md) ([中文版](automatic-execution-admission-v0.zh-CN.md)) - **Delivery on `main`:** Proposal; local implementation candidate under review. diff --git a/docs/architecture/rfcs/monorepo-distribution-split-v0.md b/docs/architecture/rfcs/monorepo-distribution-split-v0.md new file mode 100644 index 000000000..9aa18b6ea --- /dev/null +++ b/docs/architecture/rfcs/monorepo-distribution-split-v0.md @@ -0,0 +1,333 @@ +# RFC: Monorepo Distribution Split (v0) + +- **RFC status:** Draft +- **Delivery maturity:** Proposal +- **Authors / owners:** LoopX maintainers +- **Created:** 2026-09-26 +- **Last normative revision:** 2026-09-26 +- **Implementation baseline:** `3e443ad7c` +- **Related contracts:** [TypeScript Control-Plane Migration v0](typescript-control-plane-migration-v0.md), [Extensions reference](../../reference/extensions.md), [Capability catalog](../../../loopx/capabilities/README.md), [Overall Roadmap v0](loopx-overall-roadmap-v0.md) (S2, S8, S12), [import-boundary tests](../../../tests/architecture/test_control_plane_import_boundaries.py) +- **Tracking issue:** [#5072](https://github.com/loopx-project/loopx/issues/5072) +- **Language mirror:** [中文版](monorepo-distribution-split-v0.zh-CN.md) + +## Document map and maintenance contract + +Sections 1–10 are the durable design and acceptance contract. Section 11 is the +normative delivery plan. Section 12 lists unresolved decisions; proposed answers +are not approval. Appendices are non-normative. This RFC ships an English +document and a `.zh-CN.md` semantic mirror; a difference between them is a +defect. + +--- + +## 1. Decision summary + +1. **The repository stays one monorepo.** One PR flow, one CI, one release + train. Splitting into several Git repositories is a non-goal for this RFC. +2. **What is installed becomes several distributions.** The single `loopx` + wheel is replaced over time by `loopx-core`, `loopx-workspace`, and + per-capability packages under `packages/`, with `loopx` retained as a + meta-package that depends on all of them. +3. **Top-level regrouping happens before any packaging change.** `loopx/chat_*` + moves to `loopx/chat/`, `loopx/*_goal_mode/` moves to `loopx/hosts/`, with + compatibility re-exports for at least one minor release. +4. **A new architecture test pins top-level growth.** The number of `loopx/*.py` + modules may only decrease. This joins the existing zero-exception + import-boundary checks. +5. **Kernel-only install must not require Node.js at import time.** The TS + effect runtime remains required for effectful commands; its absence must + surface as the existing typed `node_unavailable` diagnostic, not as an + install or import failure. +6. **Unchanged:** kernel authority, CLI compatibility baseline, extension + lifecycle rules, public/private boundary, and every stable protocol. +7. **Not approved here:** graduating any package to its own repository, changing + licences per package, or reducing any schema. + +## 2. Problem and motivation + +A developer who wants a CLI-only or kernel-only LoopX installs the same wheel as +someone running the desktop workspace, the Lark manager, and every built-in +capability. On the baseline: + +| Fact | Baseline value | +| --- | --- | +| `loopx/*.py` flat modules | 143 files, ~78.8k lines | +| `loopx/chat_*.py` modules at top level | 37 | +| `loopx/*_goal_mode/` host packages at top level | 9 | +| `loopx/control_plane` / `capabilities` / `extensions` | ~131k / ~115k / ~44k lines in one package | +| Declared Python dependencies | `dependencies = []` | +| Runtime prerequisite for every install | Node.js ≥ 22.22.3 for the effect runtime | +| Kernel/CLI/top-level modules importing `loopx.capabilities` | 52 | +| Capability modules importing `loopx.control_plane` | 74 | +| Independently packaged extensions already in `packages/` | 9 | + +Concrete failures this produces: + +- **First-use weight.** "Install the package, run `loopx dashboard`" pulls the + chat server, presentation assets, and all capability code even when the user + only wants `loopx status` for one goal. The roadmap's first-use journey + (S1/S12) cannot become light while the wheel is monolithic. +- **Undiscoverable ownership.** A contributor looking for "where does chat + live" finds 37 sibling files next to `quota.py` and `todos.py`. Module-level + CODEOWNERS and first-round reviewers cannot be expressed cleanly on a flat + namespace. +- **Two packaging conventions.** Finance, JEV, Obelisk and repo-health already + ship from `packages/` with their own `pyproject.toml` and `extension.toml`, + while 33 built-in capabilities ship inside the kernel wheel. New capability + authors have no rule for which convention to follow. +- **Unbounded top-level growth.** Import-boundary tests protect inward edges of + `control_plane`, but nothing prevents the next 40 `something_*.py` files from + landing at the top level. + +The owners of these surfaces cannot fix this locally: a capability author cannot +decide the kernel's packaging, and the kernel cannot decide which capabilities +are optional without a repository-level rule. + +### Invariants + +- I1. One repository, one PR, one CI run for any cross-package change. +- I2. Kernel truth (Goal/Todo/claim/lease/quota/effect/receipt) has exactly one + owner and one implementation path per transaction; packaging never introduces + a second copy. +- I3. `import loopx.` keeps working for at least one minor release + after a move, with a documented deprecation. +- I4. A missing optional distribution degrades to the same behaviour as the + extension "off state": core commands work, absent capabilities are reported as + unavailable, never silently substituted. +- I5. Installing the kernel-only distribution does not require Node.js; running + an effectful command without Node.js yields the typed `node_unavailable` + diagnostic. +- I6. No public/private boundary rule, schema field, or licence changes as a + side effect of a move. + +## 3. Scope and non-goals + +### In scope + +- Directory regrouping of `loopx/` top-level modules with compatibility shims. +- An architecture test that pins the `loopx/*.py` count. +- Definition of three distribution tiers and the rule for which code belongs + where. +- Migration of built-in capabilities to `packages/` behind the existing + `CapabilityRegistry` provider boundary. +- Optional-extra or bundled delivery of the Node effect runtime for + `loopx-core`. + +### Non-goals + +- Splitting into multiple Git repositories (see Section 6 and Appendix D). +- Changing licences per distribution; all tiers remain Apache-2.0 unless a + separate licensing RFC decides otherwise. +- Any change to kernel semantics, TS migration order, or stable protocols. +- Removing legacy compatibility facades (`loopx.status`, `loopx.quota`); their + retirement stays governed by the migration RFC. + +## 4. Current-system contract + +Facts audited on `3e443ad7c`: + +- `pyproject.toml` builds one distribution `loopx` with + `packages.find(include=["loopx*"])`, ships TS sources and JSON as package data + for `loopx.control_plane*`, and declares five console scripts. +- `loopx/control_plane/effect_runtime.py` probes Node at first effectful use + and already produces `node_unavailable` as a typed startup diagnostic; the + gap is that install documentation and the desktop path treat Node as a hard + prerequisite for everything. +- `tests/architecture/test_control_plane_import_boundaries.py` rejects + control-plane imports of presentation, CLI, capability, or benchmark-adapter + layers with zero exceptions. It does not constrain top-level module count or + capability → kernel imports. +- `packages/*/extension.toml` plus `loopx.extensions.manifest` define the + extension lifecycle (readiness, version, default-off, uninstall). This is the + contract capability packages must adopt; it is not redefined here. +- `docs/reference/extensions.md` already states that a capability is a product + contract while an extension is a delivery unit. This RFC applies that rule + to the kernel wheel itself. + +## 5. Proposed architecture + +### Ownership and authority + +| Distribution | Contains | Owner boundary | Depends on | +| --- | --- | --- | --- | +| `loopx-core` | `loopx/control_plane`, `loopx/cli_commands`, `loopx/semantics`, minimal host adapters under `loopx/hosts/`, `loopx check`, doctor, status | Kernel maintainers | Python ≥ 3.11; Node effect runtime as `loopx-core[runtime]` extra or bundled artifact (D1) | +| `loopx-workspace` | `loopx/chat/`, `loopx/web`, `apps/presentation`, dashboard launcher, desktop shell glue | Frontstage maintainers | `loopx-core` | +| `loopx-capability-` (in `packages/`) | one capability's contract, providers, CLI subcommands, docs | Capability owner from CODEOWNERS | `loopx-core`; optionally other capability packages | +| `loopx` (meta) | no code | Release owner | all of the above, pinned to one release train | + +Authority does not move: the kernel remains the only writer of Goal/Todo/claim/ +lease/quota/effect state. A capability package may only register providers, +capability contracts and CLI subcommands through `CapabilityRegistry`; it never +imports kernel-private modules. + +### State model and schema + +No canonical record changes. The only new durable artefact is the architecture +fixture: + +```text +tests/architecture/top_level_module_budget.json +{ "schema": "loopx_top_level_module_budget_v0", + "baseline_commit": "", + "max_top_level_modules": 143, + "allowlist": ["__init__.py", "entrypoint.py", "cli.py"] } +``` + +`max_top_level_modules` may only be lowered in a PR that also moves files. The +allowlist names modules that must remain at the top level for entry-point +reasons. + +### Command or event lifecycle + +Moves are executed per group with this fixed sequence: + +1. `git mv` the group into its package (`loopx/chat/`, `loopx/hosts/`, or + `packages/loopx-capability-/src/`). +2. Leave a shim at the old path that re-exports the public names and emits + `DeprecationWarning` once per process. +3. Lower `max_top_level_modules` by the number of moved files. +4. Run the import-boundary tests, the budget test, `loopx check` on touched + docs, and the package smoke lane. + +A move PR that fails any of these is not mergeable; a move PR that changes +behaviour is rejected as out of scope. + +### Provider or extension contract + +Capability packages adopt the existing `extension.toml` contract without +extension. The single new rule: **a capability under `loopx/capabilities/` that +has no kernel caller on `main` must move to `packages/`** before it accepts new +features. The kernel-caller inventory is the 52 import sites named in Section +2; each one is either retargeted to a registry lookup or documented as a +retained core capability (D2). + +## 6. Alternatives and design choices + +| Alternative | Why not chosen now | +| --- | --- | +| **Split into several Git repositories** | Violates I1 during the TS transaction cutover: kernel semantics change daily and downstream repos would break on every change. Multiplies CI, release and review cost while review capacity is the scarce resource. Reopen only when the kernel migration is complete and a package has independent maintainers (Appendix D). | +| **Keep one wheel, add extras only** | Reduces install weight but leaves the flat namespace and the two packaging conventions in place; contributors still cannot find boundaries. | +| **Move everything to `packages/` at once** | Breaks I3 for many callers simultaneously and mixes behavioural risk into a structural change. Per-group moves with shims are cheaper to review and to roll back. | +| **Bundle Node into `loopx-core` unconditionally** | Solves I5 but makes the kernel wheel platform-specific and large. Kept as option D1 alongside the optional-extra approach. | + +## 7. Safety, privacy, and compatibility + +- **Default-off / feature-off parity:** absent `loopx-workspace` or capability + packages behave like the existing extension off state (I4). No command + silently falls back to another implementation. +- **Public/private boundary:** unchanged. `loopx check` continues to scan every + package; moving a file does not change its scan class. +- **Legacy readers/writers:** import shims (I3) cover Python callers. Console + scripts keep their names; `loopx` meta-package keeps `pip install loopx` + working identically for one minor release. +- **Mixed versions:** the meta-package pins all tiers to one release train. + Mixed-version installs are unsupported and reported by `loopx doctor`. +- **Fail-closed:** an effectful command without the runtime extra fails with + `node_unavailable`; it never proceeds without the TS kernel. + +## 8. Migration and rollback + +| Step | Gate | Rollback | +| --- | --- | --- | +| Budget test added at current count | none; additive | delete fixture | +| `chat_*` → `loopx/chat/` with shims | import-boundary + budget + smoke green | revert PR; shims make revert a no-op for callers | +| `*_goal_mode` → `loopx/hosts/` with shims | same | same | +| Publish `loopx-core` / `loopx-workspace` / meta `loopx` | package-smoke lane installs each on a clean runner | unpublish pre-release; `loopx` meta keeps old layout one release | +| Capability package moves | per capability, kernel-caller inventory resolved | revert one package | +| Shim removal | one minor release after the move; deprecation recorded in update notes | not applicable; requires migration by callers | + +Rollback requires no data migration at any step because no canonical record +changes. + +## 9. Validation and acceptance + +| Claim | Test or evidence | Required result | Boundary / exclusions | +| --- | --- | --- | --- | +| Top-level module count never grows | `tests/architecture/test_top_level_module_budget.py` | fails when `len(loopx/*.py) > max_top_level_modules` | Does not judge module quality | +| Old imports keep working after a move | import tests for every moved public name | pass with one `DeprecationWarning` | One minor release only | +| Kernel-only install without Node | package-smoke lane on a runner without Node: `pip install loopx-core && loopx --help && loopx status --format json` | exit 0 | Effectful commands excluded | +| Effectful command without Node fails typed | same runner: `loopx heartbeat-prompt ...` | `node_unavailable` diagnostic, exit non-zero | — | +| Absent capability package = off state | install `loopx-core` only; `loopx capability list` | capability reported unavailable, core commands unaffected | — | +| No kernel truth duplicated | import-boundary tests + review of `packages/*/src` for kernel-private imports | zero violations | Static imports only, as today | +| Meta-package parity | `pip install loopx` then existing full smoke | identical to pre-split smoke | — | + +Deterministic conformance rows above are required for each milestone; no live +qualification or performance claim is made by this RFC. + +## 10. Operational contract + +- `loopx doctor` reports the installed distribution set and versions and flags + mixed release trains. +- `loopx capability list` distinguishes "not installed" from "installed, not + ready". +- Release notes name every moved module and its shim expiry release. +- No new daemon, storage or network surface is introduced. + +## 11. Normative delivery plan + +| Milestone | Shipped behavior | Entry gate | Exit evidence | Rollback | +| --- | --- | --- | --- | --- | +| M0 | Budget fixture and test at the current count; RFC index entry | this RFC merged as Draft | budget test green; count pinned | delete test | +| M1 | `loopx/chat_*` → `loopx/chat/` with shims; budget lowered | M0 | Section 9 rows 1–2 | revert | +| M2 | `loopx/*_goal_mode` → `loopx/hosts/` with shims | M0 | rows 1–2 | revert | +| M3 | `loopx-core` + `loopx-workspace` + meta `loopx` published as pre-release; Node as extra or bundle per D1 | M1, M2; D1 decided | rows 3–4, 7 | unpublish pre-release | +| M4 | First capability without kernel callers moved to `packages/` | M3; D2 inventory | rows 5–6 | revert package | +| M5 | Remaining eligible capabilities moved; shims from M1/M2 removed after one minor release | M4 | full smoke identical | not applicable | + +## 12. Open decisions + +1. **D1 — Node delivery for `loopx-core`.** Owner: kernel maintainers. Options: + (a) `loopx-core[runtime]` extra that documents Node as prerequisite; (b) + bundle a pinned Node runtime as a platform wheel; (c) both, with (a) as + default. Recommendation: (c). Evidence needed: wheel size and platform + matrix from the package-smoke lane. Deadline: before M3. +2. **D2 — Which capabilities remain in core.** Owner: kernel + capability + maintainers. Input: the 52 kernel-side import sites. Recommendation: keep + only capabilities that the Turn driver, quota or heartbeat prompt require at + runtime; everything else is a package. Deadline: before M4. +3. **D3 — Shim lifetime.** Owner: release owner. Options: one minor release + (recommended) or two. Deadline: M1. +4. **D4 — Index placement.** Whether this RFC is listed under "Control-Plane + Kernel, State, And Migration" or "Runtime, Capability, And Collaboration + Integration". Recommendation: kernel section, since the budget test and + `loopx-core` boundary are kernel-owned. + +--- + +## Appendix A: Execution ledger (non-normative) + +### 2026-09-26 — RFC opened + +- **Baseline:** `3e443ad7c` +- **Delivered:** proposal only; metrics in Section 2 measured on this baseline. +- **Evidence:** file counts from `ls loopx/*.py`, `ls loopx/chat_*.py`, + `ls -d loopx/*_goal_mode`; import sites from `rg` over `loopx/`. +- **Known gaps:** all milestones. +- **Effect on normative design:** none. + +## Appendix B: Decision log + +| Date | Decision | Owner / approval | Alternatives | Normative sections changed | +| --- | --- | --- | --- | --- | +| — | — | — | — | — | + +## Appendix C: Evidence registry + +| Evidence id | Claim | Baseline / environment | Artifact or command | Result | Privacy / validity boundary | +| --- | --- | --- | --- | --- | --- | +| E1 | 143 top-level modules | `3e443ad7c` | `ls loopx/*.py \| wc -l` | 143 | counts files, not public API | +| E2 | 52 kernel-side capability imports | `3e443ad7c` | `rg -l "loopx\.capabilities\|from \.\.capabilities\|from \.capabilities" loopx/control_plane loopx/cli_commands loopx/*.py` | 52 | static imports only | +| E3 | 74 capability → control_plane imports | `3e443ad7c` | `rg -l control_plane loopx/capabilities` | 74 | includes docs strings; upper bound | + +## Appendix D: Rejected or superseded alternatives + +**Multi-repository split.** Rejected for this RFC because it violates I1 while +the TypeScript control-plane migration is in transaction cutover, multiplies CI +and release surfaces, and requires per-repository maintainers that do not yet +exist. Evidence that could reopen the decision: migration RFC Stage 4 complete; +a `packages/` distribution with two or more non-kernel maintainers and no +cross-package breaking change for three consecutive releases. + +## Appendix E: Incident and review lessons + +None yet. diff --git a/docs/architecture/rfcs/monorepo-distribution-split-v0.zh-CN.md b/docs/architecture/rfcs/monorepo-distribution-split-v0.zh-CN.md new file mode 100644 index 000000000..7e7f9ae8f --- /dev/null +++ b/docs/architecture/rfcs/monorepo-distribution-split-v0.zh-CN.md @@ -0,0 +1,233 @@ +# RFC:Monorepo 内的发行物拆分(v0) + +- **RFC 状态:** Draft +- **交付成熟度:** Proposal +- **作者 / 负责人:** LoopX maintainers +- **创建:** 2026-09-26 +- **最近规范性修订:** 2026-09-26 +- **实现基线:** `3e443ad7c` +- **相关契约:** [TypeScript 控制面迁移 v0](typescript-control-plane-migration-v0.zh-CN.md)、[Extensions 参考](../../reference/extensions.md)、[Capability 目录](../../../loopx/capabilities/README.md)、[总体路线图 v0](loopx-overall-roadmap-v0.zh-CN.md)(S2、S8、S12)、[import 边界测试](../../../tests/architecture/test_control_plane_import_boundaries.py) +- **跟踪 issue:** [#5072](https://github.com/loopx-project/loopx/issues/5072) +- **语言镜像:** [English](monorepo-distribution-split-v0.md) + +## 文档结构与维护约定 + +第 1–10 节是持久的设计与验收契约;第 11 节是规范性交付计划;第 12 节列出未决决策,建议答案不等于批准;附录为非规范内容。本 RFC 同时提供英文版与 `.zh-CN.md` 语义镜像,两者不一致视为缺陷。 + +--- + +## 1. 决策摘要 + +1. **仓库保持单一 monorepo。** 一条 PR 流程、一套 CI、一个发布列车。拆成多个 Git 仓库是本 RFC 的非目标。 +2. **安装物拆成多个发行物。** 单一 `loopx` wheel 逐步替换为 `loopx-core`、`loopx-workspace` 以及 `packages/` 下的按 capability 打包;`loopx` 保留为依赖全部子包的 meta-package。 +3. **先做顶层目录重组,再做打包变更。** `loopx/chat_*` 迁入 `loopx/chat/`,`loopx/*_goal_mode/` 迁入 `loopx/hosts/`,旧路径保留至少一个 minor 版本的兼容 re-export。 +4. **新增一条架构测试钉住顶层增长。** `loopx/*.py` 的文件数只能减少,与现有零例外 import 边界测试并列。 +5. **仅内核安装不得在 import 阶段要求 Node.js。** TS effect runtime 对有副作用的命令仍是必需的;缺失时必须以现有的类型化 `node_unavailable` 诊断呈现,而不是安装或 import 失败。 +6. **不变:** 内核权威、CLI 兼容基线、extension 生命周期规则、公开/私有边界、全部稳定协议。 +7. **本 RFC 不批准:** 任何子包"毕业"到独立仓库、按发行物改许可证、任何 schema 缩减。 + +## 2. 问题与动机 + +只想要 CLI 或内核的开发者,安装的和运行桌面工作台、Lark 管家、全部内置 capability 的人是同一个 wheel。基线数据: + +| 事实 | 基线值 | +| --- | --- | +| `loopx/*.py` 平铺模块 | 143 个文件,约 7.88 万行 | +| 顶层 `loopx/chat_*.py` | 37 个 | +| 顶层 `loopx/*_goal_mode/` 宿主包 | 9 个 | +| `loopx/control_plane` / `capabilities` / `extensions` | 约 13.1 万 / 11.5 万 / 4.4 万行,同一包内 | +| 声明的 Python 依赖 | `dependencies = []` | +| 每次安装的运行前提 | effect runtime 需要 Node.js ≥ 22.22.3 | +| 内核/CLI/顶层模块 import `loopx.capabilities` | 52 处 | +| capability 模块 import `loopx.control_plane` | 74 处 | +| `packages/` 下已独立打包的 extension | 9 个 | + +由此产生的具体问题: + +- **首次使用过重。** "装包、跑 `loopx dashboard`" 会拉进 chat server、展示层资产和全部 capability 代码,哪怕用户只想对一个 goal 跑 `loopx status`。wheel 单体化时,路线图 S1/S12 的首次使用路径无法变轻。 +- **归属不可发现。** 想找"chat 在哪"的贡献者看到的是 37 个和 `quota.py`、`todos.py` 并排的兄弟文件。模块级 CODEOWNERS 和首轮评审人无法在平铺命名空间上清晰表达。 +- **两套打包惯例并存。** finance、JEV、Obelisk、repo-health 已经用各自的 `pyproject.toml` + `extension.toml` 从 `packages/` 发布,而 33 个内置 capability 仍在内核 wheel 里。新 capability 作者没有规则可依。 +- **顶层增长无上限。** import 边界测试保护 `control_plane` 的入边,但没有任何机制阻止下一批 40 个 `something_*.py` 落在顶层。 + +这些表面的负责人无法在本地解决:capability 作者不能决定内核的打包方式,内核也不能在没有仓库级规则的情况下决定哪些 capability 是可选的。 + +### 不变量 + +- I1. 任何跨包变更都是一个仓库、一个 PR、一次 CI。 +- I2. 内核真相(Goal/Todo/claim/lease/quota/effect/receipt)每个事务只有一个负责人、一条实现路径;打包不得引入第二份拷贝。 +- I3. 迁移后 `import loopx.<旧模块>` 至少在一个 minor 版本内继续可用,并有文档化的弃用说明。 +- I4. 缺失的可选发行物退化为与 extension "off state" 相同的行为:核心命令可用,缺失的 capability 报告为不可用,绝不静默替换。 +- I5. 安装仅内核发行物不需要 Node.js;在没有 Node.js 时运行有副作用的命令得到类型化 `node_unavailable` 诊断。 +- I6. 任何移动都不附带改变公开/私有边界规则、schema 字段或许可证。 + +## 3. 范围与非目标 + +### 范围内 + +- `loopx/` 顶层模块的目录重组与兼容 shim。 +- 钉住 `loopx/*.py` 数量的架构测试。 +- 三层发行物的定义及"哪类代码归哪层"的规则。 +- 内置 capability 经现有 `CapabilityRegistry` provider 边界迁入 `packages/`。 +- `loopx-core` 以 optional extra 或随包捆绑方式交付 Node effect runtime。 + +### 非目标 + +- 拆成多个 Git 仓库(见第 6 节与附录 D)。 +- 按发行物改许可证;除另有许可证 RFC 决定外,所有层保持 Apache-2.0。 +- 任何内核语义、TS 迁移顺序或稳定协议的变更。 +- 移除旧兼容 facade(`loopx.status`、`loopx.quota`);其退役仍由迁移 RFC 管辖。 + +## 4. 现状契约 + +在 `3e443ad7c` 上审计的事实: + +- `pyproject.toml` 以 `packages.find(include=["loopx*"])` 构建单一发行物 `loopx`,把 TS 源码与 JSON 作为 `loopx.control_plane*` 的 package data 打包,声明五个 console script。 +- `loopx/control_plane/effect_runtime.py` 在首次有副作用调用时探测 Node,并已产出类型化启动诊断 `node_unavailable`;缺口在于安装文档与桌面路径把 Node 当作一切功能的硬前提。 +- `tests/architecture/test_control_plane_import_boundaries.py` 零例外地拒绝控制面 import 展示层、CLI、capability 或 benchmark 适配层。它不约束顶层模块数量,也不约束 capability → 内核的 import。 +- `packages/*/extension.toml` 与 `loopx.extensions.manifest` 定义 extension 生命周期(readiness、版本、默认关闭、卸载)。这是 capability 包必须采用的契约,本文不重新定义。 +- `docs/reference/extensions.md` 已写明:capability 是产品契约,extension 是交付单元。本 RFC 把这条规则应用到内核 wheel 自身。 + +## 5. 提议的架构 + +### 归属与权威 + +| 发行物 | 包含 | 负责人边界 | 依赖 | +| --- | --- | --- | --- | +| `loopx-core` | `loopx/control_plane`、`loopx/cli_commands`、`loopx/semantics`、`loopx/hosts/` 下的最小宿主适配、`loopx check`、doctor、status | 内核 maintainers | Python ≥ 3.11;Node effect runtime 作为 `loopx-core[runtime]` extra 或捆绑产物(D1) | +| `loopx-workspace` | `loopx/chat/`、`loopx/web`、`apps/presentation`、dashboard launcher、桌面壳胶水 | 前台 maintainers | `loopx-core` | +| `loopx-capability-`(位于 `packages/`) | 单个 capability 的契约、provider、CLI 子命令、文档 | CODEOWNERS 中的 capability owner | `loopx-core`;可选依赖其他 capability 包 | +| `loopx`(meta) | 无代码 | 发布负责人 | 以上全部,钉在同一发布列车 | + +权威不移动:内核仍是 Goal/Todo/claim/lease/quota/effect 状态的唯一写者。capability 包只能通过 `CapabilityRegistry` 注册 provider、capability 契约和 CLI 子命令,绝不 import 内核私有模块。 + +### 状态模型与 schema + +不改任何规范记录。唯一新增的持久产物是架构 fixture: + +```text +tests/architecture/top_level_module_budget.json +{ "schema": "loopx_top_level_module_budget_v0", + "baseline_commit": "", + "max_top_level_modules": 143, + "allowlist": ["__init__.py", "entrypoint.py", "cli.py"] } +``` + +`max_top_level_modules` 只能在同时移动文件的 PR 中调低。allowlist 列出因入口点原因必须留在顶层的模块。 + +### 命令或事件生命周期 + +每组迁移按固定顺序执行: + +1. `git mv` 该组到目标包(`loopx/chat/`、`loopx/hosts/` 或 `packages/loopx-capability-/src/`)。 +2. 在旧路径留下 shim,re-export 公开名字并每进程发出一次 `DeprecationWarning`。 +3. 按迁移文件数调低 `max_top_level_modules`。 +4. 运行 import 边界测试、预算测试、对触及文档的 `loopx check`,以及 package smoke lane。 + +任一项失败的迁移 PR 不可合并;改变行为的迁移 PR 视为超出范围而拒绝。 + +### Provider 或 extension 契约 + +capability 包直接采用现有 `extension.toml` 契约,不做扩展。唯一新规则:**`loopx/capabilities/` 下在 `main` 上没有内核调用方的 capability,在接受新特性前必须先迁到 `packages/`。** 内核调用方清单即第 2 节的 52 处 import;每处要么改为 registry 查询,要么被记录为保留的核心 capability(D2)。 + +## 6. 备选方案与设计选择 + +| 备选 | 为何现在不选 | +| --- | --- | +| **拆成多个 Git 仓库** | 在 TS 事务 cutover 期间违反 I1:内核语义每天变化,下游仓库会随每次变更断裂。评审产能是稀缺资源时,它成倍放大 CI、发布与评审成本。仅在内核迁移完成且某个包有独立 maintainers 后重开(附录 D)。 | +| **保持单 wheel,只加 extras** | 能减轻安装重量,但平铺命名空间与两套打包惯例照旧;贡献者仍找不到边界。 | +| **一次性全部搬到 `packages/`** | 同时对大量调用方破坏 I3,并把行为风险混入结构性变更。按组迁移 + shim 更易评审、更易回滚。 | +| **无条件把 Node 捆进 `loopx-core`** | 解决 I5,但让内核 wheel 变成平台相关且体积大。作为 D1 选项与 optional-extra 方案并列保留。 | + +## 7. 安全、隐私与兼容 + +- **默认关闭 / 功能关闭一致性:** 缺失 `loopx-workspace` 或 capability 包时,行为等同现有 extension off state(I4)。没有命令会静默回退到另一实现。 +- **公开/私有边界:** 不变。`loopx check` 继续扫描每个包;移动文件不改变其扫描类别。 +- **旧读者/写者:** import shim(I3)覆盖 Python 调用方。console script 保持名字;`loopx` meta-package 让 `pip install loopx` 在一个 minor 版本内行为完全一致。 +- **混合版本:** meta-package 把所有层钉到同一发布列车。混合版本安装不受支持,由 `loopx doctor` 报告。 +- **fail-closed:** 没有 runtime extra 时,有副作用的命令以 `node_unavailable` 失败,绝不在没有 TS 内核的情况下继续。 + +## 8. 迁移与回滚 + +| 步骤 | 门禁 | 回滚 | +| --- | --- | --- | +| 以当前数量加入预算测试 | 无;纯新增 | 删除 fixture | +| `chat_*` → `loopx/chat/` 带 shim | import 边界 + 预算 + smoke 全绿 | revert PR;shim 使 revert 对调用方无感 | +| `*_goal_mode` → `loopx/hosts/` 带 shim | 同上 | 同上 | +| 发布 `loopx-core` / `loopx-workspace` / meta `loopx` | package-smoke lane 在干净 runner 上分别安装 | 撤下预发布;`loopx` meta 保留旧布局一个版本 | +| capability 包迁移 | 逐个 capability,内核调用方清单已解决 | revert 单个包 | +| 移除 shim | 迁移后一个 minor 版本;弃用记入 update notes | 不适用;需调用方迁移 | + +任何步骤都不需要数据迁移,因为没有规范记录改变。 + +## 9. 验证与验收 + +| 主张 | 测试或证据 | 要求结果 | 边界 / 排除 | +| --- | --- | --- | --- | +| 顶层模块数量不增长 | `tests/architecture/test_top_level_module_budget.py` | `len(loopx/*.py) > max_top_level_modules` 时失败 | 不评判模块质量 | +| 迁移后旧 import 仍可用 | 对每个迁移公开名字的 import 测试 | 通过并发出一次 `DeprecationWarning` | 仅一个 minor 版本 | +| 无 Node 的仅内核安装 | 无 Node 的 runner 上 package-smoke:`pip install loopx-core && loopx --help && loopx status --format json` | 退出码 0 | 不含有副作用命令 | +| 无 Node 的有副作用命令类型化失败 | 同一 runner:`loopx heartbeat-prompt ...` | `node_unavailable` 诊断,非零退出 | — | +| 缺失 capability 包 = off state | 仅安装 `loopx-core`;`loopx capability list` | capability 报告不可用,核心命令不受影响 | — | +| 无内核真相重复 | import 边界测试 + 审查 `packages/*/src` 是否 import 内核私有模块 | 零违规 | 与现状一致,仅静态 import | +| meta-package 一致性 | `pip install loopx` 后跑现有完整 smoke | 与拆分前 smoke 一致 | — | + +以上确定性一致性行是每个里程碑的必需项;本 RFC 不做任何实机 qualification 或性能主张。 + +## 10. 运维契约 + +- `loopx doctor` 报告已安装的发行物集合与版本,并标记混合发布列车。 +- `loopx capability list` 区分"未安装"与"已安装但未就绪"。 +- 发布说明列出每个迁移模块及其 shim 到期版本。 +- 不引入新的守护进程、存储或网络面。 + +## 11. 规范性交付计划 + +| 里程碑 | 交付行为 | 进入门禁 | 退出证据 | 回滚 | +| --- | --- | --- | --- | --- | +| M0 | 以当前数量加入预算 fixture 与测试;RFC 索引条目 | 本 RFC 以 Draft 合入 | 预算测试绿;数量钉住 | 删除测试 | +| M1 | `loopx/chat_*` → `loopx/chat/` 带 shim;预算调低 | M0 | 第 9 节第 1–2 行 | revert | +| M2 | `loopx/*_goal_mode` → `loopx/hosts/` 带 shim | M0 | 第 1–2 行 | revert | +| M3 | `loopx-core` + `loopx-workspace` + meta `loopx` 以预发布发布;Node 按 D1 以 extra 或捆绑交付 | M1、M2;D1 已决 | 第 3–4、7 行 | 撤下预发布 | +| M4 | 第一个无内核调用方的 capability 迁入 `packages/` | M3;D2 清单 | 第 5–6 行 | revert 单包 | +| M5 | 其余符合条件的 capability 迁移;M1/M2 的 shim 在一个 minor 版本后移除 | M4 | 完整 smoke 一致 | 不适用 | + +## 12. 未决决策 + +1. **D1 — `loopx-core` 的 Node 交付方式。** 负责人:内核 maintainers。选项:(a) `loopx-core[runtime]` extra,文档说明 Node 为前提;(b) 以平台 wheel 捆绑钉住版本的 Node;(c) 两者都做,(a) 为默认。建议:(c)。所需证据:package-smoke lane 的 wheel 体积与平台矩阵。截止:M3 前。 +2. **D2 — 哪些 capability 留在 core。** 负责人:内核 + capability maintainers。输入:52 处内核侧 import。建议:只保留 Turn driver、quota 或 heartbeat prompt 在运行时需要的 capability,其余全部成包。截止:M4 前。 +3. **D3 — shim 存活期。** 负责人:发布负责人。选项:一个 minor 版本(建议)或两个。截止:M1。 +4. **D4 — 索引位置。** 本 RFC 列在"控制面内核、状态与迁移"还是"运行时、Capability 与协作集成"。建议:内核章节,因为预算测试与 `loopx-core` 边界由内核负责。 + +--- + +## 附录 A:执行台账(非规范) + +### 2026-09-26 — RFC 开启 + +- **基线:** `3e443ad7c` +- **交付:** 仅提案;第 2 节指标在该基线上测量。 +- **证据:** `ls loopx/*.py`、`ls loopx/chat_*.py`、`ls -d loopx/*_goal_mode` 的文件计数;`rg` 扫描 `loopx/` 得到的 import 位置。 +- **已知缺口:** 全部里程碑。 +- **对规范性设计的影响:** 无。 + +## 附录 B:决策日志 + +| 日期 | 决策 | 负责人 / 批准 | 备选 | 变更的规范章节 | +| --- | --- | --- | --- | --- | +| — | — | — | — | — | + +## 附录 C:证据登记 + +| 证据 id | 主张 | 基线 / 环境 | 产物或命令 | 结果 | 隐私 / 有效性边界 | +| --- | --- | --- | --- | --- | --- | +| E1 | 143 个顶层模块 | `3e443ad7c` | `ls loopx/*.py \| wc -l` | 143 | 统计文件数,非公开 API | +| E2 | 52 处内核侧 capability import | `3e443ad7c` | `rg -l "loopx\.capabilities\|from \.\.capabilities\|from \.capabilities" loopx/control_plane loopx/cli_commands loopx/*.py` | 52 | 仅静态 import | +| E3 | 74 处 capability → control_plane import | `3e443ad7c` | `rg -l control_plane loopx/capabilities` | 74 | 含 docstring;为上界 | + +## 附录 D:被拒绝或被取代的备选 + +**多仓库拆分。** 本 RFC 拒绝的原因:在 TypeScript 控制面迁移处于事务 cutover 期间违反 I1,成倍放大 CI 与发布面,且需要目前不存在的按仓库 maintainers。可重开该决策的证据:迁移 RFC Stage 4 完成;某个 `packages/` 发行物拥有两位以上非内核 maintainers,且连续三个版本无跨包破坏性变更。 + +## 附录 E:事故与评审经验 + +暂无。 From 434f4c6c3d3c24f0a4ae75501b4de5f63cff25db Mon Sep 17 00:00:00 2001 From: huangruiteng <14976749+huangruiteng@users.noreply.github.com> Date: Sun, 27 Sep 2026 11:30:04 +0800 Subject: [PATCH 2/2] docs(rfc): align the monorepo-split RFC with the status index and the current baseline Rebase onto current main and repair what the RFC's own merge-time contract needs: - declare the lifecycle state the status index parses (`Accepted, proposal only`) and the required `Supersedes / closes` declaration, in both languages; - drop the lifecycle state cached in the README index entry, since STATUS.md is the generated owner of lifecycle states; - regenerate STATUS.md / STATUS.zh-CN.md; - re-measure Section 2, the Section 5 fixture example, Section 4 and Appendix C at the new base `74d372c49` (loopx/*.py 143 -> 148, loopx/chat_*.py 37 -> 38, capability -> control_plane imports 74 -> 80) and record the drift in the execution ledger, so M0 pins a count the tree actually has instead of one that is already exceeded. Tracking: #5072 Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> --- docs/architecture/rfcs/README.md | 1 - docs/architecture/rfcs/STATUS.md | 3 +- docs/architecture/rfcs/STATUS.zh-CN.md | 3 +- .../rfcs/monorepo-distribution-split-v0.md | 45 +++++++++++++------ .../monorepo-distribution-split-v0.zh-CN.md | 37 +++++++++------ 5 files changed, 58 insertions(+), 31 deletions(-) diff --git a/docs/architecture/rfcs/README.md b/docs/architecture/rfcs/README.md index ed104e587..d0f06c3e4 100644 --- a/docs/architecture/rfcs/README.md +++ b/docs/architecture/rfcs/README.md @@ -118,7 +118,6 @@ failure leaves the generated files untouched. - [Monorepo Distribution Split v0](monorepo-distribution-split-v0.md) ([中文版](monorepo-distribution-split-v0.zh-CN.md)) - - **RFC status:** Draft. - **Delivery on `main`:** Proposal only; tracking [#5072](https://github.com/loopx-project/loopx/issues/5072). - **Current boundary:** Keeps one repository; splits the installed wheel into `loopx-core`, `loopx-workspace` and `packages/` capability distributions, diff --git a/docs/architecture/rfcs/STATUS.md b/docs/architecture/rfcs/STATUS.md index 9b35c7c9d..2a1be7490 100644 --- a/docs/architecture/rfcs/STATUS.md +++ b/docs/architecture/rfcs/STATUS.md @@ -18,7 +18,7 @@ appendix may keep dated history, but no dated log heading may precede it. [中文版](STATUS.zh-CN.md) is the semantic mirror of this file. -## Accepted (38) +## Accepted (39) | RFC | Header status | Supersedes / closes | Ledger | | --- | --- | --- | --- | @@ -49,6 +49,7 @@ appendix may keep dated history, but no dated log heading may precede it. | [RFC: Long-Running Agent Reliability Diagnostics and Governed Delivery v0](long-running-agent-reliability-diagnostics-governed-delivery-v0.md) | Accepted | none | — | | [LoopX Overall Roadmap v0: Product, Collaboration, Technology and Delivery](loopx-overall-roadmap-v0.md) | Accepted | none | — | | [Manager runtime profile v0](manager-runtime-profile-v0.md) | Accepted | none | — | +| [RFC: Monorepo Distribution Split (v0)](monorepo-distribution-split-v0.md) | Accepted | none | — | | [RFC: Obelisk Session Evidence Provider v0](obelisk-session-evidence-provider-v0.md) | Accepted | none | — | | [RFC: Agent Judgment and Optional Independent Assessment — Jev as a Candidate (v0)](optional-semantic-assistance-jev-v0.md) | Accepted | none | — | | [RFC: Post-Outcome Memory Utility Attribution v0](post-outcome-memory-utility-attribution-v0.md) | Accepted | none | — | diff --git a/docs/architecture/rfcs/STATUS.zh-CN.md b/docs/architecture/rfcs/STATUS.zh-CN.md index 4812ff440..1ed8986b9 100644 --- a/docs/architecture/rfcs/STATUS.zh-CN.md +++ b/docs/architecture/rfcs/STATUS.zh-CN.md @@ -15,7 +15,7 @@ [English](STATUS.md) 与本文互为语义镜像。 -## 已接受 (38) +## 已接受 (39) | RFC | 头部状态 | 替代 / 关闭 | Ledger | | --- | --- | --- | --- | @@ -46,6 +46,7 @@ | [RFC:长程 Agent 可靠性诊断与治理交付 v0](long-running-agent-reliability-diagnostics-governed-delivery-v0.zh-CN.md) | 已接受 | 无 | — | | [LoopX 整体路线总纲 v0:产品、协作、技术与交付](loopx-overall-roadmap-v0.zh-CN.md) | 已接受 | 无 | — | | [Manager runtime profile v0 / 管家运行模式 v0](manager-runtime-profile-v0.zh-CN.md) | 已接受 | 无 | — | +| [RFC:Monorepo 内的发行物拆分(v0)](monorepo-distribution-split-v0.zh-CN.md) | 已接受 | 无 | — | | [RFC:Obelisk Session Evidence Provider v0](obelisk-session-evidence-provider-v0.zh-CN.md) | 已接受 | 无 | — | | [RFC:Agent 判断与可选独立评估——以 Jev 为候选方案(v0)](optional-semantic-assistance-jev-v0.zh-CN.md) | 已接受 | 无 | — | | [RFC:结果后记忆效用归因 v0](post-outcome-memory-utility-attribution-v0.zh-CN.md) | 已接受 | 无 | — | diff --git a/docs/architecture/rfcs/monorepo-distribution-split-v0.md b/docs/architecture/rfcs/monorepo-distribution-split-v0.md index 9aa18b6ea..28a500dc3 100644 --- a/docs/architecture/rfcs/monorepo-distribution-split-v0.md +++ b/docs/architecture/rfcs/monorepo-distribution-split-v0.md @@ -1,13 +1,14 @@ # RFC: Monorepo Distribution Split (v0) -- **RFC status:** Draft +- **RFC status:** Accepted, proposal only (open decisions D1–D4 remain unapproved) - **Delivery maturity:** Proposal - **Authors / owners:** LoopX maintainers - **Created:** 2026-09-26 -- **Last normative revision:** 2026-09-26 -- **Implementation baseline:** `3e443ad7c` +- **Last normative revision:** 2026-09-27 +- **Implementation baseline:** `2f3d13ae9` - **Related contracts:** [TypeScript Control-Plane Migration v0](typescript-control-plane-migration-v0.md), [Extensions reference](../../reference/extensions.md), [Capability catalog](../../../loopx/capabilities/README.md), [Overall Roadmap v0](loopx-overall-roadmap-v0.md) (S2, S8, S12), [import-boundary tests](../../../tests/architecture/test_control_plane_import_boundaries.py) - **Tracking issue:** [#5072](https://github.com/loopx-project/loopx/issues/5072) +- **Supersedes / closes:** none - **Language mirror:** [中文版](monorepo-distribution-split-v0.zh-CN.md) ## Document map and maintenance contract @@ -51,14 +52,14 @@ capability. On the baseline: | Fact | Baseline value | | --- | --- | -| `loopx/*.py` flat modules | 143 files, ~78.8k lines | -| `loopx/chat_*.py` modules at top level | 37 | +| `loopx/*.py` flat modules | 148 files, ~79.8k lines | +| `loopx/chat_*.py` modules at top level | 38 | | `loopx/*_goal_mode/` host packages at top level | 9 | -| `loopx/control_plane` / `capabilities` / `extensions` | ~131k / ~115k / ~44k lines in one package | +| `loopx/control_plane` / `capabilities` / `extensions` | ~130k / ~116k / ~44k lines in one package | | Declared Python dependencies | `dependencies = []` | | Runtime prerequisite for every install | Node.js ≥ 22.22.3 for the effect runtime | | Kernel/CLI/top-level modules importing `loopx.capabilities` | 52 | -| Capability modules importing `loopx.control_plane` | 74 | +| Capability modules importing `loopx.control_plane` | 80 | | Independently packaged extensions already in `packages/` | 9 | Concrete failures this produces: @@ -68,7 +69,7 @@ Concrete failures this produces: only wants `loopx status` for one goal. The roadmap's first-use journey (S1/S12) cannot become light while the wheel is monolithic. - **Undiscoverable ownership.** A contributor looking for "where does chat - live" finds 37 sibling files next to `quota.py` and `todos.py`. Module-level + live" finds 38 sibling files next to `quota.py` and `todos.py`. Module-level CODEOWNERS and first-round reviewers cannot be expressed cleanly on a flat namespace. - **Two packaging conventions.** Finance, JEV, Obelisk and repo-health already @@ -124,7 +125,7 @@ are optional without a repository-level rule. ## 4. Current-system contract -Facts audited on `3e443ad7c`: +Facts audited on `2f3d13ae9`: - `pyproject.toml` builds one distribution `loopx` with `packages.find(include=["loopx*"])`, ships TS sources and JSON as package data @@ -169,7 +170,7 @@ fixture: tests/architecture/top_level_module_budget.json { "schema": "loopx_top_level_module_budget_v0", "baseline_commit": "", - "max_top_level_modules": 143, + "max_top_level_modules": 148, "allowlist": ["__init__.py", "entrypoint.py", "cli.py"] } ``` @@ -267,7 +268,7 @@ qualification or performance claim is made by this RFC. | Milestone | Shipped behavior | Entry gate | Exit evidence | Rollback | | --- | --- | --- | --- | --- | -| M0 | Budget fixture and test at the current count; RFC index entry | this RFC merged as Draft | budget test green; count pinned | delete test | +| M0 | Budget fixture and test at the current count; RFC index entry | this RFC accepted | budget test green; count pinned | delete test | | M1 | `loopx/chat_*` → `loopx/chat/` with shims; budget lowered | M0 | Section 9 rows 1–2 | revert | | M2 | `loopx/*_goal_mode` → `loopx/hosts/` with shims | M0 | rows 1–2 | revert | | M3 | `loopx-core` + `loopx-workspace` + meta `loopx` published as pre-release; Node as extra or bundle per D1 | M1, M2; D1 decided | rows 3–4, 7 | unpublish pre-release | @@ -305,6 +306,22 @@ qualification or performance claim is made by this RFC. - **Known gaps:** all milestones. - **Effect on normative design:** none. +### 2026-09-27 — Pre-merge re-measurement at the rebased base + +- **Baseline:** `2f3d13ae9` (the rebased PR base; `main` advanced 174 commits + between the RFC being written and this merge). +- **Delivered:** no design change. Section 2 metrics, the Section 5 fixture + example, the Section 4 audit revision and the Appendix C rows were + re-measured at the new base so M0 pins a count the tree actually has. +- **Evidence:** the same commands as Appendix C E1–E3; measured drift is + `loopx/*.py` 143 → 148, `loopx/chat_*.py` 37 → 38, capability → + `control_plane` imports 74 → 80, `loopx/*.py` lines 78.8k → 79.8k. The + kernel-side capability import count (E2, 52) and the `packages/` count + (9) are unchanged. +- **Known gaps:** all milestones; D1–D4 remain open. +- **Effect on normative design:** the budget numbers in Sections 2 and 5 + follow the new base; the decision to lower the count over time is unchanged. + ## Appendix B: Decision log | Date | Decision | Owner / approval | Alternatives | Normative sections changed | @@ -315,9 +332,9 @@ qualification or performance claim is made by this RFC. | Evidence id | Claim | Baseline / environment | Artifact or command | Result | Privacy / validity boundary | | --- | --- | --- | --- | --- | --- | -| E1 | 143 top-level modules | `3e443ad7c` | `ls loopx/*.py \| wc -l` | 143 | counts files, not public API | -| E2 | 52 kernel-side capability imports | `3e443ad7c` | `rg -l "loopx\.capabilities\|from \.\.capabilities\|from \.capabilities" loopx/control_plane loopx/cli_commands loopx/*.py` | 52 | static imports only | -| E3 | 74 capability → control_plane imports | `3e443ad7c` | `rg -l control_plane loopx/capabilities` | 74 | includes docs strings; upper bound | +| E1 | 148 top-level modules | `2f3d13ae9` | `ls loopx/*.py \| wc -l` | 148 | counts files, not public API | +| E2 | 52 kernel-side capability imports | `2f3d13ae9` | `rg -l "loopx\.capabilities\|from \.\.capabilities\|from \.capabilities" loopx/control_plane loopx/cli_commands loopx/*.py` | 52 | static imports only | +| E3 | 80 capability → control_plane imports | `2f3d13ae9` | `rg -l control_plane loopx/capabilities` | 80 | includes docs strings; upper bound | ## Appendix D: Rejected or superseded alternatives diff --git a/docs/architecture/rfcs/monorepo-distribution-split-v0.zh-CN.md b/docs/architecture/rfcs/monorepo-distribution-split-v0.zh-CN.md index 7e7f9ae8f..f5c25d72a 100644 --- a/docs/architecture/rfcs/monorepo-distribution-split-v0.zh-CN.md +++ b/docs/architecture/rfcs/monorepo-distribution-split-v0.zh-CN.md @@ -1,13 +1,14 @@ # RFC:Monorepo 内的发行物拆分(v0) -- **RFC 状态:** Draft +- **RFC 状态:** 已接受(仅提案;第 12 节 D1–D4 未决,合并不等于批准) - **交付成熟度:** Proposal - **作者 / 负责人:** LoopX maintainers - **创建:** 2026-09-26 -- **最近规范性修订:** 2026-09-26 -- **实现基线:** `3e443ad7c` +- **最近规范性修订:** 2026-09-27 +- **实现基线:** `2f3d13ae9` - **相关契约:** [TypeScript 控制面迁移 v0](typescript-control-plane-migration-v0.zh-CN.md)、[Extensions 参考](../../reference/extensions.md)、[Capability 目录](../../../loopx/capabilities/README.md)、[总体路线图 v0](loopx-overall-roadmap-v0.zh-CN.md)(S2、S8、S12)、[import 边界测试](../../../tests/architecture/test_control_plane_import_boundaries.py) - **跟踪 issue:** [#5072](https://github.com/loopx-project/loopx/issues/5072) +- **替代 / 关闭:** 无 - **语言镜像:** [English](monorepo-distribution-split-v0.md) ## 文档结构与维护约定 @@ -32,20 +33,20 @@ | 事实 | 基线值 | | --- | --- | -| `loopx/*.py` 平铺模块 | 143 个文件,约 7.88 万行 | -| 顶层 `loopx/chat_*.py` | 37 个 | +| `loopx/*.py` 平铺模块 | 148 个文件,约 7.98 万行 | +| 顶层 `loopx/chat_*.py` | 38 个 | | 顶层 `loopx/*_goal_mode/` 宿主包 | 9 个 | -| `loopx/control_plane` / `capabilities` / `extensions` | 约 13.1 万 / 11.5 万 / 4.4 万行,同一包内 | +| `loopx/control_plane` / `capabilities` / `extensions` | 约 13.0 万 / 11.6 万 / 4.4 万行,同一包内 | | 声明的 Python 依赖 | `dependencies = []` | | 每次安装的运行前提 | effect runtime 需要 Node.js ≥ 22.22.3 | | 内核/CLI/顶层模块 import `loopx.capabilities` | 52 处 | -| capability 模块 import `loopx.control_plane` | 74 处 | +| capability 模块 import `loopx.control_plane` | 80 处 | | `packages/` 下已独立打包的 extension | 9 个 | 由此产生的具体问题: - **首次使用过重。** "装包、跑 `loopx dashboard`" 会拉进 chat server、展示层资产和全部 capability 代码,哪怕用户只想对一个 goal 跑 `loopx status`。wheel 单体化时,路线图 S1/S12 的首次使用路径无法变轻。 -- **归属不可发现。** 想找"chat 在哪"的贡献者看到的是 37 个和 `quota.py`、`todos.py` 并排的兄弟文件。模块级 CODEOWNERS 和首轮评审人无法在平铺命名空间上清晰表达。 +- **归属不可发现。** 想找"chat 在哪"的贡献者看到的是 38 个和 `quota.py`、`todos.py` 并排的兄弟文件。模块级 CODEOWNERS 和首轮评审人无法在平铺命名空间上清晰表达。 - **两套打包惯例并存。** finance、JEV、Obelisk、repo-health 已经用各自的 `pyproject.toml` + `extension.toml` 从 `packages/` 发布,而 33 个内置 capability 仍在内核 wheel 里。新 capability 作者没有规则可依。 - **顶层增长无上限。** import 边界测试保护 `control_plane` 的入边,但没有任何机制阻止下一批 40 个 `something_*.py` 落在顶层。 @@ -79,7 +80,7 @@ ## 4. 现状契约 -在 `3e443ad7c` 上审计的事实: +在 `2f3d13ae9` 上审计的事实: - `pyproject.toml` 以 `packages.find(include=["loopx*"])` 构建单一发行物 `loopx`,把 TS 源码与 JSON 作为 `loopx.control_plane*` 的 package data 打包,声明五个 console script。 - `loopx/control_plane/effect_runtime.py` 在首次有副作用调用时探测 Node,并已产出类型化启动诊断 `node_unavailable`;缺口在于安装文档与桌面路径把 Node 当作一切功能的硬前提。 @@ -108,7 +109,7 @@ tests/architecture/top_level_module_budget.json { "schema": "loopx_top_level_module_budget_v0", "baseline_commit": "", - "max_top_level_modules": 143, + "max_top_level_modules": 148, "allowlist": ["__init__.py", "entrypoint.py", "cli.py"] } ``` @@ -184,7 +185,7 @@ capability 包直接采用现有 `extension.toml` 契约,不做扩展。唯一 | 里程碑 | 交付行为 | 进入门禁 | 退出证据 | 回滚 | | --- | --- | --- | --- | --- | -| M0 | 以当前数量加入预算 fixture 与测试;RFC 索引条目 | 本 RFC 以 Draft 合入 | 预算测试绿;数量钉住 | 删除测试 | +| M0 | 以当前数量加入预算 fixture 与测试;RFC 索引条目 | 本 RFC 被接受 | 预算测试绿;数量钉住 | 删除测试 | | M1 | `loopx/chat_*` → `loopx/chat/` 带 shim;预算调低 | M0 | 第 9 节第 1–2 行 | revert | | M2 | `loopx/*_goal_mode` → `loopx/hosts/` 带 shim | M0 | 第 1–2 行 | revert | | M3 | `loopx-core` + `loopx-workspace` + meta `loopx` 以预发布发布;Node 按 D1 以 extra 或捆绑交付 | M1、M2;D1 已决 | 第 3–4、7 行 | 撤下预发布 | @@ -210,6 +211,14 @@ capability 包直接采用现有 `extension.toml` 契约,不做扩展。唯一 - **已知缺口:** 全部里程碑。 - **对规范性设计的影响:** 无。 +### 2026-09-27 — 合并前按新基线重测 + +- **基线:** `2f3d13ae9`(rebase 后的 PR 基线;从 RFC 撰写到本次合并之间 `main` 前进了 174 个 commit)。 +- **交付:** 设计未变。第 2 节指标、第 5 节 fixture 示例、第 4 节审计版本与附录 C 各行按新基线重测,使 M0 钉住的是代码树真实拥有的数量。 +- **证据:** 与附录 C E1–E3 相同命令;测得漂移为 `loopx/*.py` 143 → 148、`loopx/chat_*.py` 37 → 38、capability → `control_plane` 的 import 74 → 80、`loopx/*.py` 行数 7.88 万 → 7.98 万。内核侧 capability import 数(E2,52 处)与 `packages/` 数量(9)未变。 +- **已知缺口:** 全部里程碑;D1–D4 仍为未决。 +- **对规范性设计的影响:** 第 2、5 节的预算数字跟随新基线;"随时间只降不升"的决策不变。 + ## 附录 B:决策日志 | 日期 | 决策 | 负责人 / 批准 | 备选 | 变更的规范章节 | @@ -220,9 +229,9 @@ capability 包直接采用现有 `extension.toml` 契约,不做扩展。唯一 | 证据 id | 主张 | 基线 / 环境 | 产物或命令 | 结果 | 隐私 / 有效性边界 | | --- | --- | --- | --- | --- | --- | -| E1 | 143 个顶层模块 | `3e443ad7c` | `ls loopx/*.py \| wc -l` | 143 | 统计文件数,非公开 API | -| E2 | 52 处内核侧 capability import | `3e443ad7c` | `rg -l "loopx\.capabilities\|from \.\.capabilities\|from \.capabilities" loopx/control_plane loopx/cli_commands loopx/*.py` | 52 | 仅静态 import | -| E3 | 74 处 capability → control_plane import | `3e443ad7c` | `rg -l control_plane loopx/capabilities` | 74 | 含 docstring;为上界 | +| E1 | 148 个顶层模块 | `2f3d13ae9` | `ls loopx/*.py \| wc -l` | 148 | 统计文件数,非公开 API | +| E2 | 52 处内核侧 capability import | `2f3d13ae9` | `rg -l "loopx\.capabilities\|from \.\.capabilities\|from \.capabilities" loopx/control_plane loopx/cli_commands loopx/*.py` | 52 | 仅静态 import | +| E3 | 80 处 capability → control_plane import | `2f3d13ae9` | `rg -l control_plane loopx/capabilities` | 80 | 含 docstring;为上界 | ## 附录 D:被拒绝或被取代的备选