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
7 changes: 7 additions & 0 deletions .github/workflows/refresh-profile-data.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ on:
- "docs/i18n/zh-CN/README.md"
- "scripts/build_profile_svgs.py"
- "assets/**"
- "tests/**"
workflow_dispatch:

permissions:
Expand All @@ -31,6 +32,12 @@ jobs:
- name: Marker and parity lint
run: python scripts/build_profile_svgs.py --lint

# The characterization suite is zero-dependency and sub-second: make the
# contract tests, README structure guards and SVG golden masters as
# mandatory as the lint instead of local-only.
- name: Contract and structure tests
run: python -m unittest discover tests

refresh:
if: github.event_name != 'pull_request'
runs-on: ubuntu-latest
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@
- **`give-me-a-break`** — the state machine's `evaluate` has zero time dependency. It takes the clock as an argument, so the entire rest/work/AFK lifecycle is testable against a virtual clock: sleep, crash recovery with fast-forward, a display unplugged mid-break. Three modules, one of which knows nothing about macOS.
- **`hyper-git`** — `engine/` contains no `vscode` import. That single constraint is why 403 unit tests exist: changelist grouping, DAG swimlane layout, and Conventional Commits validation are all testable without an editor host. The architecture note calls it "Path B" — consume the stable `vscode.git` API and hand-render everything above it, rather than fork.
- **`hyper-git`, again** — five AI seams (`ILlmProvider`, `ICommitMessageProvider`, `IPreCommitInspector`, `IChangelistGrouper`, `IConflictResolver`) are wired in as null implementations, modeled on JetBrains' `CheckinHandler` lifecycle. The interfaces ship; the intelligence is deferred to M5. Declaring the seam early is cheap and declaring it late is not.
- **`coding-proxy`** — the failover chain is policy, not plumbing: circuit breaker state and quota accounting are per-vendor and local, in SQLite-WAL. No Redis, no queue, so a single process is the whole deployment and a restart loses nothing that matters.
- **`coding-proxy`** — the failover chain is policy, not plumbing: circuit breaker state and quota accounting are per-vendor and local, in SQLite-WAL — so a single process is the whole deployment, and a restart loses nothing that matters.
- **`negentropy`** — the default path runs on the cheapest of its pluggable backends with **no cloud credentials at all**; if the cheapest configuration is not runnable, nobody runs the expensive one either.

