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
18 changes: 18 additions & 0 deletions doc/architecture/SPEC/app/core.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# app/core.py —— 规范

<!-- verified-against: 2026-09-26 -->

`Copilot` is the headless workflow application. It resolves a TaskSpec,
enforces plan review and confirmation, executes or resumes a playbook, and
reserves/executes durable runs. The CLI imports it through `cli.copilot` for
compatibility; the application never imports CLI or MCP transports.

The pre-execution gate runs before a normal CLI run directory is created.
Reserved runs persist a canonical request and ownership/idempotency state
before child execution. The child rechecks the read-only policy and checkout
binding; a run ID must stay contained under the configured run root. Existing
checkpoint keys, run IDs, and blocked exit codes remain stable.

`GateOutcome`, `_mode_review_context`, and `Copilot` retain their import path
through the compatibility module. Only orchestration belongs here; command
argument parsing and stdio protocol registration remain transport concerns.
22 changes: 22 additions & 0 deletions doc/architecture/SPEC/app/run_service.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# app/run_service.py —— 规范

<!-- verified-against: 2026-09-26 -->

`RunService` is the durable application boundary shared by the embedded
Strict SDK and MCP transport. It owns policy-checked reserve/start, one
serialized worker, isolated child launch, startup/orphan reconciliation,
readiness, bounded status/result polling, and repository-scoped knowledge
reads. Construction and polling require no CLI or MCP module import.

Reservations return `(run_id, created)` and enqueue only newly created runs.
The worker launches an isolated subprocess with posting and pushing disabled,
then reconciles the persisted status. A run can survive host restart; closing
a service only removes its liveness token and must not cancel a durable child.
The child rechecks repository/path/read-only policy against the same explicit
roots as the parent. Polling an unknown valid run ID reports `unknown`;
malformed or escaping IDs fail validation. Structured results and capped
report pages remain available after terminal status.

`mcp_server.CopilotMCP` remains a compatibility alias. The MCP server owns
tool registration and protocol error projection, while the SDK owns typed
request/result projection. Neither transport owns the run queue.
7 changes: 3 additions & 4 deletions doc/architecture/SPEC/cli.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# cli/ —— 规范

<!-- verified-against: 2026-09-06 -->
<!-- verified-against: 2026-09-26 -->

`LOC ~1420(6 个文件) · 接口 + 编排门面 · refactor-status: ok`

Expand All @@ -12,11 +12,10 @@ flag CLI 与 `Copilot` 门面:解析 → 过门 → 执行;并持有 run 目
## 包内布局(一个文件一个关注点)
- `__init__.py` —— 只再导出 `Copilot`、`main`(见下方公开面);无逻辑。
- `__main__.py` —— `python -m infermatrix_copilot.cli` 的对等入口。
- `copilot.py` —— `Copilot` 编排器(resolve/run_task/run_playbook/run_queue/
resume_last/_execute + 内置命令)。
- `copilot.py` —— 兼容导出 `app.core.Copilot`;编排实现不再属于 CLI。
- `entry.py` —— `argparse`、`_handle_line`、`main`(把 argv/stdin 变成对 `Copilot`
的调用)。
- `utils.py` —— 纯格式化器:`parse_task_params`、`format_metrics_line`。
- `utils.py` —— `parse_task_params` 和共享 metrics 格式化器的兼容导出。
- `doctor.py` —— 预检诊断(2026-07 新增):逐项 ✓/✗,每个失败给出**唯一**确切的修复命令。
- 子命令:`doctor` 与 `migrate-knowledge`(PR4d 部署期知识迁移;**显式 owner
动作,零 LLM**,需 `--repo <name>`,支持 report-only;见 RUNBOOK)。
Expand Down
58 changes: 13 additions & 45 deletions doc/architecture/SPEC/mcp_server.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# mcp_server.py —— 规范

<!-- verified-against: 2026-08-31 -->
<!-- verified-against: 2026-09-26 -->

`LOC ~506 · Strict 后台机器(reserve/start/poll) · refactor-status: ok`
`MCP 协议适配器 · refactor-status: ok`

## 职责
为 MCP 宿主运行 Strict 工作流:预约、拉起、跟踪、供给结果 ——
**且绝不阻塞一次同步工具调用**。
把 MCP 工具请求映射到 `app.run_service.RunService`。预约、worker、执行与
轮询的应用实现由该服务持有;这里仅注册工具并投影协议错误。

