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
5 changes: 4 additions & 1 deletion doc/architecture/SPEC/adapters/base.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# adapters/base.py —— 规范

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

`LOC ~423 · 边缘(仓库知识) · refactor-status: ok`

Expand Down Expand Up @@ -58,3 +58,6 @@
**把 `capabilities` 推导保持为 planner 信任的唯一来源** ——
`app.repository_context` 只为实际 checkout **补上** `repo.path`,
并将同一 adapter policy 交给执行层。

## 2026-09-28 退役规则不再提供
`render_briefing_docs` 经 `visible_text` 去掉退役规则。
3 changes: 3 additions & 0 deletions doc/architecture/SPEC/knowledge_docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,6 @@

## 2026-09-28 快照校验
可选 `verify(rel)` 回调在 `read`/`search` 读取每个文件前调用;MCP 传入 `KnowledgeView.path`,快照中缺失或被改动的文件使读取/搜索 fail-closed。

## 2026-09-28 退役规则不再提供
`read`/`search` 经 `visible_text` 去掉退役规则后再返回。
10 changes: 9 additions & 1 deletion doc/architecture/SPEC/knowledge_service.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# knowledge_service/ — provider curation components

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

The provider owns knowledge curation beneath the public SDK v1 facade.
`KnowledgeCurator` composes four domain components over one explicit work
Expand All @@ -17,3 +17,11 @@ local commits and proposal export. The public `sdk.v1.knowledge` module only
re-exports the curator and validator error. Existing proposal IDs, errors,
wire projections, validator order and rollback behavior are compatibility
contracts across the move.

Knowledge Ops API 2.0 sits beside the v1 curator and does not change it:
`lifecycle` parses rule pages (byte-exact) and their `kb:rule` footers,
`ops.apply_operations` applies typed add / edit_same_meaning / replace / retire
/ purge changes with their mechanical consequences, and `l1` is the
deterministic half of the quality gate (`check_tree`, `check_changeset`). The
three modules need only the standard library and PyYAML and import nothing else
from the package, so the kb-gate verifier bundle can vendor them.
34 changes: 34 additions & 0 deletions doc/architecture/SPEC/knowledge_service/l1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# knowledge_service/l1.py —— 规范

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

`LOC ~420 · 知识质量门的确定性一层(L1) · refactor-status: stable`

## 职责
- `check_tree(files)`:尾注合法、supersede 双向一致、tombstone ID 未被复用、
`_routes.yaml` 合法且引用页面存在。
- `check_changeset(base, head, changes, external_texts=)`:
- 路径白名单(`knowledge/{repos/<r>,general}/**/*.md`、`_routes.yaml`、`_tombstones.yaml`)
与文件模式 `100644`;拒绝 `knowledge/{skills,tools,.claude}/`、顶层文件、非 UTF-8。
- 无新增重复 ID、不复用退役或 tombstone ID、无指向本次退役/删除规则的悬空引用、
无新增树级问题、页面不超拆分线。
- 机械项按规则重算:frontmatter 只允许 `updated`(不倒退)与 `sources`(等于
`expected_sources`)变化;`_index.md` 只允许为新页追加索引行;tombstones 等于本次
purge 的 ID 且为规范格式。不符即 issue,不能靠判定覆盖。
- 其余变更分块:`rule`(add / edit / retire / supersede / purge,按段内容哈希)与
`prose`(页头、非规则段、非规则页、`_routes.yaml`)。每块有稳定 `block_id`,
供 L2 判定与签名逐块绑定。
- `external_refs`:知识目录之外引用了本次退役/删除 ID 的文件(配套 PR 流程)。
- 嵌套 `###` 规则随父规则退役、删除并记入 tombstones;编辑不得增删嵌套 ID;保护状态以
旧尾注为准且不得被移除;页面类型不得借改 `type:` 退出生命周期;每个新页面都必须在
同目录 `_index.md` 出现。
- purge 时序:tombstone 的 `purged_at` 不得等于该规则的 `retired_at`;传入可信的
`release` 时 `purged_at` 必须等于它。

## 不变量
纯函数;标准库 + PyYAML + `lifecycle`/`ops`,验证包原样携带。段比较忽略尾部空行
(追加段落时前一段多出的分隔空行不算改动)。