<sub>Stated as a virtue, this is testability. Stated honestly, it is what a single maintainer has to do to survive his own codebase: nothing here has a second pair of eyes, so the design has to make the mistakes cheap to find. And the null AI seams are still null — the interfaces are a plan, not a feature.</sub>
Expand Down Expand Up @@ -290,7 +290,7 @@ sequenceDiagram
P-->>C: SSE stream, in Anthropic's shape
```

The client is told nothing. That is the whole product: one line of configuration, and the failure mode changes from *stop working* to *work slower on someone else's model*. Nine vendors are wired — Claude plans, GitHub Copilot, Google Antigravity, Z AI's GLM, MiniMax, Qwen, Xiaomi, Kimi, Doubao — with a per-vendor circuit breaker and quota guard, and a local SQLite-WAL dashboard so the burn is visible before the bill is.
The client is told nothing. That is the whole product: one line of configuration, and the failure mode changes from *stop working* to *work slower on someone else's model*. Each of the nine vendors named on the card above gets its own circuit breaker and quota guard, and a local SQLite-WAL dashboard makes the burn visible before the bill is.

<sub>FastAPI and httpx. <!-- DATA:rel_cp -->12<!-- /DATA:rel_cp --> releases, the latest still alpha-tagged — translation fidelity across three request shapes is the part that keeps not being finished. Chained failover also means a request can succeed on a model you did not choose; the dashboard exists partly so that is auditable.</sub>

Expand Down
32 changes: 32 additions & 0 deletions docs/.agents/issue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Issue 台账

| ID | 标题 | 状态 | 完成时间 | 详情 |
|---|---|---|---|---|
| I-001 | 九厂商清单双层手写漂移:单侧修复未除根,结构性收敛为卡片单一事实源 | ✅ | 2026-10-18 | [详情](#I-001) |
| I-002 | 语义级镜像漂移与锚点死链无机器门:lint 仅护 marker 级 | ✅ | 2026-10-18 | [详情](#I-002) |

<a id="I-001"></a>
## I-001:九厂商清单双层手写漂移

**问题描述**:coding-proxy 九家厂商清单在项目卡(`README.md:163`)与深潜块(`README.md:293`)两处全文手写,中英共四份拷贝,无同步机制。PR #7 曾发现卡片侧漏写 Xiaomi(8 家对 9 家),修复方式为只补卡片侧数据——漂移根因(双清单结构)未除。

**表因根因**:v5 重建时同一事实一次性写入两处;两清单命名形不同(Copilot/GitHub Copilot 等),肉眼 diff 困难;lint 只校验 marker 流,散文重复不在守护范围。

**处理方式**(2026-10-18,preening-substrate 结构性熵减):`README.md:163` 定为唯一权威清单(承诺性陈述,加厂商先改这里);`:293` 深潜句收敛为「上方卡片点名的九家厂商」回指,保留每家熔断/配额守卫的增量叙述;中英同步镜像。验证:31 用例 + `--lint` 全绿,残留扫描确认厂商名仅存 :163。

**后续防范**:改厂商清单只动 :163;新增手写事实清单时先指定权威位置(编辑契约见 [knowledge-map.md](knowledge-map.md#双语镜像编辑契约卡) 第 7 条)。

**同类影响**:其余手写数字面(`403` 三处、`:249` 的 1,378、`:36` 年度序列 prose)已登记为漂移面,按 C4(角色不同)暂保留,未动。

<a id="I-002"></a>
## I-002:语义级镜像漂移与锚点死链无机器门

**问题描述**:CI lint 与测试只守护 marker 级契约(75/13 流、parity、assets 闭包);三类承重面无机器守护——① 中英两文件 marker 外文本的语义级镜像漂移;② `<a name>` 锚点 / `#user-content-` 导航链接 / FIG key 三方一致性(破坏即静默死链);③ `tests/` 31 用例未接 CI(仅本地跑)。

**表因根因**:PR #8 建立 tests 时按最小护栏取舍;锚点前缀是 GitHub 渲染侧行为,仓库内工具不感知。