## 功能
评审要跑 5–12 分钟,任何同步 MCP 调用都会超时,所以接口是**start/poll 工具对**:
Expand All @@ -15,7 +15,7 @@
和 `get_status`(`run_status` + `progress`)负责轮询。

## 公开契约
`CopilotMCP`、`build_mcp()`、那组 start/poll 工具,外加
`CopilotMCP` 是 `RunService` 的兼容别名;`build_mcp()` 注册 start/poll 工具,外加
`get_capabilities()`(包 `contract.capabilities`,含
`MAX_STRICT_WORKERS=1` 与文件锁能力上报)。`start_review` 接受
`expected_head_sha`;`start_strict_review` 接受 `idempotency_key` 并把
Expand All @@ -28,50 +28,18 @@
`pr-quality` playbook。

## 不变量(**C2**、**C3**、**E1**)
- **安全是结构性的,不是"信任宿主"。** `enforce_mcp_policy` 在这里跑一次,
**并且**在子进程里(权威地)再跑一次,因此只有 `READ_ONLY_KINDS` 会被执行、
`post` 恒为 `False`、只有 allowlist 内的仓库可达 —— **无论被篡改的
`request.json` 声称什么**。
- **每次 run 都是一个隔离子进程**
(`python -m infermatrix_copilot --execute-reserved <id>`)。两个理由都很关键:
copilot 的 stdout 必须离开本进程的 JSON-RPC stdio 通道(子进程 stdout →
`<run_dir>/console.log`),且进程级全局 tracer / `last_run_dir` **由此天然按 run 隔离**。
- **run 由单个 worker 线程串行执行。** worker 等待子进程后把退出码交给对账:
退出码 3 且无终态即 lock-loser 签名(真正 BLOCKED 的子进程会先写终态再退出),
以 `suspect_lock_loser` 传入 `reconcile_after_wait`。
- 跨重启、跨多个并发 server 的轮询正确性,来自 `run_status.py` 的持久记录 + 按属主
对账,**而不是内存状态**。
- **`mcp` SDK 是可选 import**,藏在 `[mcp]` extra 之后,且**绝不能**被核心包 import
—— 纯 CLI 安装保持零依赖。
- `build_mcp()` 暴露 V1 工具面(autonomous 工作流执行器);它**默认不注册**。
- **子进程环境闸恒关**:`_launch` 对**每个**子进程(含 Strict)无条件写
`ALLOW_POST="0"` —— 给策略的"拒绝而非恢复"(`mcp_policy.md`)系上第二道
背带;同时转发 `MCP_ALLOWED_REPO_ROOTS`,让子进程的
`authorize_repo_path` 复检对着父进程判过的同一组根。
- **入队只发生在 `created` 为真时**:`reserve_run` 现在返回
`(run_id, created)` —— 重试命中既有键只返回既有 run,绝不第二次入队
(配合 `run_status.claim_for_execution` 的 CAS,双保险关掉重复执行)。
- **`configure_strict_repo` 已删除**(进程全局 `settings.repo_paths` 突变):
替代是 `strict_readiness(repo, repo_path="")` 按调用校验 —— 两个并发
Strict 请求各带各的 checkout,再无共享状态可互踩。
- **回收是尽力而为、绝不拦路**:启动时与每次 run 终局后跑
`idempotency.reap_stale`;一次清不完的扫描绝不阻止 server 服务或 run
完成。
- **轮询对未知 id 给结构化答案**:`get_result` 对形状合法但不存在的
run_id 返回 `contract.unknown_run_result`(`state: unknown`)而不是抛错
—— 丢响应和还在跑可区分;终局响应同时附带
`contract.build_review_result` 的结构化 `result`(分页 `report` 保留)。
- `pr_quality` 与 Strict 共用同一个串行 worker 和隔离子进程,但用普通的
`--execute-reserved` 权威复检;结果由 `get_quality_result` 装配为独立质量契约,
不会把 code-review findings 数量误当质量结论。
- `build_mcp()` 只注册协议工具和转换拒绝;不创建第二个执行器。
- `mcp` SDK 在 `build_mcp()` 内才加载;SDK 和核心包不依赖可选 MCP 安装。
- `CopilotMCP` 兼容别名指向 `RunService`,所以旧调用仍共享同一策略、
幂等与轮询语义。队列、隔离子进程、双重策略复检及恢复规则详见
[`app/run_service.md`](app/run_service.md)。