## 测试
`test_knowledge_ops_l1.py`(伪造的 sources/frontmatter/删除 active 规则/重复 ID、白名单与
模式、prose 分块、外部引用、真实知识树上执行各类操作后两个既有校验器均通过)。
24 changes: 24 additions & 0 deletions doc/architecture/SPEC/knowledge_service/lifecycle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# knowledge_service/lifecycle.py —— 规范

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

`LOC ~330 · 规则页数据模型与生命周期尾注 · refactor-status: stable`

## 职责
把规则页解析为 frontmatter、head 与 `## ` 段(跳过代码围栏内的标题),未改动的页
解析再渲染逐字节相同。规则段可带 `<!-- kb:rule ... -->` 尾注(status、since、retired_at、
reason、evidence、supersedes、superseded_by、protected);无尾注即 active。

## 公开契约
`Page.parse/render`、`Page.rules/rule/replace_section/append_section`、
`Page.sources/with_sources`(按页面原风格写 flow 或 block 列表)、`Section.footer/
with_footer/body_without_footer/content_sha256/citations`、`Footer.parse/render/check`、
`expected_sources`(旧列表 + active 规则新引用,只追加)、`visible_text`(去掉退役规则,
无退役规则时逐字节原样返回)。

## 不变量
- 仅依赖标准库与 PyYAML、不 import 包内其他模块:kb-gate 验证包原样携带。
- 尾注键未知、重复、格式错误或状态约束不满足时抛 `LifecycleError`。

## 测试
`test_knowledge_ops_l1.py`(全树逐字节往返、服务端隐藏退役规则)。
27 changes: 27 additions & 0 deletions doc/architecture/SPEC/knowledge_service/ops.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# knowledge_service/ops.py —— 规范

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

`LOC ~360 · Knowledge Ops API 2.0(类型化知识变更) · refactor-status: stable`

## 职责
`apply_operations(files, ops, release, today)` 是纯函数:输入知识相对路径到文本的映射与
类型化操作(`add`、`edit_same_meaning`、`replace`、`retire`、`purge`),输出变更后的文件。
随后写入机械结果:`updated:`、只追加的 `sources:`、新页面在同目录 `_index.md` 的一行、
purge 的 `_tombstones.yaml` 条目。

## 不变量
- 新 ID(含嵌套 `###`)在全树唯一,且不在 tombstones 中。
- `replace` 在一次调用内同时退役旧规则(`reason=superseded`、`superseded_by`)并添加带
`supersedes` 的后继规则。
- `purge` 只针对已退役且 `retired_at` 不是本次发版的规则。
- `protected` 规则需 `allow_protected`(人工路径)。
- 超过拆分线(32 KiB / 500 非空行)的页面被拒绝,提示改用新的 `rules-<topic>.md`。
- 任何非法操作抛 `LifecycleError`,输入映射从不被修改(无部分应用)。
- 版本常量 `KNOWLEDGE_OPS_API_VERSION = 2.0.0`,与 v1 `KnowledgeCurator`(1.1.0)并存。

## 依赖(允许)
标准库 + PyYAML + `lifecycle`(验证包原样携带)。

