Makefile is a file: True
restored issues: [('source.navigation', 'local tree source requires a same-repository, same-revision source file')]
offline issues: [('source.navigation', 'local tree source requires a same-repository, same-revision source file')]
问题描述
governance/contracts/checks/source_navigation.py:59,66使用路径末级名称是否包含.来推断本地 Git 源码路径是不是文件。严格来源导航遇到固定提交 checkout 中真实存在的Makefile等无扩展名文件时,第 66 行会把它排除在同仓库、同 revision 的文件链接之外,并错误报告source.navigation。checkout 尚未恢复时,第 59、66 行也会按文件名猜测类型;这与governance/specs/schemas.md所述“离线缺根时只校验路径形状”不符。在已恢复 checkout 中,这会阻断引用合法无扩展名源码文件的卡片通过严格 Lint;离线时则可能对无法确认类型的安全路径产生误报。
环境信息
cann/cannbot-knowledge,f50303368db6b5c2cea311d9d7a64f8e09ad49e9重现步骤
在目标仓库根目录执行以下命令。脚本仅在临时目录创建本地 Git fixture,不访问网络或 NPU:
PYTHONDONTWRITEBYTECODE=1 python3 - <<'PY' from pathlib import Path import subprocess from tempfile import TemporaryDirectory from types import SimpleNamespace from governance.contracts.checks.source_navigation import check def probe(root: Path, revision: str): contract = SimpleNamespace( root=root, local_sources={"fixture": { "kind": "git", "root": "repo", "repository": "https://gitcode.com/acme/repo", "revisions": [revision], }}, ) document = SimpleNamespace( path=root / "knowledge/card.md", metadata={"sources": [{"resource": f"repo/{revision}/src"}]}, body=f"[Makefile](../repo/{revision}/Makefile)", ) return [(issue.code, issue.message) for issue in check( contract, document, SimpleNamespace(strict_navigation=True) )] with TemporaryDirectory() as temporary: root = Path(temporary) checkout = root / "repo" / "work" (checkout / "src").mkdir(parents=True) (checkout / "src" / "source.cpp").write_text("int source;\n", encoding="utf-8") (checkout / "Makefile").write_text("all:\n\t@true\n", encoding="utf-8") subprocess.run(["git", "init", "--quiet", str(checkout)], check=True) subprocess.run(["git", "-C", str(checkout), "add", "."], check=True) subprocess.run([ "git", "-C", str(checkout), "-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "--quiet", "-m", "fixture", ], check=True) revision = subprocess.run( ["git", "-C", str(checkout), "rev-parse", "HEAD"], check=True, capture_output=True, text=True, ).stdout.strip() checkout.rename(root / "repo" / revision) print("Makefile is a file:", (root / "repo" / revision / "Makefile").is_file()) print("restored issues:", probe(root, revision)) with TemporaryDirectory() as temporary: root = Path(temporary) print("offline issues:", probe(root, "a" * 40)) PY预期结果
已恢复且 revision 匹配的 checkout 中,存在的
Makefile应作为文件通过严格来源导航;链接到目录不应冒充文件。checkout 缺失时,应按既有规范只校验安全路径与登记 revision,不根据后缀推断文件或目录。实际结果
日志/最小复现
上述命令使用临时 Git 仓库生成真实 40 位 commit,并将 checkout 放在登记的
repo/<revision>路径下。当前严格导航回归覆盖了远端 Git URL 的 tree/blob 关系,但没有覆盖本地 checkout 中的无扩展名文件;离线来源规则也没有覆盖该路径推断。影响与规避方式
受影响者是在源码 checkout 已恢复时,按
knowledge-lint的严格导航流程引用Makefile、LICENSE等合法无扩展名文件的知识卡贡献者;Lint 会以source.navigation拒绝卡片。暂时增加同一仓库、同一 revision 下另一个带扩展名文件的正文链接可能绕过该检查,但不能证明目标Makefile的类型,不是等价修复;不建议关闭严格导航。建议的回归测试
为
governance/contracts/checks/source_navigation.py增加本地来源测试,覆盖:已恢复 checkout 中无扩展名普通文件应通过、已恢复 checkout 中目录仍应拒绝、checkout 缺失时只执行路径形状检查。测试需确认 repository 与 revision 匹配条件仍然成立。AI 辅助说明