**处理方式**(2026-10-18,两步闭环):第一步重构时以 lab 级 gate 脚本护航(锚点三方一致 + marker 行号镜像 + 骨架行号镜像 + 行数相等),编辑契约沉淀至 [knowledge-map.md](knowledge-map.md#双语镜像编辑契约卡);第二步将检查上收为 [`tests/test_readme_structure.py`](../../tests/test_readme_structure.py)(零依赖纯正则,35 用例之一),并在 `refresh-profile-data.yml` lint job 中接入 `python -m unittest discover tests`,全套护栏由本地自觉变 PR 强制门。

**后续防范**:改任一 README 后跑 knowledge-map 契约卡第 6 条两条命令(与 CI 同款);新增结构元素(details/锚点/表格)时行级镜像约束由测试自动拦截。

**注意事项**:本地 Markdown 预览不加 `user-content-` 前缀,导航链接在本地必显死链——这是设计使然,以 GitHub 渲染为准。
28 changes: 28 additions & 0 deletions docs/.agents/knowledge-map.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Knowledge Map

本仓文档索引与跨文档编辑契约的唯一入口。

## 文档索引

| 文档 | 职责 |
|---|---|
| [`README.md`](../../README.md) | 英文 profile 主页(月度构建产物:13 FIG + 75 DATA 标记) |
| [`docs/i18n/zh-CN/README.md`](../i18n/zh-CN/README.md) | 中文镜像主页(与英文版逐行结构镜像) |
| [`docs/motion-constraints.md`](../motion-constraints.md) | SVG 动效/布局闸门的「为什么」层(闸门机械强制在 `scripts/build_profile_svgs.py`,冲突时以闸门为准) |
| [`docs/.agents/issue.md`](issue.md) | Issue 台账(头部索引 + 详情锚点) |

## 双语镜像编辑契约卡

改任一 README 前必读。两份文件是**逐行结构镜像**:marker 行号、结构骨架行号(details/summary/锚点/表格/代码块)、总行数必须逐位一致;合法差异仅三类——正文语言、img `src` 绝对化(中文版仓库外渲染所需)、语言切换行相对路径。

1. **机器领地,永不手改**:`<!-- FIG:k -->…<!-- /FIG:k -->` 体(img/alt/src 每次刷新从零重建,手改必被覆盖);`<!-- DATA:k -->值<!-- /DATA:k -->` 的值。生成器为 [`scripts/build_profile_svgs.py`](../../scripts/build_profile_svgs.py)。
2. **标记即契约**:key 必须 ∈ `FACT_KEYS`/`FIG_SPEC`;两文件 (kind,key) 有序流必须全等且非空(`audit_parity`);增删/换序标记须两文件同步,且计数变化需同步 `tests/test_contract.py` 的 75/13 硬编码断言。
3. **含 marker 的行谨慎编辑**:值以外的行内文字可改,但 marker 本身与其相对顺序不可动;`DATA:cur_year`/`DATA:cur_total` 是月度刷新「当年数字不回退」守卫的状态源,永不删除。
4. **锚点三方同名同序**:`<summary><a name="X">` ×13、两条簇导航 `<sub>` 行的 `#user-content-X` 链接、FIG key `X` 三者集合与顺序一致(`tests/test_readme_structure.py` 机器强制);GitHub 对手写 `name` 自动加 `user-content-` 前缀,链接侧必须手写该前缀。
5. **冻结面绕行**:mermaid 块、upstream/census/道法术表格、IEEE 脚注、recompute 命令块承载既有契约,结构性重构一律绕行。
6. **编辑后验证**(仓库根,两命令即 PR CI lint job 全部门禁):
```bash
python3 -m unittest discover tests # 35 用例:Golden Master + 锚点三方一致 + 双文件行级镜像 + 单调性状态源
python3 scripts/build_profile_svgs.py --lint # 零网络,marker 流与 assets 闭包审计
```
7. **散文重复收敛原则**:同一事实多处手写 = 漂移风险(先例:九厂商清单曾单侧漏一家,PR #7 只修了数据);收敛时指定唯一权威位置(如承诺性陈述放项目卡),其余降为回指;机器供数(DATA/alt)的重复无漂移风险,不做收敛。
4 changes: 2 additions & 2 deletions docs/i18n/zh-CN/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@
- **`give-me-a-break`** —— 状态机的 `evaluate` 零时间依赖。时钟由参数传入,于是整套休息/工作/AFK 生命周期都能在虚拟时钟下测试:睡眠、崩溃后快进恢复、休息中途被拔掉的显示器。三个模块,其中一个对 macOS 一无所知。
- **`hyper-git`** —— `engine/` 里没有一处 `vscode` import。仅这一条约束,就是 403 个单元测试之所以存在的原因:变更列表分组、DAG 泳道布局、Conventional Commits 校验,全都不需要编辑器宿主即可测试。架构说明称之为「Path B」——消费稳定的 `vscode.git` API,其上一切手绘,而不去 fork。
- **`hyper-git`,再一次** —— 五个 AI 接缝(`ILlmProvider`、`ICommitMessageProvider`、`IPreCommitInspector`、`IChangelistGrouper`、`IConflictResolver`)以空实现预先接入,形制取自 JetBrains 的 `CheckinHandler` 生命周期。接口先交付,智能延到 M5。早声明接缝很便宜,晚声明就不便宜了。
- **`coding-proxy`** —— 故障转移链是策略,不是管道:熔断状态与配额记账按厂商分离、且是本地的,落在 SQLite-WAL 里。没有 Redis、没有队列,于是单进程就是整个部署,重启不会丢掉任何要紧的东西。
- **`coding-proxy`** —— 故障转移链是策略,不是管道:熔断状态与配额记账按厂商分离、且是本地的,落在 SQLite-WAL 里——于是单进程就是整个部署,重启不会丢掉任何要紧的东西。
- **`negentropy`** —— 默认路径跑在可插拔后端中最便宜的那档上,**完全不需要云凭据**;如果最便宜的配置跑不起来,也就没人会去跑贵的那个。

<sub>说得好听,这叫可测性。说得诚实,这是一个单人维护者为了在自己的代码库里活下来必须做的事:这里没有第二双眼睛,所以设计必须让错误便宜到能被找出来。而那五个空的 AI 接缝,至今仍然是空的——接口是一份计划,不是一个功能。</sub>
Expand Down Expand Up @@ -290,7 +290,7 @@ sequenceDiagram
P-->>C: SSE 流,仍是 Anthropic 的形状
```

客户端什么都不知道。这就是产品的全部:一行配置,把失败模式从「停止工作」改成「用别人的模型慢一点工作」。已接入九家——Claude 套餐、GitHub Copilot、Google Antigravity、Z AI 的 GLM、MiniMax、Qwen、小米、Kimi、豆包——每家独立熔断与配额守卫,另有本地 SQLite-WAL 看板,让消耗在账单之前就可见。
客户端什么都不知道。这就是产品的全部:一行配置,把失败模式从「停止工作」改成「用别人的模型慢一点工作」。上方卡片点名的九家厂商,每家独立熔断与配额守卫,另有本地 SQLite-WAL 看板,让消耗在账单之前就可见。

<sub>FastAPI 与 httpx。<!-- DATA:rel_cp -->12<!-- /DATA:rel_cp --> 个 release,最新仍带 alpha 标——三种请求形状之间的转译保真度,正是那个一直没做完的部分。链式转移也意味着请求可能在你没选的模型上成功;看板的存在,一部分就是为了让这件事可审计。</sub>

Expand Down
73 changes: 37 additions & 36 deletions scripts/build_profile_svgs.py
Original file line number Diff line number Diff line change
Expand Up @@ -1655,6 +1655,41 @@ def upstream_alt(f, items, pub_prs):
ledger, format(pub_prs, ","), len(rows) / pub_prs * 100))


def cadence_alt(f, rel_lists, domain, trunk_commits, rel_total):
lanes = sorted(((n, sorted(v, key=lambda r: r["published_at"]))
for n, v in rel_lists.items() if v),
key=lambda kv: (-len(kv[1]), kv[0]))
# The trunk sentence carries two numbers that MOVE — negentropy is the
# active repo, so a literal here would have the alt contradict the
# neg_commits marker rendered beside it within a month.
trunk_rels = len(rel_lists.get(TRUNK, ()))
if f == EN:
rows = "; ".join(
"%s: %d releases, %s on %s through %s on %s%s" % (
n, len(v), v[0]["tag_name"], v[0]["published_at"][:10],
v[-1]["tag_name"], v[-1]["published_at"][:10],
"" if not all(r["prerelease"] for r in v) else ", all pre-releases")
for n, v in lanes)
return ("Dot timeline of %d public releases across %d repositories on one shared "
"date axis, %s to %s. %s. Hollow dots are pre-releases. %s shows only its "
"%d release candidates against %s commits because it is a deployed "
"service, not a distributed package — its shipping unit is the merged "
"pull request, not the tag. Data: GitHub."
% (rel_total, len(lanes), domain[0], domain[1], rows,
TRUNK, trunk_rels, format(trunk_commits, ",")))
rows = ";".join(
"%s %d 个 release,%s(%s)至 %s(%s)%s" % (
n, len(v), v[0]["tag_name"], v[0]["published_at"][:10],
v[-1]["tag_name"], v[-1]["published_at"][:10],
"" if not all(r["prerelease"] for r in v) else ",全部为预发布")
for n, v in lanes)
return ("各仓库公开 release 的点式时间线(共用日期轴,%s 至 %s)。%s。空心点为预发布。"
"%s 在 %s 条提交面前只有 %d 个 rc,因为它是部署型服务而非分发包——"
"它的交付单元是已合并 PR,不是 tag。数据:GitHub。"
% (domain[0], domain[1], rows,
TRUNK, format(trunk_commits, ","), trunk_rels))


LAT_EDGES = [1, 2, 5, 15, 60, 240, 1440, float("inf")]
LAT_LABELS = ["<1m", "1-2", "2-5", "5-15", "15-60m", "1-4h", "4-24h", ">24h"]

Expand Down Expand Up @@ -1862,41 +1897,6 @@ def windowed_run(dom):
die(f"release roster {sorted(rel_counts)} != source roster "
f"{sorted(repo_commits)} — a repo's releases would render as 0, refusing")

def cadence_alt(f, rel_lists, domain, trunk_commits):
lanes = sorted(((n, sorted(v, key=lambda r: r["published_at"]))
for n, v in rel_lists.items() if v),
key=lambda kv: (-len(kv[1]), kv[0]))
# The trunk sentence carries two numbers that MOVE — negentropy is the
# active repo, so a literal here would have the alt contradict the
# neg_commits marker rendered beside it within a month.
trunk_rels = len(rel_lists.get(TRUNK, ()))
if f == EN:
rows = "; ".join(
"%s: %d releases, %s on %s through %s on %s%s" % (
n, len(v), v[0]["tag_name"], v[0]["published_at"][:10],
v[-1]["tag_name"], v[-1]["published_at"][:10],
"" if not all(r["prerelease"] for r in v) else ", all pre-releases")
for n, v in lanes)
return ("Dot timeline of %d public releases across %d repositories on one shared "
"date axis, %s to %s. %s. Hollow dots are pre-releases. %s shows only its "
"%d release candidates against %s commits because it is a deployed "
"service, not a distributed package — its shipping unit is the merged "
"pull request, not the tag. Data: GitHub."
% (rel_total, len(lanes), domain[0], domain[1], rows,
TRUNK, trunk_rels, format(trunk_commits, ",")))
rows = ";".join(
"%s %d 个 release,%s(%s)至 %s(%s)%s" % (
n, len(v), v[0]["tag_name"], v[0]["published_at"][:10],
v[-1]["tag_name"], v[-1]["published_at"][:10],
"" if not all(r["prerelease"] for r in v) else ",全部为预发布")
for n, v in lanes)
return ("各仓库公开 release 的点式时间线(共用日期轴,%s 至 %s)。%s。空心点为预发布。"
"%s 在 %s 条提交面前只有 %d 个 rc,因为它是部署型服务而非分发包——"
"它的交付单元是已合并 PR,不是 tag。数据:GitHub。"
% (domain[0], domain[1], rows,
TRUNK, format(trunk_commits, ","), trunk_rels))


# ------------------------------------------------- figure derivations ----
# Degenerate-but-reachable shapes die cleanly here instead of raising a bare
# traceback further down; the write-nothing contract holds either way.
Expand Down Expand Up @@ -2078,7 +2078,8 @@ def ecdf_points():
"accrual": {f: accrual_alt(f, monthly, DOMAIN, grad_events, commits_total)
for f in README_FILES},
"lifecycles": {f: lifecycles_alt(f, spans, DOMAIN) for f in README_FILES},
"cadence": {f: cadence_alt(f, rel_lists, DOMAIN, len(repo_commits.get(TRUNK, ())))
"cadence": {f: cadence_alt(f, rel_lists, DOMAIN,
len(repo_commits.get(TRUNK, ())), rel_total)
for f in README_FILES},
"streak": {f: streak_alt(f, rug_act, RUG_DAYS - rug_act, rug_run, rug_dom, RUG_DAYS)
for f in README_FILES},
Expand Down
Loading
Loading