## 测试
`test_knowledge_ops_l1.py`。
3 changes: 3 additions & 0 deletions doc/architecture/SPEC/sdk.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,6 @@ Direct 1.1 / Strict 1.3 accept typed `CarriedFinding` inputs (unique IDs, source

## 2026-09-28 知识视图
`DirectClient` 每次 `plan` 解析一个 `KnowledgeView`,文档引用与 `resource_revision` 均取自该视图;`validate` 按计划时钉住的知识根校验资源未变,激活新快照不会使进行中的 review 失效;`diagnostics.knowledge_snapshot` 报告所用快照。`read_document(..., review_context_id=...)` 从该 review 计划时钉住的知识树读取(含分页),未签发的 context id 被拒绝;不带 context id 时读当前激活树。

## 2026-09-28 退役规则不再提供
`DirectClient` 返回的文档内容与 `sha256` 均基于去掉退役规则后的服务文本(无退役规则时与磁盘字节一致)。
27 changes: 27 additions & 0 deletions doc/knowledge/SCHEMA.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,33 @@ contradictions: [相对路径] # 可选:与本页冲突的页面
先把仍然有效的独有结论合入最近 owner 的幸存页面,再删除被取代或重复页面并修复
入链。Git 历史负责追溯旧版本;`knowledge/` 内不再维护 `_archive/` 副本。

## 规则生命周期尾注

规则段(`## <ID> — <标题>`)可以以一行机器可读尾注结束,渲染时不可见:

```
<!-- kb:rule status=retired since=v0.30.0 retired_at=v0.31.0 reason=upstream-removed evidence="PR #8201" -->
<!-- kb:rule status=active since=v0.31.0 supersedes=BENCH-1b -->
```

- 没有尾注 = `status=active`(兼容全部既有规则)。
- `status=retired` 必须带 `retired_at`、`reason`(`upstream-removed` / `superseded` /
`incorrect` / `duplicate`)与 `evidence`;`superseded` 还必须带 `superseded_by`,且
后继规则带对应的 `supersedes`。
- 退役规则的正文在磁盘上保留一个发版周期供复核与撤销,但**立即停止对外提供**:
Direct 文档读取、`doc_read`/`doc_search`、briefing 都会去掉它。
- `protected=true` 的规则只能经人工路径修改、退役或删除。
- 物理删除(purge)只针对已退役满一个发版周期的规则;被删除的 ID 记入同仓库的
`_tombstones.yaml`,永不复用。
- 尾注、`updated:`、`sources:`(只追加)、新页面的 `_index.md` 行与 tombstones 由知识服务
按 Knowledge Ops API 2.0 写入,并由 L1 门禁重算核对;手工改动也必须符合同一规则。

## Direct 路由表

每个仓库的 Direct owner 路由在 `repos/<repo>/_routes.yaml`(`schema_version: 1`,
`owners[{owner, path, signals, scope_prefixes}]`,可选 `models{dir, page}`)。路由引用的页面
必须存在;新增仓库只需提供它和 adapter,不改代码。

## 校验

```bash
Expand Down
4 changes: 3 additions & 1 deletion src/infermatrix_copilot/adapters/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

import yaml

from ..knowledge_service.lifecycle import visible_text

HIGH_RISK_SECTIONS = ("push", "repo", "upstream", "rebase")


Expand Down Expand Up @@ -87,7 +89,7 @@ def render_briefing_docs(root: Path | str, docs: list[str], *, header: str = "",
if warnings is not None:
warnings.append(f"briefing document missing or not Markdown: {rel}")
continue
text = p.read_text(encoding="utf-8", errors="replace")
text = visible_text(p.read_text(encoding="utf-8", errors="replace"))
parts.append(_without_frontmatter(text).strip())
if not parts:
return ""
Expand Down
8 changes: 5 additions & 3 deletions src/infermatrix_copilot/knowledge_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from pathlib import Path
from typing import Callable

from .knowledge_service.lifecycle import visible_text


class KnowledgeDocsError(ValueError):
"""A refused or invalid knowledge-base operation."""
Expand Down Expand Up @@ -94,7 +96,7 @@ def read(self, path: str, *, offset: int = 0, limit: int = 24_000) -> dict:
raise KnowledgeDocsError("limit must be a positive integer")
limit = min(limit, 65_536)
target = self._resolve_doc(path)
data = target.read_text(encoding="utf-8", errors="replace")
data = visible_text(target.read_text(encoding="utf-8", errors="replace"))
end = offset + limit
return {
"path": target.relative_to(self.root).as_posix(),
Expand All @@ -121,8 +123,8 @@ def search(self, query: str, *, limit: int = 40) -> list[dict]:
seen_files.add(target)
self._checked(target)
rel = target.relative_to(self.root).as_posix()
for lineno, line in enumerate(
target.read_text(encoding="utf-8", errors="replace").splitlines(), 1):
served = visible_text(target.read_text(encoding="utf-8", errors="replace"))
for lineno, line in enumerate(served.splitlines(), 1):
line_folded = line.casefold()
exact = folded in line_folded
term_match = bool(terms) and all(t in line_folded for t in terms)
Expand Down
Loading
Loading