Skip to content

Commit 7e669bb

Browse files
committed
Load builtin workflow steps when assembling registry
1 parent 7aa11c5 commit 7e669bb

5 files changed

Lines changed: 69 additions & 37 deletions

File tree

‎doc/architecture/CODE_TOUR.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,8 @@ TaskSpec 遇上仓库能力,解析成一条可执行流水线。
124124
(name/kind/risk/handler)、`StepResult`(ok + 类型化 `FailureKind`,失败是
125125
**值**不是异常)、`StepContext`(handler 能触碰的一切:settings/state/
126126
params/run_dir/trace/llm)。六种失败类型走不同路由。`engine/registry.py`
127-
(39)是"名字→handler"的唯一解析点;`engine/steps/__init__.py`(34)导入
128-
即触发 `@step` 自注册、`register_builtin_steps` 刷进 registry;
127+
(39)是"名字→handler"的唯一解析点;`engine/steps/__init__.py` 延迟加载
128+
内建模块,`register_builtin_steps` 触发 `@step` 自注册并填充 registry;
129129
`engine/__init__.py`(8)导出。
130130
- **`config.py`**(418)——`Settings`(pydantic,从 `.env`/环境,永不提交),
131131
被塞进每个 `StepContext`:LLM 端点、仓库路径、引擎预算
Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,40 @@
11
# engine/steps/__init__.py —— 规范
22

3-
<!-- verified-against: 2026-08-25 -->
3+
<!-- verified-against: 2026-09-26 -->
44

55
`LOC ~34 · step 库聚合 · refactor-status: ok`
66

77
## 职责
8-
为了**注册副作用**而 import 每个 step 模块,并暴露 `register_builtin_steps`。
8+
暴露 `register_builtin_steps` 作为唯一的内建 step 模块加载与注册入口;单独
9+
import 本包不会加载写入型 workflow 模块。
910

1011
## 功能
11-
import `_common` + 全部 8 个领域模块(rebase 侧现为 `rebase_v3` + `rebase_knowledge`;
12-
从而运行它们的 `@step`/`register_step` 装饰器,
13-
填充 `_common._COLLECTED`);`register_builtin_steps` 把这份集合冲刷进一个
12+
import `_common` 而不加载领域模块。调用 `register_builtin_steps` 时显式加载
13+
8 个领域模块(rebase 侧为 `rebase_v3` + `rebase_knowledge`),触发各模块的
14+
`@step`/`register_step` 装饰器填充 `_common._COLLECTED`,然后把集合写入给定
1415
`StepRegistry`。
1516

1617
## 公开契约
1718
`register_builtin_steps(registry) -> registry`。
1819

1920
## 不变量
20-
- import 这个包会把所有 step **恰好注册一次**(靠模块缓存);对每个全新 registry,
21-
`register_builtin_steps` 是幂等的(**A4**)。
22-
- **这份 import 列表就是 step 模块的权威集合** —— 新的 step 模块必须加到这里才会被发现。
21+
- import 这个包不会加载领域模块;首次组装 registry 才加载全部内建模块。
22+
Python 模块缓存保证装饰器只运行一次;每个全新 registry 都得到同一集合(**A4**)。
23+
- **`_BUILTIN_MODULES` 就是内建 step 模块的权威集合** —— 新模块必须加入它。
2324

2425
## 边界 —— 不属于这里
2526
不含 step 逻辑;除冲刷之外不含注册策略。
2627

2728
## 依赖(允许)
28-
`engine/registry`、`._common`,以及那 8 个 step 模块。
29+
`engine/registry`、`._common`;只有调用注册函数时才导入那 8 个模块。
2930

3031
## 扩展点
31-
新的 step 领域模块 → 加进这份副作用 import 列表。
32+
新的 step 领域模块 → 加进 `_BUILTIN_MODULES`。
3233

3334
## 测试
34-
每次 `register_builtin_steps(StepRegistry())` 调用都会覆盖(冒烟:45 个 step)。
35+
隔离子进程验证 import 惰性;`register_builtin_steps(StepRegistry())` 的现有
36+
组装测试覆盖内建 step 集合。
3537

