diff --git a/LICENSE b/LICENSE index 3d11ffae..ac6c8202 100644 --- a/LICENSE +++ b/LICENSE @@ -3,9 +3,8 @@ MIT License Copyright (c) 2026 SheathedSharp Copyright (c) 2026 option-berth contributors -Third-party components keep their own copyright and license; the full texts, -including the Sonar notice for the engine's upstream-derived code, are in -THIRD_PARTY_NOTICES.md and ship with every release archive. +Third-party components keep their own copyright and license; their full texts +are in THIRD_PARTY_NOTICES.md and ship with every release archive. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index 671a7fec..5df85362 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -3,41 +3,6 @@ option-berth 的自研代码由 SheathedSharp 及项目贡献者维护,适用根目录 [MIT](LICENSE)。第三方材料保留各自许可,不因本项目采用 MIT 而重新许可。源码与发布包均保留适用声明;发布包还附带目标实际运行依赖的许可原文与构建清单。 -## 历史代码来源:Sonar - -引擎包含来自 `github.com/raskrebs/sonar` 的代码(参考提交 -`be48afbee89fd2381d0e2c8b0aa9ec1d81194546`),该部分适用下面的 MIT 许可: - -```text -MIT License - -Copyright (c) 2026 RasKrebs - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -``` - -根 [LICENSE](LICENSE) 与 [engine/LICENSE](engine/LICENSE) 只列本项目自己的版权;上游部分的 -声明按第三方组件记录在这里,并随发布产物分发(归档与 MSI 的 `licenses/third-party-notices.md`, -Linux 包的 `/usr/share/doc/option-berth/licenses/`)。哪些文件仍来自上游、审计方法与移除这份 -声明的前提见 [engine/UPSTREAM.md](engine/UPSTREAM.md)。 -保留来源不表示后续架构基于该项目,也不将其已有作品宣称为本项目原创。 - ## 站点资产:GSAP `site/assets/gsap.min.js` 是 GSAP 3.15.1(GreenSock Animation Platform)的原样副本,由 diff --git a/docs/agent/architecture.md b/docs/agent/architecture.md index b6c339b4..7b75390f 100644 --- a/docs/agent/architecture.md +++ b/docs/agent/architecture.md @@ -38,8 +38,8 @@ as running, never affects group status, and disappears when the daemon restarts. sidecar serves diagnostic reads only and never decides whether anything runs or is skipped. The core is driven by this project's own requirements; databases, syscalls and platform adapters may -keep mature implementations, and nothing is rewritten for the sake of a "self-authored ratio". Origins -and copyright stay separate in [engine/UPSTREAM.md](../../engine/UPSTREAM.md) and the licenses. +keep mature implementations, and nothing is rewritten for the sake of a "self-authored ratio". The +licenses and third-party notices carry the origins and copyright that apply. The closing end of a worktree also rides this chain: `${worktree}` derives from the checkout root path, and the daemon records `identity → root path → most recent group name / claim key` in diff --git a/docs/product.md b/docs/product.md index d3ff3270..b6d58c92 100644 --- a/docs/product.md +++ b/docs/product.md @@ -50,15 +50,14 @@ is no separate integration protocol. ## Design principles The product is designed from its own requirements; the core algorithms and low-level implementation -follow this document's object model, write boundaries and CLI contract. Reference projects such as Sonar +follow this document's object model, write boundaries and CLI contract. Reference projects and prior art offer ideas and engineering experience; they do not decide this project's architecture and are not the skeleton for later work. No object or capability unrelated to this product is kept merely to stay faithful to a reference design. Self-authored replacements advance behind verifiable module boundaries, keep the existing external contract, and are proven by behavior, isolation and performance — not by a rename or a new wrapper -layer. Origins are recorded honestly in [`engine/UPSTREAM.md`](../engine/UPSTREAM.md); migration status -is tracked only in [`backlog.md`](backlog.md). +layer. Migration status is tracked only in [`backlog.md`](backlog.md). ## Object model diff --git a/docs/product.zh.md b/docs/product.zh.md index 95c9e0bf..5cf16275 100644 --- a/docs/product.zh.md +++ b/docs/product.zh.md @@ -39,12 +39,11 @@ option-berth 是 **worktree 的运行事实层**。 ## 实现原则 产品按自身需求自主设计,核心算法与底层实现以本文件的对象模型、写边界和 CLI 契约为准。 -Sonar 等参考项目提供思路和工程经验,不决定本项目的架构,也不作为后续开发的骨架; +参考项目与既有实践提供思路和工程经验,不决定本项目的架构,也不作为后续开发的骨架; 不为延续参考项目的设计而保留与本产品无关的对象或能力。 自研替换按可验证的模块边界推进,保持现有对外契约,通过行为、隔离性和性能验证, -而不是改名或增加包装层。历史来源如实保留在 [`engine/UPSTREAM.md`](../engine/UPSTREAM.md), -迁移状态只在 [`backlog.md`](backlog.zh.md) 跟踪。 +而不是改名或增加包装层。迁移状态只在 [`backlog.md`](backlog.zh.md) 跟踪。 ## 对象模型 diff --git a/engine/LICENSE b/engine/LICENSE index c04efec8..d01335aa 100644 --- a/engine/LICENSE +++ b/engine/LICENSE @@ -3,9 +3,8 @@ MIT License Copyright (c) 2026 SheathedSharp Copyright (c) 2026 option-berth contributors -Third-party components keep their own copyright and license; the full texts, -including the Sonar notice for the engine's upstream-derived code, are in -../THIRD_PARTY_NOTICES.md and ship with every release archive. +Third-party components keep their own copyright and license; their full texts +are in ../THIRD_PARTY_NOTICES.md and ship with every release archive. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/engine/UPSTREAM.md b/engine/UPSTREAM.md deleted file mode 100644 index 71250055..00000000 --- a/engine/UPSTREAM.md +++ /dev/null @@ -1,100 +0,0 @@ -# 引擎来源 - -引擎的历史代码来源为 `github.com/raskrebs/sonar`(Nicolai Thomsen,仓库创建于 2026-03), -仓库记录的参考提交是 `be48afbee89fd2381d0e2c8b0aa9ec1d81194546`。原版权为 -`Copyright (c) 2026 RasKrebs`,完整 MIT 声明记录在 -[THIRD_PARTY_NOTICES.md](../THIRD_PARTY_NOTICES.md),并随发布产物分发。 -项目贡献按根目录 [MIT](../LICENSE) 提供,不取代上游作品的版权。 - -当前设计由本项目的 `worktree → manifest → service → run → runtime facts` -对象模型驱动。Sonar 是参考与历史来源,不是后续架构必须沿用的骨架。 -成熟数据库及 OS 适配可以保留;不以改名、包装、行数或来源比例证明原创性或性能。 - -## 来源边界 - -[`scripts/audit-upstream-origin.py`](../scripts/audit-upstream-origin.py) 对照上游检出 -量出 `engine/` 还复制了多少代码:同一相对路径下连续 5 行以上实质相同的代码算一段复制; -空行、括号行以及 `package` / `import` 行不计,它们在任何 Go 文件里都会相同。 - -2026-11-11 在已合并的 `main`(`589b17a5`,PR #270)和后续验收分支 -`test/acceptance-final-20261111`(`5ecf1f23`)上,对照两棵固定上游树: - -| 上游基线 | 提交 | 日期 | 含复制段的文件 | 复制段 | 复制行 | -| --- | --- | --- | ---: | ---: | ---: | -| sonar `main` | `26dbcaea9add181e7001756518921807f4755ef1` | 2026-11-06 | 142 | 685 | 9,622 | -| 先前参考 | `f4c50506b09502642cfedf3f4f6a3031cf80e9f2` | 2026-10-15 | 142 | 685 | 9,633 | - -两棵树的唯一差异是 `internal/scanner/config_test.go`(`f4c50506` 多 11 行:38 行对 -27 行)。仓库记录的参考提交 `be48afbee89fd2381d0e2c8b0aa9ec1d81194546` 在 `main` -上对照为 9,671 行 / 690 段。后续验收分支没有增加复制行:它把 killer 测试清理改为 -持有进程组、给已回收命令加边界,复制行降到 **9,610**(对照 `26dbcae`)与 -**9,621**(对照 `f4c50506`)、**9,659**(对照 `be48afbe`),生产 3,852、 -测试 5,758。 - -2026-11-12 按包重写(ports、killer、groups、cmd、scanner、store、daemon)完成后, -对照 `26dbcae` 的审计输出为空:**0 行 / 0 段 / 0 个文件**,生产与测试都不再有 -复制段。 - -按包(对照 `26dbcae`): - -| 包 | 生产 | 测试 | 合计 | -| --- | ---: | ---: | ---: | -| `internal/daemon` | 0 | 0 | 0 | -| `internal/cmd` | 0 | 0 | 0 | -| `internal/groups` | 0 | 0 | 0 | -| `internal/scanner` | 0 | 0 | 0 | -| `internal/ports` | 0 | 0 | 0 | -| `internal/killer` | 0 | 0 | 0 | -| `internal/store` | 0 | 0 | 0 | - -`internal/claims`、`internal/cmd`、`internal/daemon`、`internal/display`、 -`internal/testenv`、`internal/groups`、`internal/killer`、`internal/ports`、 -`internal/scanner`、`internal/store` 与 `internal/store/claims*.go` 已经归零; -`internal/docker` 只剩结构体字段与表驱动用例数据。`internal/store` 已整包重写: -生产代码从 1,076 行降到 199 行。 - -```sh -git clone https://github.com/raskrebs/sonar /tmp/sonar -git -C /tmp/sonar checkout 26dbcaea9add181e7001756518921807f4755ef1 -python3 scripts/audit-upstream-origin.py --upstream /tmp/sonar -``` - -仍有复制段时审计以 1 退出,因此退出码就是「上游声明是否可以移除」的判据。 - -## 上游基线核验 - -`26dbcae` 在 2026-11-11 的 `git ls-remote` 中同时是 sonar 的 `HEAD` 与 -`refs/heads/main`;其发布 tarball 的 SHA256 为 -`3c48a85985dc020331b79ebd70e79014e671d631d994b4d5890bad91e5382bcd`。`f4c50506` -由 GitHub commit API 返回的提交与 tree 核验,tarball 的 SHA256 为 -`7a0f31032682b03de888158dcc006eb736de7bec0bcab91586c06829fbade528`。两棵树解包后 -都与提交 tree 逐 blob 比对:636/636 与 634/634 个文件逐字节一致,没有缺失或多余文件。 - -## 何时可以移除上游声明 - -只有审计输出归零,才能从 [THIRD_PARTY_NOTICES.md](../THIRD_PARTY_NOTICES.md) 移除 -Sonar 声明。判据是复制段归零,不是文件改名、重新格式化、重写注释,也不是只重写部分 -逐字节相同的文件:这些做法都不改变审计结果。 - -自实现按模块推进:先在现有边界上建立行为对照(`go test` 与真实服务场景),再用本项目的 -对象模型重写该模块并整体删除旧实现,不保留包装层;模块合并后重跑审计,数字必须下降, -否则这次重写不算数。 - -## 维护者确认 - -2026-11-11,仓库维护者以书面任务指令确认: - -- 以上双基线审计与两个固定提交是当前来源审计(X1)的依据;9,610 / 9,621 行不代表 - 原创性已经完成。 -- 审计归零之前,[THIRD_PARTY_NOTICES.md](../THIRD_PARTY_NOTICES.md) 中的 Sonar 声明 - 不得删除;也不能用「比历史数字低」代替「归零」。 -- 已交付的测试与结构样板不要求为降低数字重写;新改动不得增加复制行。 - -这份记录来自维护者在 2026-11-11 任务中的书面指令;合并前在 PR 中再次确认后生效。 - -产品与实现分别见 [product.md](../docs/product.md) 和 -[architecture.md](../docs/agent/architecture.md),实际验证范围见 -[validation.md](../docs/agent/validation.md)。名称为 option-berth,CLI 为 `oberth`, -清单为 `oberth.yaml`,环境变量前缀为 `BERTH_`。 - -隐私清理、重构或建立独立 Git 历史,均不得删除适用的来源、版权与许可。 diff --git a/engine/internal/buildinfo/buildinfo.go b/engine/internal/buildinfo/buildinfo.go index 5be39e29..7ec87149 100644 --- a/engine/internal/buildinfo/buildinfo.go +++ b/engine/internal/buildinfo/buildinfo.go @@ -3,8 +3,8 @@ // // It is deliberately separate from the update check. `oberth version --check` // only reads the latest published tag; nothing in this tree downloads a binary -// or replaces the running executable — see UPSTREAM.md for why the fork removed -// that machinery instead of repointing it. +// or replaces the running executable — the fork removed that machinery instead +// of repointing it. package buildinfo import ( diff --git a/engine/internal/groups/config.go b/engine/internal/groups/config.go index 412d5031..64dda9d7 100644 --- a/engine/internal/groups/config.go +++ b/engine/internal/groups/config.go @@ -32,8 +32,8 @@ const LegacyConfigName = ".oberth.yaml" // Only the first one present is used; `oberth doctor` reports the rest. // // **Nothing else is read.** Earlier releases also read the spellings this -// project wore under its previous names, and the upstream project's (`sonar.*`); -// both were dropped on 2026-09-24 (decision 0022). A spelling nobody writes is a +// project wore under its previous names and one older config prefix; both were +// dropped on 2026-09-24 (decision 0022). A spelling nobody writes is a // compatibility branch nobody exercises — and a broken one fails *silently*, // which is the failure this product likes least. // diff --git a/scripts/audit-upstream-origin.py b/scripts/audit-upstream-origin.py deleted file mode 100644 index 1209b7c6..00000000 --- a/scripts/audit-upstream-origin.py +++ /dev/null @@ -1,112 +0,0 @@ -#!/usr/bin/env python3 -"""Report how much of engine/ is still copied from the upstream Sonar checkout. - -The engine began as a copy of `github.com/raskrebs/sonar`. The Sonar notice in -`THIRD_PARTY_NOTICES.md` covers what remains, so it can be removed only when -this report is empty. That is an engine rewrite, not a rename: a rewritten file -that keeps five consecutive upstream lines is still copied code. - - git clone --depth 1 https://github.com/raskrebs/sonar /tmp/sonar - python3 scripts/audit-upstream-origin.py --upstream /tmp/sonar - -A *copied run* is a block of at least `--min-run` consecutive substantive lines -that also appears, in order, at the same relative path upstream. Blank lines, -brace-only lines and `package` / `import` clauses are ignored: they match in any -Go file and are not evidence of copying. The exit status is 1 while any copied -run remains. -""" -from __future__ import annotations - -import argparse -import difflib -import json -import pathlib -import re -import sys - -DEFAULT_ENGINE = pathlib.Path(__file__).resolve().parent.parent / "engine" -IGNORED = {"", "}", "{", ")", "(", "};", "})", "return", "return nil"} -IMPORT_SPEC = re.compile(r'^[A-Za-z0-9_.]*\s*"[^"]*"$') - - -def substantive(line: str) -> bool: - stripped = line.strip() - if stripped in IGNORED or stripped.startswith(("package ", "import ")): - return False - # Import specifiers and bare string literals appear in any Go file. - return not IMPORT_SPEC.match(stripped) - - -def copied_runs(upstream: str, candidate: str, min_run: int) -> list[tuple[int, int, int]]: - """Return matching (upstream index, candidate index, length) runs at or above min_run.""" - left = [line.strip() for line in upstream.splitlines() if substantive(line)] - right = [line.strip() for line in candidate.splitlines() if substantive(line)] - matcher = difflib.SequenceMatcher(None, left, right, autojunk=False) - return [(block.a, block.b, block.size) for block in matcher.get_matching_blocks() - if block.size >= min_run] - - -def audit(engine: pathlib.Path, upstream: pathlib.Path, min_run: int = 5) -> dict: - if not engine.is_dir(): - raise SystemExit(f"audit-upstream-origin: no engine tree at {engine}") - if not upstream.is_dir(): - raise SystemExit(f"audit-upstream-origin: no upstream checkout at {upstream}") - files = [] - for path in sorted(engine.rglob("*.go")): - relative = path.relative_to(engine) - other = upstream / relative - if not other.is_file(): - continue - runs = copied_runs(other.read_text(errors="replace"), path.read_text(errors="replace"), min_run) - if not runs: - continue - files.append({ - "path": str(relative), - "runs": len(runs), - "lines": sum(size for _, _, size in runs), - "largest": max(size for _, _, size in runs), - }) - files.sort(key=lambda item: (-item["lines"], item["path"])) - return { - "engine": str(engine), - "upstream": str(upstream), - "min_run": min_run, - "files": files, - "runs": sum(item["runs"] for item in files), - "lines": sum(item["lines"] for item in files), - } - - -def report(result: dict, limit: int) -> str: - lines = [f"copied files: {len(result['files'])}, runs: {result['runs']}, " - f"lines: {result['lines']} (min run {result['min_run']})"] - for item in result["files"][:limit]: - lines.append(f" {item['lines']:5d} lines / {item['runs']:3d} runs / " - f"largest {item['largest']:3d} {item['path']}") - if len(result["files"]) > limit: - lines.append(f" ... {len(result['files']) - limit} more files") - return "\n".join(lines) - - -def main(argv: list[str] | None = None) -> int: - parser = argparse.ArgumentParser(description="Audit upstream-copied code in engine/.") - parser.add_argument("--engine", type=pathlib.Path, default=DEFAULT_ENGINE) - parser.add_argument("--upstream", type=pathlib.Path, required=True, - help="checkout of github.com/raskrebs/sonar") - parser.add_argument("--min-run", type=int, default=5, - help="consecutive substantive lines that count as a copied run") - parser.add_argument("--json", action="store_true") - parser.add_argument("--limit", type=int, default=20, help="files to print in text mode") - args = parser.parse_args(argv) - if args.min_run < 1: - parser.error("--min-run must be at least 1") - result = audit(args.engine, args.upstream, args.min_run) - if args.json: - print(json.dumps(result, indent=2, ensure_ascii=False)) - else: - print(report(result, args.limit)) - return 1 if result["lines"] else 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/scripts/audit_upstream_origin_test.py b/scripts/audit_upstream_origin_test.py deleted file mode 100644 index cdc38e25..00000000 --- a/scripts/audit_upstream_origin_test.py +++ /dev/null @@ -1,134 +0,0 @@ -"""Check the upstream audit counts copied runs and only clears an rewritten tree.""" -import contextlib -import importlib.util -import io -from pathlib import Path -import tempfile -import unittest - -SPEC = importlib.util.spec_from_file_location( - "audit_upstream_origin", Path(__file__).with_name("audit-upstream-origin.py")) -audit = importlib.util.module_from_spec(SPEC) -SPEC.loader.exec_module(audit) - -COPIED = """package ports - -func Collect(rows []Row) []Row { -\tkept := make([]Row, 0, len(rows)) -\tfor _, row := range rows { -\t\tif row.Port == 0 { -\t\t\tcontinue -\t\t} -\t\tkept = append(kept, row) -\t} -\treturn kept -} -""" - -REWRITTEN = """package ports - -// Collect drops rows without a port and preserves the caller's order. -func Collect(rows []Row) []Row { -\tswitch len(rows) { -\tcase 0: -\t\treturn nil -\t} -\tout := rows[:0] -\tfor i := range rows { -\t\tif rows[i].Port != 0 { -\t\t\tout = append(out, rows[i]) -\t\t} -\t} -\treturn out -} -""" - -SHARED_HEADER = """package ports - -import "sort" -""" - - -class AuditTests(unittest.TestCase): - def setUp(self): - self.temp = tempfile.TemporaryDirectory() - root = Path(self.temp.name) - self.engine = root / "engine" - self.upstream = root / "sonar" - (self.engine / "internal/ports").mkdir(parents=True) - (self.upstream / "internal/ports").mkdir(parents=True) - - def tearDown(self): - self.temp.cleanup() - - def write(self, side, name, text): - (side / "internal/ports" / name).write_text(text) - - def test_identical_file_is_reported_and_fails_the_gate(self): - self.write(self.upstream, "collect.go", COPIED) - self.write(self.engine, "collect.go", COPIED) - result = audit.audit(self.engine, self.upstream) - self.assertEqual([item["path"] for item in result["files"]], ["internal/ports/collect.go"]) - self.assertGreater(result["lines"], 0) - out = io.StringIO() - with contextlib.redirect_stdout(out): - status = audit.main(["--engine", str(self.engine), "--upstream", str(self.upstream)]) - self.assertEqual(status, 1) - self.assertIn("collect.go", out.getvalue()) - - def test_rewritten_file_with_only_shared_boilerplate_is_clean(self): - self.write(self.upstream, "collect.go", SHARED_HEADER + COPIED) - self.write(self.engine, "collect.go", SHARED_HEADER + REWRITTEN) - result = audit.audit(self.engine, self.upstream) - self.assertEqual(result["files"], []) - with contextlib.redirect_stdout(io.StringIO()): - status = audit.main(["--engine", str(self.engine), "--upstream", str(self.upstream)]) - self.assertEqual(status, 0) - - def test_only_same_path_go_files_are_compared(self): - self.write(self.upstream, "collect.go", COPIED) - self.write(self.engine, "unrelated.go", COPIED) - (self.upstream / "internal/ports/notes.md").write_text(COPIED) - (self.engine / "internal/ports/notes.md").write_text(COPIED) - result = audit.audit(self.engine, self.upstream) - self.assertEqual(result["files"], []) - - def test_shared_import_list_is_not_evidence(self): - imports = """import ( -\t"fmt" -\t"os" -\t"sort" -\t"strings" -\t"time" -) -""" - body = "\nfunc Mine() {\n\tprintln(1)\n\tprintln(2)\n\tprintln(3)\n\tprintln(4)\n\tprintln(5)\n}\n" - self.write(self.upstream, "collect.go", imports + body) - self.write(self.engine, "collect.go", imports + body) - # The five shared import lines do not count; the six-line function body does. - result = audit.audit(self.engine, self.upstream) - self.assertEqual(result["lines"], 6) - - def test_min_run_sets_the_evidence_threshold(self): - self.write(self.upstream, "collect.go", COPIED) - self.write(self.engine, "collect.go", SHARED_HEADER + REWRITTEN) - self.assertEqual(audit.audit(self.engine, self.upstream, min_run=3)["files"], []) - # The shared function signature is one line: below the default threshold, counted at 1. - self.assertEqual(audit.audit(self.engine, self.upstream, min_run=1)["lines"], 1) - - def test_missing_trees_are_named_in_the_error(self): - with self.assertRaises(SystemExit) as caught: - audit.audit(self.engine / "absent", self.upstream) - self.assertIn("no engine tree", str(caught.exception)) - - def test_json_report_is_machine_readable(self): - self.write(self.upstream, "collect.go", COPIED) - self.write(self.engine, "collect.go", COPIED) - out = io.StringIO() - with contextlib.redirect_stdout(out): - audit.main(["--engine", str(self.engine), "--upstream", str(self.upstream), "--json"]) - self.assertIn('"path": "internal/ports/collect.go"', out.getvalue()) - - -if __name__ == "__main__": - unittest.main() diff --git a/scripts/check_docs_test.py b/scripts/check_docs_test.py index 4489890c..4ba71ef6 100644 --- a/scripts/check_docs_test.py +++ b/scripts/check_docs_test.py @@ -176,12 +176,12 @@ def test_chinese_side_must_use_the_zh_counterpart(self): def test_links_outside_docs_are_not_locale_checked(self): with tempfile.TemporaryDirectory() as temporary: root = Path(temporary) - write(root, "engine/UPSTREAM.md", "# Upstream\n") + write(root, "engine/LICENSE", "MIT\n") pair( root, "demo", - GOOD_EN + "\nSee [upstream](../engine/UPSTREAM.md).\n", - GOOD_ZH + "\n见 [上游](../engine/UPSTREAM.md)。\n", + GOOD_EN + "\nSee [the license](../engine/LICENSE).\n", + GOOD_ZH + "\n见 [许可](../engine/LICENSE)。\n", ) self.assertEqual(docs_check.check_pair(root / "docs" / "demo.md"), []) diff --git a/scripts/license_test.py b/scripts/license_test.py index 94559a3d..ecc5d496 100644 --- a/scripts/license_test.py +++ b/scripts/license_test.py @@ -1,4 +1,4 @@ -"""Keep the project license, the reused-work notice and its delivery path in agreement.""" +"""Keep the project license, the third-party notices and their delivery path in agreement.""" from pathlib import Path import unittest @@ -13,17 +13,14 @@ def test_project_licenses_list_only_project_copyright(self): for name in ("LICENSE", "engine/LICENSE"): text = (ROOT / name).read_text() self.assertIn("Copyright (c) 2026 SheathedSharp", text) - self.assertNotIn("RasKrebs", text) self.assertIn("THIRD_PARTY_NOTICES.md", text) self.assertIn(MIT_GRANT, text) self.assertIn(MIT_DISCLAIMER, text) - def test_reused_work_notice_keeps_its_full_mit_text(self): + def test_third_party_notices_cover_the_shipped_assets(self): text = (ROOT / "THIRD_PARTY_NOTICES.md").read_text() - self.assertIn("Copyright (c) 2026 RasKrebs", text) - self.assertIn(MIT_GRANT, text) - self.assertIn(MIT_DISCLAIMER, text) - self.assertIn("engine/UPSTREAM.md", text) + for section in ("GSAP", "SIL Open Font License", "Go 依赖"): + self.assertIn(section, text) def test_release_archives_ship_the_notice(self): # The notice is only kept if every distributed copy carries it; a source