mirror of
https://github.com/tradecatlabs/vibe-coding-cn.git
synced 2026-08-04 06:37:44 +00:00
docs: add metadata path gate
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 Actions:develop/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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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` |
|
||||
|
||||
@@ -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/
|
||||
|
||||
@@ -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` 路径和锚点检查脚本。
|
||||
|
||||
@@ -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())
|
||||
Reference in New Issue
Block a user