3638
## 重构备注
37-
可以考虑自动发现(遍历这个包)以去掉手工 import 列表 —— 但显式 import **可 grep**,
38-
且拼错时会大声失败,所以当前形式是可以接受的。若保持手工,这份列表 + `_COLLECTED`
39-
就是"到底存在哪些 step"的唯一来源。
39+
保留显式模块列表,避免自动发现把未经审核的 step 带入 registry;模块名拼错在
40+
组装时大声失败。`_BUILTIN_MODULES` + `_COLLECTED` 是内建 step 集合的来源。
Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,32 @@
1-
"""Vetted step library, self-registering via the `@step` decorator.
1+
"""Vetted step library, assembled explicitly when a registry is requested.
22
3-
Importing this package imports every step module for its registration side
4-
effects (each `@step` / `register_step` records a `StepSpec`);
5-
`register_builtin_steps` flushes the collected specs into a `StepRegistry`.
6-
There is no central `add(StepSpec(...))` block to keep in sync — a step's name,
7-
metadata and handler live together at its definition (see doc/architecture/CODE_TOUR.md §5).
3+
Each module still keeps its step name, metadata and handler together with
4+
`@step` / `register_step`. Importing this package alone is inert; only
5+
`register_builtin_steps` loads the vetted modules and fills a `StepRegistry`.
6+
This keeps application and SDK imports from loading every workflow effect.
87
"""
98

109
from __future__ import annotations
1110

11+
from importlib import import_module
12+
1213
from ..registry import StepRegistry
1314
from . import _common
1415

15-
# side-effect imports: each module registers its steps into the collection.
16-
from . import ( # noqa: F401,E402
17-
workspace,
18-
review,
19-
report,
20-
pr,
21-
issue,
22-
profile,
23-
rebase_v3,
24-
rebase_knowledge,
16+
_BUILTIN_MODULES = (
17+
"workspace", "review", "report", "pr", "issue", "profile",
18+
"rebase_v3", "rebase_knowledge",
2519
)
2620

2721

2822
def register_builtin_steps(registry: StepRegistry) -> StepRegistry:
29-
"""Flush every step collected by import side effects into `registry` and
30-
return it. Called once at startup, after this package's module imports have
31-
populated the collection; the registry then holds the full vetted library."""
23+
"""Load the vetted modules and install their collected specs into `registry`.
24+
25+
Module imports are cached by Python, so repeated registry assembly uses
26+
the same step definitions without a second registration side effect.
27+
"""
28+
for name in _BUILTIN_MODULES:
29+
import_module(f"{__name__}.{name}")
3230
for spec in _common.collected():
3331
registry.register(spec)
3432
return registry

‎src/infermatrix_copilot/engine/steps/_common.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
55
1. the `@step` / `register_step` **self-registration** surface — decorating a
66
handler (or calling `register_step` for factory-built handlers) records a
7-
`StepSpec` into the package-level collection; `steps.register_builtin_steps`
8-
flushes that collection into a `StepRegistry`. There is no separate
7+
`StepSpec` into the package-level collection when its module is loaded;
8+
`steps.register_builtin_steps` loads the vetted modules and flushes that
9+
collection into a `StepRegistry`. There is no separate
910
`add(StepSpec(...))` block to keep in sync anymore.
1011
2. the cross-module helpers every step file shares (`gh`, `repo_path`, `git`,
1112
`task_spec`, `gh_read_tools`, `post_step`) — one home instead of a
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""Importing the registry facade must not load workflow effect modules."""
2+
3+
import os
4+
import subprocess
5+
import sys
6+
from pathlib import Path
7+
8+
9+
def test_builtin_steps_load_only_when_registry_is_assembled():
10+
source = Path(__file__).resolve().parents[1] / "src"
11+
code = """
12+
import sys
13+
from infermatrix_copilot.engine.steps import register_builtin_steps
14+
from infermatrix_copilot.engine.registry import StepRegistry
15+
16+
prefix = 'infermatrix_copilot.engine.steps.'
17+
for name in ('workspace', 'review', 'report', 'pr', 'issue', 'profile',
18+
'rebase_v3', 'rebase_knowledge'):
19+
assert prefix + name not in sys.modules, name
20+
21+
registry = register_builtin_steps(StepRegistry())
22+
assert {'workspace.guard_clean', 'review.patch_gate', 'pr.fetch_diff'} <= set(registry.names())
23+
for name in ('workspace', 'review', 'report', 'pr', 'issue', 'profile',
24+
'rebase_v3', 'rebase_knowledge'):
25+
assert prefix + name in sys.modules, name
26+
"""
27+
result = subprocess.run(
28+
[sys.executable, "-c", code],
29+
env={**os.environ, "PYTHONPATH": str(source)},
30+
capture_output=True, text=True, timeout=30,
31+
)
32+
assert result.returncode == 0, result.stderr

0 commit comments

Comments
 (0)