## 边界 —— 不属于这里
不含 Direct 模式逻辑(`thin_mcp_server.py`);不定义策略(`mcp_policy.py`);
不定义状态文件格式(`run_status.py`)。

## 依赖(允许)
stdlib + `mcp`(可选 extra)+ `.mcp_policy` + `.run_status` + `.cli`
+ `.contract` + `.idempotency`。
stdlib + `mcp`(仅构造时可选加载)+ `.app.run_service` + `.mcp_policy`。

## 测试
`test_mcp.py`(篡改防御、单写者对账、分页、只读工具集、快照绑定转发、
Expand All @@ -81,5 +49,5 @@ stdlib + `mcp`(可选 extra)+ `.mcp_policy` + `.run_status` + `.cli`
第二个子进程 no-op、post 拒绝)。

## 重构备注
预约形状(先建 run 目录,再规划)是 **MCP 专属**的;CLI 主路径仍然在**建目录之前**
过门,所以被放弃的计划不会留下目录。**不要在没有保住这个差别的前提下把两者统一。**
预约形状(先建 run 目录,再规划)由 MCP 和 embedded Strict SDK 共用;CLI
主路径仍在建目录之前过门。不要把这两种不同的执行入口混为一谈。
3 changes: 2 additions & 1 deletion doc/architecture/SPEC/metrics.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# metrics.py —— 规范

<!-- verified-against: 2026-09-06 -->
<!-- verified-against: 2026-09-26 -->

`LOC ~493 · 跨切(测量) · refactor-status: oversized`

Expand All @@ -14,6 +14,7 @@ S(由 incident 得出的安全系数)、C(RQS3e 式的对数成本,基

## 公开契约
`collect_run_metrics(run_dir, settings, status) -> {quality, risk, cost, catq}`。
`format_metrics_line` 为应用和 CLI 共用的纯格式化函数,不参与评分计算。

## 不变量
- **E3**:metrics 是关于一次 run 的**事实**,且**绝不能把这次 run 搞坏** ——
Expand Down
10 changes: 7 additions & 3 deletions doc/architecture/SPEC/sdk.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@
- Direct:`DirectReviewRequest` → `DirectClient.plan()` → `DirectReviewPlan`;
`read_document(document_id, offset, max_bytes)`;
`DirectCompletionRequest` → `validate()` → `DirectCompletionDecision`。
- Strict:`StrictRuntime(settings_overrides=...)`,以及
- Strict:`StrictRuntime(config=StrictRuntimeConfig(...))`,以及
`capabilities` / `readiness` / `reserve_review`(`start_review` 别名)/
`get_status` / `get_result` / `close`。
`get_status` / `get_result` / `close`。旧 `settings_overrides` 暂作兼容入口;
新配置显式绑定完整仓库名、checkout、允许根、backend 和可选 run root。
`StrictPollResult.review` 提供类型化结构结果,`payload` 保持旧 wire 兼容。
- Quality:同一 `StrictRuntime` 接受 typed `QualityReviewRequest`,提供
`quality_readiness` / `reserve_quality_review`(`start_quality_review` 别名)/
`get_quality_result`;机械信号只是 bounded hints,结果固定绑定 expected head。
`QualityPollResult.review` 投影类型化质量结果;非法结构触发 `ResultDecodeError`。
- Knowledge:宿主把不可信输入投影成 `KnowledgeEvidenceEvent` / `KnowledgeEvidenceBatch`,
再依次调用 `KnowledgeCurator.build_prompt()`、自己的 model adapter、
`validate_proposals()` 与 `apply()`。返回值为 typed
Expand All @@ -36,7 +39,8 @@

- **公开 import 无副作用**:单纯 import `sdk.v1` 不加载 `config`、legacy
`contract`、`direct_routing` 或任一 server;Direct provider 实现在调用
`plan`/`validate` 时才加载,Strict server 在构造 `StrictRuntime` 时才加载。
`plan`/`validate` 时才加载;构造 `StrictRuntime` 加载 headless RunService,
不加载 CLI 或 MCP transport。
- **只跨 document ID,不跨 provider filesystem path**:知识入口、route、guide、
map 与 fallback 均为相对 knowledge-root 的 ID;`read_document` 拒绝 absolute、
traversal 与 bundle 外路径。Strict result 丢弃私有 `report_path`,保留 report
Expand Down
1 change: 1 addition & 0 deletions src/infermatrix_copilot/app/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Headless application services shared by SDK and transports."""
Loading
Loading