Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,63 @@ jobs:
/tmp/clean/bin/pip install --quiet dist/*.whl
cd /tmp # import the INSTALLED package, never the source checkout
/tmp/clean/bin/python - <<'PY'
import importlib.metadata
import importlib.resources
import sys
import tempfile
from pathlib import Path
from infermatrix_copilot import __version__
from infermatrix_copilot.sdk.v1 import (
ChangedPath, DirectClient, DirectReviewRequest,
KNOWLEDGE_API_VERSION,
KnowledgeCurator, KnowledgeEvidenceBatch,
KnowledgeEvidenceEvent, RepositoryRef, StrictRuntime,
get_capabilities,
)
forbidden = {
"infermatrix_copilot.config",
"infermatrix_copilot.contract",
"infermatrix_copilot.direct_routing",
"infermatrix_copilot.mcp_server",
}
loaded = sorted(forbidden.intersection(sys.modules))
if loaded:
sys.exit(f"public SDK import loaded private implementation: {loaded}")
caps = get_capabilities()
dist_version = importlib.metadata.version("infermatrix-copilot")
if not (__version__ == dist_version == caps.distribution_version == "0.2.0"):
sys.exit(
"version mismatch: "
f"module={__version__}, metadata={dist_version}, "
f"capabilities={caps.distribution_version}"
)
if not caps.resource_revision.startswith("sha256:"):
sys.exit("SDK capabilities have no resource revision")
if not caps.supports_knowledge_curation:
sys.exit("SDK capabilities omit knowledge curation")
if not (
caps.knowledge_api_version == KNOWLEDGE_API_VERSION == "1.0.0"
):
sys.exit("installed SDK knowledge API version mismatch")
if KnowledgeCurator.proposal_schema(max_rules=3)["properties"][
"rules"
]["maxItems"] != 3:
sys.exit("installed SDK has a broken knowledge proposal schema")
evidence = KnowledgeEvidenceBatch(
batch_id="wheel-smoke",
repository=RepositoryRef(alias="vllm-omni"),
events=(KnowledgeEvidenceEvent(
event_id="event-1", source_kind="merged_pr",
source_reference="PR #1", title="wheel smoke",
),),
)
if evidence.to_dict()["events"][0]["event_id"] != "event-1":
sys.exit("installed SDK knowledge models are not serializable")
if not importlib.resources.files("infermatrix_copilot").joinpath(
"py.typed"
).is_file():
sys.exit("wheel does not ship py.typed")

from infermatrix_copilot.config import _resource_dir, _RESOURCE_MARKERS
# resolve through the production probe and require its marker to exist:
# a present-but-empty directory would satisfy a bare is_dir() check
Expand All @@ -79,6 +135,33 @@ jobs:
bad.append(f"{name}: {d} has no {marker}")
if bad:
sys.exit("wheel runtime data broken: " + "; ".join(bad))
client = DirectClient()
for repo, changed in (
("vllm-omni", "vllm_omni/core/sched/scheduler.py"),
("afd-plugin", "afd_plugin/compat/vllm.py"),
):
plan = client.plan(DirectReviewRequest(
review_id="wheel-smoke",
repository=RepositoryRef(alias=repo),
pr_number=1,
expected_head_sha="a" * 40,
title="runtime change",
body="exercise the installed SDK",
changed_paths=(ChangedPath(changed),),
))
if not plan.knowledge_routes:
sys.exit(f"installed SDK did not route {repo}")
if any("/" == ref.document.document_id[:1]
for ref in plan.knowledge_routes):
sys.exit("installed SDK leaked an absolute document path")
if Path(plan.navigation_policy["fallback_document_id"]).is_absolute():
sys.exit("installed SDK leaked an absolute fallback path")
with tempfile.TemporaryDirectory() as run_root:
with StrictRuntime(settings_overrides={
"_env_file": None, "run_root": run_root,
}) as strict:
if strict.capabilities().distribution_version != "0.2.0":
sys.exit("Strict facade capabilities mismatch")
print("packaged runtime data resolves via _resource_dir:",
", ".join(_RESOURCE_MARKERS))
PY
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,13 @@ python knowledge/tools/check_knowledge_tree.py
python knowledge/tools/check_wiki_lint.py
```

自动知识回流也遵循同一条边界:本仓库发布的
`infermatrix_copilot.sdk.v1.KnowledgeCurator` 是 catalog、受信 prompt、proposal
shape/ID/page/source 校验、append-only apply 与两道 validator rollback 的唯一实现;
ReviewBot 只把 evidence 和 model JSON 接到该 API,并继续拥有 dedicated clone、
ledger/retry、Git commit、fork push、PR 与 schedule。SDK 不 clone、不调 model、
不发布,也不会写 wheel 内的 knowledge 副本。

## 快速上手 4 · CI 修复(imcifix)

从 GitHub issue 出发:本地复现、最小修复、针对性验证;默认不 commit、
Expand Down
2 changes: 1 addition & 1 deletion doc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
| [`features/strict-review-deep-engine.md`](features/strict-review-deep-engine.md) | 已实现(默认开) |
| [`features/review-recall.md`](features/review-recall.md) | 已实现(默认开)——v14/v15 召回攻坚 |
| [`features/auto-run.md`](features/auto-run.md) | **draft,未实现**——GitHub 事件触发 |
| [`RFC-knowledge-intake.md`](RFC-knowledge-intake.md) | 已实现(默认关,shadow 起步)——合并 PR 与 bugfix 学习经 reviewbot 蒸馏,以 fork PR 回流知识库,人审合并即晋升 |
| [`RFC-knowledge-intake.md`](RFC-knowledge-intake.md) | 已实现(默认关,shadow 起步)——Copilot typed SDK 唯一拥有知识规则,ReviewBot 只编排 evidence/model/Git/fork PR,人审合并即晋升 |

## architecture/ —— 维护者

Expand Down
116 changes: 83 additions & 33 deletions doc/RFC-knowledge-intake.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
# RFC — Knowledge intake: merged-PR and bugfix learnings return as reviewed knowledge PRs

- Status: implemented, shadow-first and default-off — copilot side in
PR #110 (`pr.harvest_debug_knowledge`), reviewbot side in
zuiho-kai/omni-reviewbot#21 (intake ledger) and #22 (daily distiller)
- Owner: knowledge plane (`knowledge/`, `AGENTS.md` contract) on this side;
`omni_reviewbot.knowledge_intake` / `knowledge_distiller` on the bot side
- Status: implemented, shadow-first and default-off — producer intake in
PR #110 (`pr.harvest_debug_knowledge`), provider domain contract in public
SDK 0.2.0 (`infermatrix_copilot.sdk.v1.KnowledgeCurator`), and ReviewBot
orchestration in zuiho-kai/omni-reviewbot#21/#22
- Owner: this repo uniquely owns knowledge policy, catalog/prompt/proposal/apply
semantics and validators; ReviewBot owns evidence/model/Git/ledger/publication
orchestration and consumes the versioned public SDK only
- Evidence: `test/test_knowledge_harvest.py` (step + executor/resume e2e),
`test/test_sdk_knowledge_v1.py` (provider boundary, rollback and locking),
reviewbot `tests/test_knowledge_intake.py`, `tests/test_knowledge_distiller.py`,
`tests/test_e2e_knowledge_flow.py` (cross-repo contract pinned)

Expand Down Expand Up @@ -54,23 +57,29 @@ Three stages, deliberately decoupled so each can fail without losing events.

### 2. Distillation (expensive, at most once a day)

- The model **only proposes**: it reads a dedicated work clone of this repo
(never the live checkout the Direct bridge imports) and returns
catalog-constrained structured output — target `rules.md` page from the
scanned catalog, a new auditable rule id, one complete section in the
page's own language and format, cited sources. Source events are fenced
as `<untrusted_data>`; instructions never come from them.
- Code **applies mechanically and append-only**: new sections plus an
`updated:` frontmatter bump. Existing sections are never rewritten —
merging and pruning stay human/curator work. Proposals that miss the
catalog, collide with an existing rule id, or fail shape checks are
dropped and counted.
- The **validators are the gate**: `check_knowledge_tree.py` and
`check_wiki_lint.py` run in the clone; a failure — or a missing
validator — fails the batch closed. Rows stay `pending` and retry on the
next daily batch, bounded by an attempt cap that parks poison rows
visibly as `error`. The vllm-omni release audit runs in CI on the PR, as
it does for any knowledge edit.
- ReviewBot converts pending rows into typed `KnowledgeEvidenceEvent` values
and one bounded `KnowledgeEvidenceBatch`. It creates `KnowledgeCurator`
over a dedicated work clone of this repo — never the immutable knowledge
resources shipped inside the runtime wheel — then asks the SDK to build the catalog-constrained
prompt and strict JSON schema. ReviewBot invokes the configured model; the
model **only proposes**.
- The SDK treats all source events and model output as untrusted. Evidence is
fenced as `<untrusted_data>`; catalog membership, proposal shape, rule ID,
one complete heading, source citations, duplicate IDs and target-page SHA
are checked mechanically. Accepted proposals receive content-addressed IDs;
rejected model indexes and stable reasons are returned to the host.
- `KnowledgeCurator.apply()` **applies mechanically and append-only**: complete
new sections plus the one `updated:` frontmatter bump. Existing sections are
never rewritten. The SDK serializes writers across threads/processes and
rechecks target SHAs after acquiring the lock, so a stale daily worker cannot
overwrite a concurrent edit.
- The **validators are the gate**: the SDK runs exactly
`check_knowledge_tree.py`, then `check_wiki_lint.py`, inside the clone. A
missing validator fails before any write; a failed, timed-out or unlaunchable
validator restores every target page byte-for-byte and raises a typed error.
ReviewBot leaves rows `pending` for retry, bounded by its attempt cap. The
vllm-omni release audit additionally runs in CI on the PR, as for any
knowledge edit.

### 3. Publication (fork-based, double-gated)

Expand Down Expand Up @@ -100,9 +109,13 @@ Three stages, deliberately decoupled so each can fail without losing events.
branch recovery was considered and skipped: it trades real complexity
for saving one day of latency on an already-daily loop.

## The cross-repo contract
## The cross-repo contracts

The drop record is the only coupling between the two repos:
There are two narrow, versioned seams; neither repo imports the other's private
modules.

The first is the producer drop record written by a Copilot run and consumed by
ReviewBot:

```json
{"run_id": "...", "repo": "owner/repo", "pr": 123, "kind": "bugfix_run",
Expand All @@ -122,6 +135,33 @@ The drop record is the only coupling between the two repos:
side's test but not the other's; the contract therefore changes only in
lockstep, with this section as the reference.

The second is the installed public Python SDK used by ReviewBot's adapter. The
call order is deliberately small:

```python
curator = KnowledgeCurator(dedicated_clone)
batch = KnowledgeEvidenceBatch(...)
prompt = curator.build_prompt(batch)
model_json = reviewbot_model_call(
prompt, output_schema=curator.proposal_schema(max_rules=batch.max_rules)
)
validation = curator.validate_proposals(model_json, batch)
result = curator.apply(validation)
```

All values crossing that seam have lossless `to_dict()` projections and contain
document IDs, never provider absolute paths. ReviewBot uses `accepted_indexes`
and `rejected_indexes` to account for model rows. Malformed requests raise
`InvalidRequestError`; catalog/integrity/stale-writer failures raise
`KnowledgeCurationError`; validator failures raise `KnowledgeValidatorError`
whose `.result` records validator status/output and whether bytes were rolled
back. ReviewBot catches these public errors and owns retry/artifact decisions.

The SDK explicitly has no clone, model, ledger, commit, push, pull-request or
scheduling operation. Conversely, ReviewBot has no local catalog/prompt/parser/
apply/validator implementation: knowledge policy changes are released here and
adopted by pinning a new wheel version.

## Fit with this repo's invariants

- **Repo neutrality**: the harvest step contains no repo literal; the drop
Expand All @@ -132,6 +172,9 @@ The drop record is the only coupling between the two repos:
- **Read-wide/write-narrow**: the tree is still written only by humans
merging PRs; the automation's write surface is a drop directory and a
fork.
- **Single policy owner**: knowledge domain rules live behind this repo's
public SDK; the ReviewBot adapter is orchestration-only. Runtime state and
publication credentials never flow into the provider library.

## Alternatives considered

Expand All @@ -141,26 +184,33 @@ The drop record is the only coupling between the two repos:
- **Direct writes to the tree (or pushing to this repo)** — rejected
outright: it deletes the human promotion step the knowledge plane is
built on, and would require granting the bot write access here.
- **Copilot-hosted distiller** — this repo has no daemon; the reviewbot
already runs a poll loop with maintenance gating, budgets, and crash
recovery, so the scheduled half lives there and this repo stays a
library plus one playbook step.
- **Copilot-hosted daemon/orchestrator** — rejected: this repo has no daemon;
ReviewBot already runs a poll loop with maintenance gating, budgets and crash
recovery. **Copilot-owned domain library** is the chosen split: policy stays
beside the governed knowledge tree without moving scheduling or publication.
- **Duplicated distillation helpers in ReviewBot** — rejected: catalog, prompt,
validation and append logic would drift independently from `knowledge/`
governance. The versioned SDK makes that dependency explicit and testable.
- **Stopping at skill candidates / debug memory** — that is where learnings
already go today; both are run-scoped proposals with no path into the
human-curated tree. This design is that missing path, not a replacement
for them.

## Rollout

1. Reviewbot: `KNOWLEDGE_INTAKE_ENABLED=true` — merged-PR recording plus
daily shadow batches; inspect artifacts and `knowledge-batch` output.
2. Wire the bugfix channel: point this repo's `KNOWLEDGE_INTAKE_DIR` and
1. Build and pin InferMatrixCopilot SDK `0.2.0` in ReviewBot; its adapter must
import only `infermatrix_copilot.sdk.v1` and exercise the full call order in
a temp-clone integration test.
2. ReviewBot: `KNOWLEDGE_INTAKE_ENABLED=true` — merged-PR recording plus daily
shadow batches; inspect typed curation results, artifacts and
`knowledge-batch` output.
3. Wire the bugfix channel: point this repo's `KNOWLEDGE_INTAKE_DIR` and
the reviewbot's `COPILOT_INTAKE_DIR` at the **same directory** (v1
assumes a shared host). Without this pair, everything else works but
`pr_debug` learnings are silently absent from batches — both values
default to empty/off.
3. Create the bot's fork of this repo; set `KNOWLEDGE_FORK_SLUG`.
4. `KNOWLEDGE_PR_ENABLED=true` under `POST_MODE=review` — PRs start; every
4. Create the bot's fork of this repo; set `KNOWLEDGE_FORK_SLUG`.
5. `KNOWLEDGE_PR_ENABLED=true` under `POST_MODE=review` — PRs start; every
one is reviewed like any other knowledge edit.

Rollback at any stage is turning the flag off; nothing in the tree changes
Expand Down
26 changes: 17 additions & 9 deletions doc/architecture/SPEC/contract.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,34 @@
# contract.py —— 规范

<!-- verified-against: 2026-08-28 -->
<!-- verified-against: 2026-08-31 -->

`LOC ~181 · 跨仓库公开契约面 · refactor-status: ok`
`LOC ~185 · 旧版跨仓库契约兼容层 · refactor-status: compatibility-shim`

## 职责
本 copilot 的**对外消费契约**:一个 review bot(或任何宿主)被允许 import 的
一切都住在这里,此外的任何 import 都不受支持。它的存在源于一次真实事故:
本 copilot 的**旧版对外消费契约兼容层**。新宿主只允许 import
`infermatrix_copilot.sdk.v1`;本模块继续服务既有 MCP 与尚未迁移的调用者。
它的存在源于一次真实事故:
下游仓库曾经用 `importlib` 伸进 `thin_mcp_server` 拿四个 `_direct_*` 私有名,
一次重命名就让另一个仓库在运行期断裂、且没有任何构建期信号。

## 公开契约(`__all__`)
`DIRECT_API_VERSION` / `STRICT_API_VERSION`(`"1.0.0"`,形状变化到消费方
必须察觉时递增);`capabilities(max_strict_workers=1,
supports_file_locking=True) -> dict`(版本/能力握手:`supports_expected_head`、
`SDK_API_VERSION` / `DIRECT_API_VERSION` / `STRICT_API_VERSION` /
`KNOWLEDGE_API_VERSION`(当前均为 `"1.0.0"`,且都从 `sdk.v1.models`
取唯一值)。`capabilities(max_strict_workers=1,
supports_file_locking=True) -> dict` 委托 SDK typed handshake 再投影为兼容
dict;它包含 distribution/SDK/Direct/Strict/Knowledge 版本、resource
revision、supported repositories,以及 `supports_expected_head`、
`supports_structured_result`、`supports_post_false`、`supports_file_locking`、
`max_strict_workers`);`build_review_result(run_dir) -> dict`(结构化评审
`supports_idempotent_strict_start`、`supports_knowledge_curation`、
`max_strict_workers`。其余兼容导出包括 `build_review_result(run_dir) -> dict`(结构化评审
结果:`contract_version`、`run_id`、`state`、`reviewed_head_sha`、`verdict`、
`summary_markdown`、`comments`、`stale`/`expected_head_sha`/
`actual_head_sha`、`diagnostics`);`unknown_run_result(run_id)`(显式
`state: unknown`,绝不抛错 —— 丢响应和还在跑必须可区分);
`sanitize_comments` 与 `COMMENT_FIELDS`;以及从 `direct_routing` 再导出的
`direct_knowledge_routes` / `direct_execution_budget` /
`direct_completion_result` / `direct_mandatory_review_guides`。
`direct_completion_result` / `direct_mandatory_review_guides` / 完整的
`direct_review_plan`。

## 不变量
- **依赖方向单向、由测试钉住**:本模块 import 数据层(`run_status`、
Expand All @@ -38,6 +44,8 @@ supports_file_locking=True) -> dict`(版本/能力握手:`supports_expected_
(`_verified`、`corroborated_by` 等)绝不泄进消费方输出。
- 本模块自身保持仓库中立;仓库专属的 Direct 路由表住在
`direct_routing.py`(见其页)。
- 能力身份的权威实现在 `sdk.v1.get_capabilities`;这里不维护第二份版本或
resource-revision 算法。

## 边界 —— 不属于这里
不调模型、不执行 run、不做策略强制(`mcp_policy`)、不实现 Direct 路由
Expand Down
Loading
Loading