docs: add metadata path gate

This commit is contained in:
tukuaiai
2026-05-04 00:14:13 +08:00
parent 7ab504b055
commit f4889accd7
7 changed files with 156 additions and 6 deletions
+2
View File
@@ -28,6 +28,8 @@ jobs:
run: python3 scripts/check-local-links.py
- name: Check markdown details and summaries
run: python3 scripts/check-markdown-details.py
- name: Check metadata paths and anchors
run: python3 scripts/check-metadata.py
link-checker:
runs-on: ubuntu-latest
+6 -3
View File
@@ -9,7 +9,7 @@
### 允许的操作
- 读取、修改顶层文档:`README.md``AGENTS.md``CONTRIBUTING.md`
- 读取、修改 `docs/``prompts/``skills/``tools/config/``tools/external/` 下的文档与代码
- 执行 `make lint``make check-links``make check-details`、prompts-library 转换工具
- 执行 `make lint``make check-links``make check-details``make check-metadata`prompts-library 转换工具
- 新增/修改提示词、技能、文档
- 提交符合规范的 commit
@@ -66,6 +66,7 @@ git push origin develop
| `make lint` | 校验全仓库 Markdown | 需安装 markdownlint-cli |
| `make check-links` | 校验仓库内 Markdown 相对链接 | Python 3 |
| `make check-details` | 校验 Markdown 折叠块 `<details>/<summary>` 结构 | Python 3 |
| `make check-metadata` | 校验 metadata 路径与锚点 | Python 3 |
| `make test` | 执行本地质量门禁 | Node.js 22+、Python 3 |
| `git submodule update --init --recursive` | 初始化外部 Git 仓库指针 | Git |
| `cd tools/prompts-library && python3 main.py` | 提示词格式转换 | `pip install -r tools/prompts-library/requirements.txt` |
@@ -211,6 +212,7 @@ git push origin develop
- `.github/workflows/ci.yml` - GitHub Actionsdevelop/master 分支 markdown-lint + link-checker
- `scripts/check-local-links.py` - 仓库内 Markdown 相对链接与锚点检查脚本,供 `make check-links` 与 CI 使用
- `scripts/check-markdown-details.py` - 仓库内 Markdown 折叠块结构检查脚本,供 `make check-details` 与 CI 使用
- `scripts/check-metadata.py` - metadata 路径与锚点检查脚本,供 `make check-metadata` 与 CI 使用
- `tools/prompts-library/main.py` - 提示词转换工具入口
- `docs/getting-started/README.md` - 从零开始完整入门,包含学习地图、Vibe Coding 经验、网络配置、CLI 配置与开发环境搭建
- `docs/concepts/README.md#concept-problem-solving` - 问题定义与求解路径底层模型
@@ -259,7 +261,8 @@ feat|fix|docs|chore|refactor|test: scope - summary
1. `markdown-lint` - Markdown 格式检查
2. `check local markdown links and anchors` - 仓库内相对链接与锚点检查
3. `check markdown details and summaries` - Markdown 折叠块结构检查
4. `link-checker` - 链接有效性检查
4. `check metadata paths and anchors` - metadata 路径与锚点检查
5. `link-checker` - 链接有效性检查
### 提交前清单
- [ ] 运行 `make lint` 通过
@@ -316,7 +319,7 @@ make test
2. **Conversion Tool**: 使用 Python + pandas + openpyxl
3. **Documentation Standard**: 用户文档使用中文;代码/文件名使用英文
4. **Skills**: 每个技能有独立的 `SKILL.md`
5. **Quality Gates**: `make test` 执行 Markdown lint、本地相对链接/锚点检查折叠块结构检查
5. **Quality Gates**: `make test` 执行 Markdown lint、本地相对链接/锚点检查折叠块结构检查与 metadata 路径检查
## Development Workflow
+7 -2
View File
@@ -1,6 +1,6 @@
# Makefile for Vibe Coding Guide
.PHONY: help lint check-links check-details build test clean
.PHONY: help lint check-links check-details check-metadata build test clean
help:
@echo "Makefile for Vibe Coding Guide"
@@ -10,6 +10,7 @@ help:
@echo " lint - Lint all markdown files"
@echo " check-links - Check local markdown links and anchors"
@echo " check-details - Check markdown details/summary blocks"
@echo " check-metadata - Check metadata paths and anchors"
@echo " build - Verify knowledge base has no build step"
@echo " test - Run repository quality gates"
@echo " clean - Remove ignored generated caches"
@@ -28,10 +29,14 @@ check-details:
@echo "Checking markdown details/summary blocks..."
@python3 scripts/check-markdown-details.py
check-metadata:
@echo "Checking metadata paths and anchors..."
@python3 scripts/check-metadata.py
build:
@echo "No build step: this repository is a documentation and knowledge-base project."
test: lint check-links check-details
test: lint check-links check-details check-metadata
@echo "Quality gates complete."
clean:
+1
View File
@@ -216,6 +216,7 @@ pip install -r tools/prompts-library/scripts/requirements.txt
| 全仓 Markdown lint | `make lint` | `Makefile` + `.github/lint_config.json` |
| 本地相对链接检查 | `make check-links` | `scripts/check-local-links.py` |
| 折叠块结构检查 | `make check-details` | `scripts/check-markdown-details.py` |
| Metadata 路径检查 | `make check-metadata` | `scripts/check-metadata.py` |
| 全部本地质量门禁 | `make test` | `Makefile` |
| 提示词格式转换 | `cd tools/prompts-library && python3 main.py` | `tools/prompts-library/main.py` |
| Skill 严格校验示例 | `skills/auto-skill/scripts/validate-skill.sh skills/auto-skill --strict` | `skills/auto-skill/scripts/validate-skill.sh` |
+1 -1
View File
@@ -2,7 +2,7 @@ redirects:
- from: docs/guides/getting-started/
to: docs/getting-started/
- from: docs/principles/fundamentals/
to: docs/concepts/ 或 docs/references/
to: docs/README.md
- from: docs/principles/philosophy/
to: docs/philosophy/
- from: docs/concepts/philosophy/
+1
View File
@@ -6,3 +6,4 @@
- `check-local-links.py`:仓库内 Markdown 相对链接与锚点检查脚本。
- `check-markdown-details.py`:仓库内 Markdown `<details>/<summary>` 折叠块结构检查脚本。
- `check-metadata.py``metadata/taxonomy.yml``metadata/redirects.yml` 路径和锚点检查脚本。
+138
View File
@@ -0,0 +1,138 @@
#!/usr/bin/env python3
"""Check metadata path references resolve to real repository paths and anchors."""
from __future__ import annotations
import re
import sys
import urllib.parse
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
METADATA_FILES = [
Path("metadata/taxonomy.yml"),
Path("metadata/redirects.yml"),
]
def github_slug(title: str) -> str:
title = re.sub(r"<[^>]+>", "", title.strip().lower())
title = re.sub(r"[`*_~]", "", title)
title = re.sub(r"[^\w\u4e00-\u9fff\- ]+", "", title)
title = re.sub(r"\s+", "-", title).strip("-")
return title
def markdown_anchors(path: Path) -> set[str]:
text = path.read_text(encoding="utf-8", errors="ignore")
anchors = set(re.findall(r"<a\s+id=[\"']([^\"']+)[\"']", text))
used: dict[str, int] = {}
in_fence = False
fence_marker = ""
for line in text.splitlines():
stripped = line.lstrip()
if stripped.startswith(("```", "~~~")):
marker = stripped[:3]
if not in_fence:
in_fence = True
fence_marker = marker
elif marker == fence_marker:
in_fence = False
fence_marker = ""
continue
if in_fence:
continue
heading = re.match(r"^(#{1,6})\s+(.+?)\s*#*\s*$", line)
if not heading:
continue
slug = github_slug(heading.group(2))
if not slug:
continue
count = used.get(slug, 0)
used[slug] = count + 1
anchors.add(slug if count == 0 else f"{slug}-{count}")
return anchors
def metadata_targets(path: Path) -> list[tuple[int, str]]:
targets: list[tuple[int, str]] = []
text = path.read_text(encoding="utf-8", errors="ignore")
for lineno, line in enumerate(text.splitlines(), start=1):
stripped = line.strip()
if stripped.startswith(("path:", "entry:", "agent_guide:", "to:")):
_, value = stripped.split(":", 1)
targets.append((lineno, value.strip().strip("\"'")))
continue
if stripped.startswith("- "):
value = stripped[2:].strip().strip("\"'")
if value.startswith("from:"):
continue
if value.startswith(("path:", "entry:", "agent_guide:", "to:")):
_, value = value.split(":", 1)
targets.append((lineno, value.strip().strip("\"'")))
continue
if value.startswith((".", "/")) or "/" in value or value.endswith(".md"):
targets.append((lineno, value))
return targets
def validate_target(source: Path, lineno: int, raw: str, anchor_cache: dict[Path, set[str]]) -> str | None:
if not raw or raw.startswith(("http://", "https://", "mailto:", "tel:")):
return None
if "" in raw:
return f"{source}:{lineno}: ambiguous metadata target: {raw}"
target = urllib.parse.unquote(raw.strip("<>"))
path_part, _, anchor = target.partition("#")
destination = (ROOT / path_part).resolve()
try:
destination.relative_to(ROOT)
except ValueError:
return None
if not destination.exists():
return f"{source}:{lineno}: missing metadata target: {raw}"
if anchor and destination.suffix.lower() == ".md":
if destination not in anchor_cache:
anchor_cache[destination] = markdown_anchors(destination)
if anchor not in anchor_cache[destination]:
rel = destination.relative_to(ROOT)
return f"{source}:{lineno}: missing metadata anchor: {rel}#{anchor}"
return None
def main() -> int:
errors: list[str] = []
anchor_cache: dict[Path, set[str]] = {}
for rel_path in METADATA_FILES:
path = ROOT / rel_path
if not path.exists():
errors.append(f"{rel_path}: missing metadata file")
continue
for lineno, target in metadata_targets(path):
error = validate_target(rel_path, lineno, target, anchor_cache)
if error:
errors.append(error)
if errors:
print("METADATA_ERRORS")
for error in errors:
print(error)
print(f"TOTAL={len(errors)}")
return 1
print("OK metadata paths checked")
return 0
if __name__ == "__main__":
sys.exit(main())