mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-12 13:14:30 +08:00
Compare commits
3 Commits
d836ec8027
...
fe4fd1aa5e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe4fd1aa5e | ||
|
|
ce6bab2301 | ||
|
|
1239fb4651 |
52
.agents/skills/README.md
Normal file
52
.agents/skills/README.md
Normal file
@ -0,0 +1,52 @@
|
||||
# GFramework Skills
|
||||
|
||||
文档工作流的公开入口已统一为 `gframework-doc-refresh`。
|
||||
|
||||
## 公开入口
|
||||
|
||||
### `gframework-doc-refresh`
|
||||
|
||||
按源码模块驱动文档刷新,而不是按 `guide`、`tutorial`、`api` 等类型拆入口。
|
||||
|
||||
适用场景:
|
||||
|
||||
- 刷新某个模块的 landing page
|
||||
- 复核专题页是否与源码、测试、README 一致
|
||||
- 评估是否需要补 API reference 或教程
|
||||
- 在 adoption path 不清晰时引入 `ai-libs/` 消费者接法作为补充证据
|
||||
|
||||
推荐调用:
|
||||
|
||||
```bash
|
||||
/gframework-doc-refresh <module>
|
||||
```
|
||||
|
||||
示例:
|
||||
|
||||
```bash
|
||||
/gframework-doc-refresh Core
|
||||
/gframework-doc-refresh Godot.SourceGenerators
|
||||
/gframework-doc-refresh Cqrs
|
||||
```
|
||||
|
||||
## 共享资源
|
||||
|
||||
- `_shared/DOCUMENTATION_STANDARDS.md`
|
||||
- 统一的文档规则、证据顺序与验证要求
|
||||
- `_shared/module-map.json`
|
||||
- 机器可读的模块映射表
|
||||
- `_shared/module-config.sh`
|
||||
- 轻量 shell 辅助函数
|
||||
|
||||
## 内部资源
|
||||
|
||||
`gframework-doc-refresh/` 下包含:
|
||||
|
||||
- `references/`
|
||||
- 模块选择、证据顺序、输出策略
|
||||
- `templates/`
|
||||
- landing page、专题页、API reference、教程模板
|
||||
- `scripts/`
|
||||
- 模块扫描与文档验证脚本
|
||||
|
||||
旧 `vitepress-*` skills 不再作为并列公开入口保留。
|
||||
108
.agents/skills/_shared/DOCUMENTATION_STANDARDS.md
Normal file
108
.agents/skills/_shared/DOCUMENTATION_STANDARDS.md
Normal file
@ -0,0 +1,108 @@
|
||||
# GFramework 文档编写规范
|
||||
|
||||
本文件只保留跨模块稳定生效的写作与校验规则,不再维护容易失真的固定页面清单。
|
||||
|
||||
模块到源码、测试、README、`docs/zh-CN` 栏目以及 `ai-libs/` 参考入口的映射,统一以
|
||||
`.agents/skills/_shared/module-map.json` 为准。
|
||||
|
||||
## 证据顺序
|
||||
|
||||
统一按以下顺序判断文档应写什么、删什么、保留什么:
|
||||
|
||||
1. 源码、公开 XML docs、`*.csproj`
|
||||
2. 对应测试和 snapshot
|
||||
3. 模块 `README.md`
|
||||
4. 当前 `docs/zh-CN` 页面
|
||||
5. `ai-libs/` 下已验证的消费者项目
|
||||
6. 归档文档,仅在前述证据无法解释当前行为时回看
|
||||
|
||||
不要把旧文档互相抄写当成“更新”。
|
||||
|
||||
## 模块驱动规则
|
||||
|
||||
- 先按源码模块归一化输入,再决定落到 landing page、专题页、API reference、教程还是仅做校验。
|
||||
- 如果用户给的是栏目名而不是源码模块名,先映射回模块;若仍有歧义,只给归一化建议,不直接生成文档。
|
||||
- 文档栏目是派生输出,不是主输入源。
|
||||
|
||||
## `ai-libs/` 使用边界
|
||||
|
||||
`ai-libs/` 只用于补消费者视角证据:
|
||||
|
||||
- 验证真实接入目录结构
|
||||
- 查最小 wiring、扩展点装配方式
|
||||
- 给 adoption path 提供端到端例子
|
||||
|
||||
不要用 `ai-libs/` 覆盖以下事实:
|
||||
|
||||
- 公共 API 契约
|
||||
- 当前版本支持范围
|
||||
- Source Generator 诊断与生成语义
|
||||
|
||||
如果 `ai-libs/` 与当前源码或测试冲突,以当前仓库实现为准,并在文档里写明迁移或兼容边界。
|
||||
|
||||
## Markdown 规则
|
||||
|
||||
### 泛型与 HTML 转义
|
||||
|
||||
代码块外出现泛型或 XML 标签时必须转义:
|
||||
|
||||
- `List<T>`
|
||||
- `Result<TValue, TError>`
|
||||
- `<summary>`
|
||||
- `<param>`
|
||||
|
||||
### Frontmatter
|
||||
|
||||
每个文档都必须包含合法 frontmatter:
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: 文档标题
|
||||
description: 1-2 句话描述当前页面解决什么问题
|
||||
---
|
||||
```
|
||||
|
||||
### 代码块
|
||||
|
||||
- 始终标注语言,如 `csharp`、`bash`、`json`
|
||||
- 示例只保留当前实现可追溯的最小路径
|
||||
- 必要时写中文注释解释接入原因或边界,不要堆砌与代码同步无关的注释
|
||||
|
||||
### 链接
|
||||
|
||||
- 只链接到当前仓库真实存在的页面
|
||||
- 站内链接优先使用 `/zh-CN/...` 形式
|
||||
- 如果文档站不允许跳出 `docs/` 根目录,就不要把仓库 README 写成站内链接
|
||||
|
||||
## 输出优先级
|
||||
|
||||
统一按以下顺序决定产出:
|
||||
|
||||
1. 先修模块 README、landing page 与 adoption path
|
||||
2. 再修失真的专题页
|
||||
3. 再补 API reference
|
||||
4. 最后才补教程
|
||||
|
||||
## 验证清单
|
||||
|
||||
- [ ] frontmatter 正确
|
||||
- [ ] 代码块语言标记齐全
|
||||
- [ ] 泛型和 XML 标签已转义
|
||||
- [ ] 站内链接存在
|
||||
- [ ] 示例与当前实现一致
|
||||
- [ ] `ai-libs/` 只作为消费者接入参考,没有覆盖源码契约
|
||||
|
||||
## 验证工具
|
||||
|
||||
统一复用 `gframework-doc-refresh/scripts/` 下的校验脚本:
|
||||
|
||||
- `validate-frontmatter.sh`
|
||||
- `validate-links.sh`
|
||||
- `validate-code-blocks.sh`
|
||||
- `validate-all.sh`
|
||||
|
||||
需要站点级验证时,执行:
|
||||
|
||||
```bash
|
||||
cd docs && bun run build
|
||||
```
|
||||
271
.agents/skills/_shared/module-config.sh
Normal file
271
.agents/skills/_shared/module-config.sh
Normal file
@ -0,0 +1,271 @@
|
||||
#!/bin/bash
|
||||
# 共享的模块配置
|
||||
# 机器可读映射以 .agents/skills/_shared/module-map.json 为准。
|
||||
|
||||
normalize_module() {
|
||||
local INPUT
|
||||
INPUT="$(echo "$1" | tr '[:upper:]' '[:lower:]' | tr ' ' '-' | tr '_' '-')"
|
||||
|
||||
case "$INPUT" in
|
||||
core|core-runtime|runtime-core|core-module)
|
||||
echo "Core"
|
||||
;;
|
||||
core.abstractions|core-abstractions)
|
||||
echo "Core.Abstractions"
|
||||
;;
|
||||
core.sourcegenerators|core-source-generators|core-sourcegenerators)
|
||||
echo "Core.SourceGenerators"
|
||||
;;
|
||||
core.sourcegenerators.abstractions|core-source-generators-abstractions)
|
||||
echo "Core.SourceGenerators.Abstractions"
|
||||
;;
|
||||
game|game-runtime|runtime-game|game-module)
|
||||
echo "Game"
|
||||
;;
|
||||
game.abstractions|game-abstractions)
|
||||
echo "Game.Abstractions"
|
||||
;;
|
||||
game.sourcegenerators|game-source-generators)
|
||||
echo "Game.SourceGenerators"
|
||||
;;
|
||||
godot|godot-runtime|runtime-godot|godot-module)
|
||||
echo "Godot"
|
||||
;;
|
||||
godot.sourcegenerators|godot-source-generators|godot-generators)
|
||||
echo "Godot.SourceGenerators"
|
||||
;;
|
||||
godot.sourcegenerators.abstractions|godot-source-generators-abstractions)
|
||||
echo "Godot.SourceGenerators.Abstractions"
|
||||
;;
|
||||
cqrs|mediator|cqrs-module)
|
||||
echo "Cqrs"
|
||||
;;
|
||||
cqrs.abstractions|cqrs-abstractions)
|
||||
echo "Cqrs.Abstractions"
|
||||
;;
|
||||
cqrs.sourcegenerators|cqrs-source-generators)
|
||||
echo "Cqrs.SourceGenerators"
|
||||
;;
|
||||
ecs|ecs.arch|ecs-arch)
|
||||
echo "Ecs.Arch"
|
||||
;;
|
||||
ecs.arch.abstractions|ecs-arch-abstractions)
|
||||
echo "Ecs.Arch.Abstractions"
|
||||
;;
|
||||
sourcegenerators.common|source-generators-common)
|
||||
echo "SourceGenerators.Common"
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
get_all_modules() {
|
||||
cat <<'EOF'
|
||||
Core
|
||||
Core.Abstractions
|
||||
Core.SourceGenerators
|
||||
Core.SourceGenerators.Abstractions
|
||||
Game
|
||||
Game.Abstractions
|
||||
Game.SourceGenerators
|
||||
Godot
|
||||
Godot.SourceGenerators
|
||||
Godot.SourceGenerators.Abstractions
|
||||
Cqrs
|
||||
Cqrs.Abstractions
|
||||
Cqrs.SourceGenerators
|
||||
Ecs.Arch
|
||||
Ecs.Arch.Abstractions
|
||||
SourceGenerators.Common
|
||||
EOF
|
||||
}
|
||||
|
||||
is_valid_module() {
|
||||
normalize_module "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
get_source_dirs() {
|
||||
local MODULE
|
||||
MODULE="$(normalize_module "$1")" || return 1
|
||||
|
||||
case "$MODULE" in
|
||||
Core)
|
||||
echo "GFramework.Core"
|
||||
;;
|
||||
Core.Abstractions)
|
||||
echo "GFramework.Core.Abstractions"
|
||||
;;
|
||||
Core.SourceGenerators)
|
||||
echo "GFramework.Core.SourceGenerators"
|
||||
;;
|
||||
Core.SourceGenerators.Abstractions)
|
||||
echo "GFramework.Core.SourceGenerators.Abstractions"
|
||||
;;
|
||||
Game)
|
||||
echo "GFramework.Game"
|
||||
;;
|
||||
Game.Abstractions)
|
||||
echo "GFramework.Game.Abstractions"
|
||||
;;
|
||||
Game.SourceGenerators)
|
||||
echo "GFramework.Game.SourceGenerators"
|
||||
;;
|
||||
Godot)
|
||||
echo "GFramework.Godot"
|
||||
;;
|
||||
Godot.SourceGenerators)
|
||||
echo "GFramework.Godot.SourceGenerators"
|
||||
;;
|
||||
Godot.SourceGenerators.Abstractions)
|
||||
echo "GFramework.Godot.SourceGenerators.Abstractions"
|
||||
;;
|
||||
Cqrs)
|
||||
echo "GFramework.Cqrs"
|
||||
;;
|
||||
Cqrs.Abstractions)
|
||||
echo "GFramework.Cqrs.Abstractions"
|
||||
;;
|
||||
Cqrs.SourceGenerators)
|
||||
echo "GFramework.Cqrs.SourceGenerators"
|
||||
;;
|
||||
Ecs.Arch)
|
||||
echo "GFramework.Ecs.Arch"
|
||||
;;
|
||||
Ecs.Arch.Abstractions)
|
||||
echo "GFramework.Ecs.Arch.Abstractions"
|
||||
;;
|
||||
SourceGenerators.Common)
|
||||
echo "GFramework.SourceGenerators.Common"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
get_test_projects() {
|
||||
local MODULE
|
||||
MODULE="$(normalize_module "$1")" || return 1
|
||||
|
||||
case "$MODULE" in
|
||||
Core|Core.Abstractions)
|
||||
echo "GFramework.Core.Tests/GFramework.Core.Tests.csproj"
|
||||
;;
|
||||
Core.SourceGenerators|Core.SourceGenerators.Abstractions|Game.SourceGenerators|Cqrs.SourceGenerators|SourceGenerators.Common)
|
||||
echo "GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj"
|
||||
;;
|
||||
Game|Game.Abstractions)
|
||||
echo "GFramework.Game.Tests/GFramework.Game.Tests.csproj"
|
||||
;;
|
||||
Godot)
|
||||
echo "GFramework.Godot.Tests/GFramework.Godot.Tests.csproj"
|
||||
;;
|
||||
Godot.SourceGenerators|Godot.SourceGenerators.Abstractions)
|
||||
echo "GFramework.Godot.SourceGenerators.Tests/GFramework.Godot.SourceGenerators.Tests.csproj"
|
||||
;;
|
||||
Cqrs|Cqrs.Abstractions)
|
||||
echo "GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj"
|
||||
;;
|
||||
Ecs.Arch|Ecs.Arch.Abstractions)
|
||||
echo "GFramework.Ecs.Arch.Tests/GFramework.Ecs.Arch.Tests.csproj"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
get_readme_paths() {
|
||||
local MODULE
|
||||
MODULE="$(normalize_module "$1")" || return 1
|
||||
|
||||
case "$MODULE" in
|
||||
Core)
|
||||
echo "GFramework.Core/README.md"
|
||||
;;
|
||||
Core.Abstractions)
|
||||
echo "GFramework.Core.Abstractions/README.md"
|
||||
;;
|
||||
Core.SourceGenerators)
|
||||
echo "GFramework.Core.SourceGenerators/README.md"
|
||||
;;
|
||||
Core.SourceGenerators.Abstractions)
|
||||
echo "GFramework.Core.SourceGenerators.Abstractions/README.md"
|
||||
;;
|
||||
Game)
|
||||
echo "GFramework.Game/README.md"
|
||||
;;
|
||||
Game.Abstractions)
|
||||
echo "GFramework.Game.Abstractions/README.md"
|
||||
;;
|
||||
Game.SourceGenerators)
|
||||
echo "GFramework.Game.SourceGenerators/README.md"
|
||||
;;
|
||||
Godot)
|
||||
echo "GFramework.Godot/README.md"
|
||||
;;
|
||||
Godot.SourceGenerators)
|
||||
echo "GFramework.Godot.SourceGenerators/README.md"
|
||||
;;
|
||||
Godot.SourceGenerators.Abstractions)
|
||||
echo "GFramework.Godot.SourceGenerators.Abstractions/README.md"
|
||||
;;
|
||||
Cqrs)
|
||||
echo "GFramework.Cqrs/README.md"
|
||||
;;
|
||||
Cqrs.Abstractions)
|
||||
echo "GFramework.Cqrs.Abstractions/README.md"
|
||||
;;
|
||||
Cqrs.SourceGenerators)
|
||||
echo "GFramework.Cqrs.SourceGenerators/README.md"
|
||||
;;
|
||||
Ecs.Arch)
|
||||
echo "GFramework.Ecs.Arch/README.md"
|
||||
;;
|
||||
Ecs.Arch.Abstractions)
|
||||
echo "GFramework.Ecs.Arch.Abstractions/README.md"
|
||||
;;
|
||||
SourceGenerators.Common)
|
||||
echo "GFramework.SourceGenerators.Common/README.md"
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
infer_module_from_namespace() {
|
||||
local NAMESPACE="$1"
|
||||
|
||||
if [[ "$NAMESPACE" == GFramework.Core.SourceGenerators.Abstractions* ]]; then
|
||||
echo "Core.SourceGenerators.Abstractions"
|
||||
elif [[ "$NAMESPACE" == GFramework.Core.SourceGenerators* ]]; then
|
||||
echo "Core.SourceGenerators"
|
||||
elif [[ "$NAMESPACE" == GFramework.Core.Abstractions* ]]; then
|
||||
echo "Core.Abstractions"
|
||||
elif [[ "$NAMESPACE" == GFramework.Core* ]]; then
|
||||
echo "Core"
|
||||
elif [[ "$NAMESPACE" == GFramework.Game.SourceGenerators* ]]; then
|
||||
echo "Game.SourceGenerators"
|
||||
elif [[ "$NAMESPACE" == GFramework.Game.Abstractions* ]]; then
|
||||
echo "Game.Abstractions"
|
||||
elif [[ "$NAMESPACE" == GFramework.Game* ]]; then
|
||||
echo "Game"
|
||||
elif [[ "$NAMESPACE" == GFramework.Godot.SourceGenerators.Abstractions* ]]; then
|
||||
echo "Godot.SourceGenerators.Abstractions"
|
||||
elif [[ "$NAMESPACE" == GFramework.Godot.SourceGenerators* ]]; then
|
||||
echo "Godot.SourceGenerators"
|
||||
elif [[ "$NAMESPACE" == GFramework.Godot* ]]; then
|
||||
echo "Godot"
|
||||
elif [[ "$NAMESPACE" == GFramework.Cqrs.SourceGenerators* ]]; then
|
||||
echo "Cqrs.SourceGenerators"
|
||||
elif [[ "$NAMESPACE" == GFramework.Cqrs.Abstractions* ]]; then
|
||||
echo "Cqrs.Abstractions"
|
||||
elif [[ "$NAMESPACE" == GFramework.Cqrs* ]]; then
|
||||
echo "Cqrs"
|
||||
elif [[ "$NAMESPACE" == GFramework.Ecs.Arch.Abstractions* ]]; then
|
||||
echo "Ecs.Arch.Abstractions"
|
||||
elif [[ "$NAMESPACE" == GFramework.Ecs.Arch* ]]; then
|
||||
echo "Ecs.Arch"
|
||||
elif [[ "$NAMESPACE" == GFramework.SourceGenerators.Common* ]]; then
|
||||
echo "SourceGenerators.Common"
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
434
.agents/skills/_shared/module-map.json
Normal file
434
.agents/skills/_shared/module-map.json
Normal file
@ -0,0 +1,434 @@
|
||||
{
|
||||
"version": 1,
|
||||
"description": "Canonical documentation refresh module map for GFramework skills.",
|
||||
"modules": {
|
||||
"Core": {
|
||||
"aliases": ["core", "core-runtime", "runtime-core", "core module"],
|
||||
"source_paths": ["GFramework.Core"],
|
||||
"project_file": "GFramework.Core/GFramework.Core.csproj",
|
||||
"test_projects": ["GFramework.Core.Tests/GFramework.Core.Tests.csproj"],
|
||||
"readme_paths": ["GFramework.Core/README.md"],
|
||||
"docs": {
|
||||
"landing": ["docs/zh-CN/core/index.md"],
|
||||
"topics": [
|
||||
"docs/zh-CN/core/architecture.md",
|
||||
"docs/zh-CN/core/context.md",
|
||||
"docs/zh-CN/core/lifecycle.md",
|
||||
"docs/zh-CN/core/events.md",
|
||||
"docs/zh-CN/core/property.md",
|
||||
"docs/zh-CN/core/logging.md",
|
||||
"docs/zh-CN/core/state-management.md",
|
||||
"docs/zh-CN/core/coroutine.md"
|
||||
],
|
||||
"fallback": [
|
||||
"docs/zh-CN/getting-started/quick-start.md",
|
||||
"docs/zh-CN/api-reference/index.md"
|
||||
]
|
||||
},
|
||||
"ai_libs": {
|
||||
"paths": [
|
||||
"ai-libs/CoreGrid/CoreGrid.csproj",
|
||||
"ai-libs/CoreGrid/global",
|
||||
"ai-libs/CoreGrid/docs"
|
||||
],
|
||||
"search_hints": [
|
||||
"rg -n \"Architecture|RegisterModel|RegisterSystem|BindableProperty|EventBus\" ai-libs/CoreGrid"
|
||||
]
|
||||
}
|
||||
},
|
||||
"Core.Abstractions": {
|
||||
"aliases": ["core.abstractions", "core-abstractions", "core abstractions"],
|
||||
"source_paths": ["GFramework.Core.Abstractions"],
|
||||
"project_file": "GFramework.Core.Abstractions/GFramework.Core.Abstractions.csproj",
|
||||
"test_projects": ["GFramework.Core.Tests/GFramework.Core.Tests.csproj"],
|
||||
"readme_paths": ["GFramework.Core.Abstractions/README.md"],
|
||||
"docs": {
|
||||
"landing": ["docs/zh-CN/abstractions/core-abstractions.md"],
|
||||
"topics": [
|
||||
"docs/zh-CN/core/index.md",
|
||||
"docs/zh-CN/core/architecture.md",
|
||||
"docs/zh-CN/core/context.md"
|
||||
],
|
||||
"fallback": ["docs/zh-CN/api-reference/index.md"]
|
||||
},
|
||||
"ai_libs": {
|
||||
"paths": ["ai-libs/CoreGrid/CoreGrid.csproj"],
|
||||
"search_hints": [
|
||||
"rg -n \"GFramework\\.Core\\.Abstractions|IArchitecture|IModel|ISystem\" ai-libs/CoreGrid"
|
||||
]
|
||||
}
|
||||
},
|
||||
"Core.SourceGenerators": {
|
||||
"aliases": [
|
||||
"core.sourcegenerators",
|
||||
"core-sourcegenerators",
|
||||
"core-source-generators",
|
||||
"core source generators"
|
||||
],
|
||||
"source_paths": ["GFramework.Core.SourceGenerators"],
|
||||
"project_file": "GFramework.Core.SourceGenerators/GFramework.Core.SourceGenerators.csproj",
|
||||
"test_projects": ["GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj"],
|
||||
"readme_paths": ["GFramework.Core.SourceGenerators/README.md"],
|
||||
"docs": {
|
||||
"landing": ["docs/zh-CN/source-generators/index.md"],
|
||||
"topics": [
|
||||
"docs/zh-CN/source-generators/context-aware-generator.md",
|
||||
"docs/zh-CN/source-generators/context-get-generator.md",
|
||||
"docs/zh-CN/source-generators/priority-generator.md",
|
||||
"docs/zh-CN/source-generators/logging-generator.md"
|
||||
],
|
||||
"fallback": ["docs/zh-CN/api-reference/index.md"]
|
||||
},
|
||||
"ai_libs": {
|
||||
"paths": ["ai-libs/CoreGrid/global", "ai-libs/CoreGrid/scripts"],
|
||||
"search_hints": [
|
||||
"rg -n \"ContextAware|ContextGet|Priority|GeneratedLogger|Log\" ai-libs/CoreGrid"
|
||||
]
|
||||
}
|
||||
},
|
||||
"Core.SourceGenerators.Abstractions": {
|
||||
"aliases": [
|
||||
"core.sourcegenerators.abstractions",
|
||||
"core-source-generators-abstractions",
|
||||
"core source generators abstractions"
|
||||
],
|
||||
"source_paths": ["GFramework.Core.SourceGenerators.Abstractions"],
|
||||
"project_file": "GFramework.Core.SourceGenerators.Abstractions/GFramework.Core.SourceGenerators.Abstractions.csproj",
|
||||
"test_projects": ["GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj"],
|
||||
"readme_paths": [],
|
||||
"docs": {
|
||||
"landing": ["docs/zh-CN/source-generators/index.md"],
|
||||
"topics": [
|
||||
"docs/zh-CN/source-generators/context-aware-generator.md",
|
||||
"docs/zh-CN/source-generators/context-get-generator.md",
|
||||
"docs/zh-CN/source-generators/priority-generator.md"
|
||||
],
|
||||
"fallback": ["docs/zh-CN/api-reference/index.md"]
|
||||
},
|
||||
"ai_libs": {
|
||||
"paths": ["ai-libs/CoreGrid/global"],
|
||||
"search_hints": [
|
||||
"rg -n \"GFramework\\.Core\\.SourceGenerators\\.Abstractions|\\[ContextAware\\]|\\[Priority\\]\" ai-libs/CoreGrid"
|
||||
]
|
||||
}
|
||||
},
|
||||
"Game": {
|
||||
"aliases": ["game", "game-runtime", "runtime-game", "game module"],
|
||||
"source_paths": ["GFramework.Game"],
|
||||
"project_file": "GFramework.Game/GFramework.Game.csproj",
|
||||
"test_projects": ["GFramework.Game.Tests/GFramework.Game.Tests.csproj"],
|
||||
"readme_paths": ["GFramework.Game/README.md"],
|
||||
"docs": {
|
||||
"landing": ["docs/zh-CN/game/index.md"],
|
||||
"topics": [
|
||||
"docs/zh-CN/game/scene.md",
|
||||
"docs/zh-CN/game/ui.md",
|
||||
"docs/zh-CN/game/data.md",
|
||||
"docs/zh-CN/game/storage.md",
|
||||
"docs/zh-CN/game/serialization.md",
|
||||
"docs/zh-CN/game/setting.md",
|
||||
"docs/zh-CN/game/config-system.md"
|
||||
],
|
||||
"fallback": [
|
||||
"docs/zh-CN/getting-started/quick-start.md",
|
||||
"docs/zh-CN/api-reference/index.md"
|
||||
]
|
||||
},
|
||||
"ai_libs": {
|
||||
"paths": [
|
||||
"ai-libs/CoreGrid/global",
|
||||
"ai-libs/CoreGrid/scenes",
|
||||
"ai-libs/CoreGrid/addons"
|
||||
],
|
||||
"search_hints": [
|
||||
"rg -n \"SceneRouter|UiRouter|Setting|Storage|Serialization|Config\" ai-libs/CoreGrid"
|
||||
]
|
||||
}
|
||||
},
|
||||
"Game.Abstractions": {
|
||||
"aliases": ["game.abstractions", "game-abstractions", "game abstractions"],
|
||||
"source_paths": ["GFramework.Game.Abstractions"],
|
||||
"project_file": "GFramework.Game.Abstractions/GFramework.Game.Abstractions.csproj",
|
||||
"test_projects": ["GFramework.Game.Tests/GFramework.Game.Tests.csproj"],
|
||||
"readme_paths": ["GFramework.Game.Abstractions/README.md"],
|
||||
"docs": {
|
||||
"landing": ["docs/zh-CN/abstractions/game-abstractions.md"],
|
||||
"topics": [
|
||||
"docs/zh-CN/game/index.md",
|
||||
"docs/zh-CN/game/scene.md",
|
||||
"docs/zh-CN/game/ui.md"
|
||||
],
|
||||
"fallback": ["docs/zh-CN/api-reference/index.md"]
|
||||
},
|
||||
"ai_libs": {
|
||||
"paths": ["ai-libs/CoreGrid/global", "ai-libs/CoreGrid/scenes"],
|
||||
"search_hints": [
|
||||
"rg -n \"ISceneFactory|ISceneRoot|UiInteractionProfile|IUiRoot\" ai-libs/CoreGrid"
|
||||
]
|
||||
}
|
||||
},
|
||||
"Game.SourceGenerators": {
|
||||
"aliases": [
|
||||
"game.sourcegenerators",
|
||||
"game-source-generators",
|
||||
"game source generators"
|
||||
],
|
||||
"source_paths": ["GFramework.Game.SourceGenerators"],
|
||||
"project_file": "GFramework.Game.SourceGenerators/GFramework.Game.SourceGenerators.csproj",
|
||||
"test_projects": ["GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj"],
|
||||
"readme_paths": ["GFramework.Game.SourceGenerators/README.md"],
|
||||
"docs": {
|
||||
"landing": ["docs/zh-CN/source-generators/index.md"],
|
||||
"topics": [
|
||||
"docs/zh-CN/source-generators/auto-scene-generator.md",
|
||||
"docs/zh-CN/source-generators/auto-ui-page-generator.md"
|
||||
],
|
||||
"fallback": ["docs/zh-CN/game/index.md"]
|
||||
},
|
||||
"ai_libs": {
|
||||
"paths": ["ai-libs/CoreGrid/scenes", "ai-libs/CoreGrid/addons"],
|
||||
"search_hints": [
|
||||
"rg -n \"AutoScene|AutoUiPage|SceneRouter|UiRouter\" ai-libs/CoreGrid"
|
||||
]
|
||||
}
|
||||
},
|
||||
"Godot": {
|
||||
"aliases": ["godot", "godot-runtime", "runtime-godot", "godot module"],
|
||||
"source_paths": ["GFramework.Godot"],
|
||||
"project_file": "GFramework.Godot/GFramework.Godot.csproj",
|
||||
"test_projects": ["GFramework.Godot.Tests/GFramework.Godot.Tests.csproj"],
|
||||
"readme_paths": ["GFramework.Godot/README.md"],
|
||||
"docs": {
|
||||
"landing": ["docs/zh-CN/godot/index.md"],
|
||||
"topics": [
|
||||
"docs/zh-CN/godot/architecture.md",
|
||||
"docs/zh-CN/godot/scene.md",
|
||||
"docs/zh-CN/godot/ui.md",
|
||||
"docs/zh-CN/godot/storage.md",
|
||||
"docs/zh-CN/godot/setting.md",
|
||||
"docs/zh-CN/godot/signal.md"
|
||||
],
|
||||
"fallback": [
|
||||
"docs/zh-CN/source-generators/index.md",
|
||||
"docs/zh-CN/api-reference/index.md"
|
||||
]
|
||||
},
|
||||
"ai_libs": {
|
||||
"paths": [
|
||||
"ai-libs/CoreGrid/project.godot",
|
||||
"ai-libs/CoreGrid/global",
|
||||
"ai-libs/CoreGrid/scenes"
|
||||
],
|
||||
"search_hints": [
|
||||
"rg -n \"AutoLoad|InputActions|Node|Signal|PackedScene|Godot\" ai-libs/CoreGrid"
|
||||
]
|
||||
}
|
||||
},
|
||||
"Godot.SourceGenerators": {
|
||||
"aliases": [
|
||||
"godot.sourcegenerators",
|
||||
"godot-source-generators",
|
||||
"godot source generators",
|
||||
"godot generators"
|
||||
],
|
||||
"source_paths": ["GFramework.Godot.SourceGenerators"],
|
||||
"project_file": "GFramework.Godot.SourceGenerators/GFramework.Godot.SourceGenerators.csproj",
|
||||
"test_projects": ["GFramework.Godot.SourceGenerators.Tests/GFramework.Godot.SourceGenerators.Tests.csproj"],
|
||||
"readme_paths": ["GFramework.Godot.SourceGenerators/README.md"],
|
||||
"docs": {
|
||||
"landing": ["docs/zh-CN/source-generators/index.md"],
|
||||
"topics": [
|
||||
"docs/zh-CN/source-generators/godot-project-generator.md",
|
||||
"docs/zh-CN/source-generators/get-node-generator.md",
|
||||
"docs/zh-CN/source-generators/bind-node-signal-generator.md",
|
||||
"docs/zh-CN/source-generators/auto-register-exported-collections-generator.md"
|
||||
],
|
||||
"fallback": ["docs/zh-CN/godot/index.md"]
|
||||
},
|
||||
"ai_libs": {
|
||||
"paths": [
|
||||
"ai-libs/CoreGrid/project.godot",
|
||||
"ai-libs/CoreGrid/global",
|
||||
"ai-libs/CoreGrid/scenes"
|
||||
],
|
||||
"search_hints": [
|
||||
"rg -n \"GetNode|BindNodeSignal|AutoRegisterExported|project\\.godot|AutoLoad\" ai-libs/CoreGrid"
|
||||
]
|
||||
}
|
||||
},
|
||||
"Godot.SourceGenerators.Abstractions": {
|
||||
"aliases": [
|
||||
"godot.sourcegenerators.abstractions",
|
||||
"godot-source-generators-abstractions",
|
||||
"godot source generators abstractions"
|
||||
],
|
||||
"source_paths": ["GFramework.Godot.SourceGenerators.Abstractions"],
|
||||
"project_file": "GFramework.Godot.SourceGenerators.Abstractions/GFramework.Godot.SourceGenerators.Abstractions.csproj",
|
||||
"test_projects": ["GFramework.Godot.SourceGenerators.Tests/GFramework.Godot.SourceGenerators.Tests.csproj"],
|
||||
"readme_paths": [],
|
||||
"docs": {
|
||||
"landing": ["docs/zh-CN/source-generators/index.md"],
|
||||
"topics": [
|
||||
"docs/zh-CN/source-generators/godot-project-generator.md",
|
||||
"docs/zh-CN/source-generators/get-node-generator.md",
|
||||
"docs/zh-CN/source-generators/bind-node-signal-generator.md"
|
||||
],
|
||||
"fallback": ["docs/zh-CN/godot/index.md"]
|
||||
},
|
||||
"ai_libs": {
|
||||
"paths": ["ai-libs/CoreGrid/project.godot", "ai-libs/CoreGrid/global"],
|
||||
"search_hints": [
|
||||
"rg -n \"GFramework\\.Godot\\.SourceGenerators\\.Abstractions|GetNode|BindNodeSignal\" ai-libs/CoreGrid"
|
||||
]
|
||||
}
|
||||
},
|
||||
"Cqrs": {
|
||||
"aliases": ["cqrs", "mediator", "cqrs module"],
|
||||
"source_paths": ["GFramework.Cqrs"],
|
||||
"project_file": "GFramework.Cqrs/GFramework.Cqrs.csproj",
|
||||
"test_projects": ["GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj"],
|
||||
"readme_paths": ["GFramework.Cqrs/README.md"],
|
||||
"docs": {
|
||||
"landing": ["docs/zh-CN/core/cqrs.md"],
|
||||
"topics": [
|
||||
"docs/zh-CN/core/command.md",
|
||||
"docs/zh-CN/core/query.md",
|
||||
"docs/zh-CN/core/cqrs.md"
|
||||
],
|
||||
"fallback": [
|
||||
"docs/zh-CN/core/index.md",
|
||||
"docs/zh-CN/api-reference/index.md"
|
||||
]
|
||||
},
|
||||
"ai_libs": {
|
||||
"paths": ["ai-libs/CoreGrid/global", "ai-libs/CoreGrid/scripts"],
|
||||
"search_hints": [
|
||||
"rg -n \"CommandHandler|QueryHandler|RegisterCqrs|PipelineBehavior\" ai-libs/CoreGrid"
|
||||
]
|
||||
}
|
||||
},
|
||||
"Cqrs.Abstractions": {
|
||||
"aliases": ["cqrs.abstractions", "cqrs-abstractions", "cqrs abstractions"],
|
||||
"source_paths": ["GFramework.Cqrs.Abstractions"],
|
||||
"project_file": "GFramework.Cqrs.Abstractions/GFramework.Cqrs.Abstractions.csproj",
|
||||
"test_projects": ["GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj"],
|
||||
"readme_paths": ["GFramework.Cqrs.Abstractions/README.md"],
|
||||
"docs": {
|
||||
"landing": ["docs/zh-CN/core/cqrs.md"],
|
||||
"topics": [
|
||||
"docs/zh-CN/core/command.md",
|
||||
"docs/zh-CN/core/query.md"
|
||||
],
|
||||
"fallback": ["docs/zh-CN/api-reference/index.md"]
|
||||
},
|
||||
"ai_libs": {
|
||||
"paths": ["ai-libs/CoreGrid/global"],
|
||||
"search_hints": [
|
||||
"rg -n \"GFramework\\.Cqrs\\.Abstractions|ICommand|IQuery|IRequest\" ai-libs/CoreGrid"
|
||||
]
|
||||
}
|
||||
},
|
||||
"Cqrs.SourceGenerators": {
|
||||
"aliases": [
|
||||
"cqrs.sourcegenerators",
|
||||
"cqrs-source-generators",
|
||||
"cqrs source generators"
|
||||
],
|
||||
"source_paths": ["GFramework.Cqrs.SourceGenerators"],
|
||||
"project_file": "GFramework.Cqrs.SourceGenerators/GFramework.Cqrs.SourceGenerators.csproj",
|
||||
"test_projects": ["GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj"],
|
||||
"readme_paths": ["GFramework.Cqrs.SourceGenerators/README.md"],
|
||||
"docs": {
|
||||
"landing": ["docs/zh-CN/source-generators/index.md"],
|
||||
"topics": [],
|
||||
"fallback": [
|
||||
"docs/zh-CN/core/cqrs.md",
|
||||
"docs/zh-CN/api-reference/index.md"
|
||||
]
|
||||
},
|
||||
"ai_libs": {
|
||||
"paths": ["ai-libs/CoreGrid/global"],
|
||||
"search_hints": [
|
||||
"rg -n \"GFramework\\.Cqrs\\.SourceGenerators|RequestHandler|PipelineBehavior\" ai-libs/CoreGrid"
|
||||
]
|
||||
}
|
||||
},
|
||||
"Ecs.Arch": {
|
||||
"aliases": ["ecs.arch", "ecs-arch", "ecs arch", "ecs"],
|
||||
"source_paths": ["GFramework.Ecs.Arch"],
|
||||
"project_file": "GFramework.Ecs.Arch/GFramework.Ecs.Arch.csproj",
|
||||
"test_projects": ["GFramework.Ecs.Arch.Tests/GFramework.Ecs.Arch.Tests.csproj"],
|
||||
"readme_paths": ["GFramework.Ecs.Arch/README.md"],
|
||||
"docs": {
|
||||
"landing": ["docs/zh-CN/ecs/index.md"],
|
||||
"topics": ["docs/zh-CN/ecs/arch.md"],
|
||||
"fallback": [
|
||||
"docs/zh-CN/core/index.md",
|
||||
"docs/zh-CN/api-reference/index.md"
|
||||
]
|
||||
},
|
||||
"ai_libs": {
|
||||
"paths": ["ai-libs/CoreGrid/scripts", "ai-libs/CoreGrid/global"],
|
||||
"search_hints": [
|
||||
"rg -n \"Arch\\.Core|World|SystemGroup|QueryDescription\" ai-libs/CoreGrid"
|
||||
]
|
||||
}
|
||||
},
|
||||
"Ecs.Arch.Abstractions": {
|
||||
"aliases": [
|
||||
"ecs.arch.abstractions",
|
||||
"ecs-arch-abstractions",
|
||||
"ecs arch abstractions"
|
||||
],
|
||||
"source_paths": ["GFramework.Ecs.Arch.Abstractions"],
|
||||
"project_file": "GFramework.Ecs.Arch.Abstractions/GFramework.Ecs.Arch.Abstractions.csproj",
|
||||
"test_projects": ["GFramework.Ecs.Arch.Tests/GFramework.Ecs.Arch.Tests.csproj"],
|
||||
"readme_paths": [],
|
||||
"docs": {
|
||||
"landing": ["docs/zh-CN/ecs/index.md"],
|
||||
"topics": ["docs/zh-CN/ecs/arch.md"],
|
||||
"fallback": ["docs/zh-CN/api-reference/index.md"]
|
||||
},
|
||||
"ai_libs": {
|
||||
"paths": ["ai-libs/CoreGrid/scripts"],
|
||||
"search_hints": [
|
||||
"rg -n \"GFramework\\.Ecs\\.Arch\\.Abstractions|IArchSystem|IArchModel\" ai-libs/CoreGrid"
|
||||
]
|
||||
}
|
||||
},
|
||||
"SourceGenerators.Common": {
|
||||
"aliases": [
|
||||
"sourcegenerators.common",
|
||||
"source-generators-common",
|
||||
"source generators common"
|
||||
],
|
||||
"source_paths": ["GFramework.SourceGenerators.Common"],
|
||||
"project_file": "GFramework.SourceGenerators.Common/GFramework.SourceGenerators.Common.csproj",
|
||||
"test_projects": ["GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj"],
|
||||
"readme_paths": [],
|
||||
"docs": {
|
||||
"landing": ["docs/zh-CN/source-generators/index.md"],
|
||||
"topics": [],
|
||||
"fallback": ["docs/zh-CN/api-reference/index.md"]
|
||||
},
|
||||
"ai_libs": {
|
||||
"paths": [],
|
||||
"search_hints": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"docs_section_aliases": {
|
||||
"core": ["Core"],
|
||||
"abstractions": ["Core.Abstractions", "Game.Abstractions", "Ecs.Arch.Abstractions"],
|
||||
"game": ["Game"],
|
||||
"godot": ["Godot"],
|
||||
"cqrs": ["Cqrs"],
|
||||
"ecs": ["Ecs.Arch"],
|
||||
"source-generators": [
|
||||
"Core.SourceGenerators",
|
||||
"Game.SourceGenerators",
|
||||
"Cqrs.SourceGenerators",
|
||||
"Godot.SourceGenerators"
|
||||
]
|
||||
}
|
||||
}
|
||||
205
.agents/skills/gframework-doc-refresh/SKILL.md
Normal file
205
.agents/skills/gframework-doc-refresh/SKILL.md
Normal file
@ -0,0 +1,205 @@
|
||||
---
|
||||
name: gframework-doc-refresh
|
||||
description: "Refresh or reassess GFramework documentation for a source module such as Core, Game, Godot, Cqrs, Ecs.Arch, or their generator/abstraction packages. Use this when the user asks to update module docs, re-evaluate landing pages, fix outdated topic pages, refresh API reference coverage, verify adoption paths against source/tests/README, or compare current docs with ai-libs consumer wiring. Recommended command: /gframework-doc-refresh <module>."
|
||||
---
|
||||
|
||||
# Purpose
|
||||
|
||||
Use this skill to refresh GFramework documentation from source-first evidence.
|
||||
|
||||
The public entry is module-driven, not doc-type-driven:
|
||||
|
||||
- Input: a source module or a resolvable docs section alias
|
||||
- Output: the minimal documentation update set needed for that module
|
||||
- Evidence: code, tests, README, current docs, then `ai-libs/`
|
||||
|
||||
Do not start by deciding “this is an API doc task” or “this is a tutorial task”.
|
||||
Decide that only after the module scan.
|
||||
|
||||
# Triggers
|
||||
|
||||
Use this skill when the user asks things like:
|
||||
|
||||
- `refresh docs for Core`
|
||||
- `update Game module docs`
|
||||
- `根据 Godot 模块源码刷新文档`
|
||||
- `重新评估 Cqrs 模块文档并更新`
|
||||
- `核对 Godot.SourceGenerators 的文档状态`
|
||||
- `看看 source-generators 栏目哪些页面已经失真`
|
||||
|
||||
Recommended command form:
|
||||
|
||||
```bash
|
||||
/gframework-doc-refresh <module>
|
||||
```
|
||||
|
||||
# Supported Modules
|
||||
|
||||
Canonical module names:
|
||||
|
||||
- `Core`
|
||||
- `Core.Abstractions`
|
||||
- `Core.SourceGenerators`
|
||||
- `Core.SourceGenerators.Abstractions`
|
||||
- `Game`
|
||||
- `Game.Abstractions`
|
||||
- `Game.SourceGenerators`
|
||||
- `Godot`
|
||||
- `Godot.SourceGenerators`
|
||||
- `Godot.SourceGenerators.Abstractions`
|
||||
- `Cqrs`
|
||||
- `Cqrs.Abstractions`
|
||||
- `Cqrs.SourceGenerators`
|
||||
- `Ecs.Arch`
|
||||
- `Ecs.Arch.Abstractions`
|
||||
- `SourceGenerators.Common`
|
||||
|
||||
The canonical mapping lives in `.agents/skills/_shared/module-map.json`.
|
||||
|
||||
If the user supplies a docs section name:
|
||||
|
||||
- resolve it back to a source module first
|
||||
- if it maps to multiple modules, stop at normalization guidance and do not draft docs yet
|
||||
|
||||
# Workflow
|
||||
|
||||
## 1. Normalize the input
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
python3 .agents/skills/gframework-doc-refresh/scripts/scan_module_evidence.py <module>
|
||||
```
|
||||
|
||||
The script normalizes aliases, reports ambiguity, and prints the module's evidence surface.
|
||||
|
||||
If the result is ambiguous:
|
||||
|
||||
- return the candidate modules
|
||||
- ask the user to pick the intended source module
|
||||
- do not continue into document generation
|
||||
|
||||
## 2. Scan the evidence surface
|
||||
|
||||
For the resolved module, inspect:
|
||||
|
||||
- source directories
|
||||
- `*.csproj`
|
||||
- relevant test projects
|
||||
- sibling `README.md`
|
||||
- mapped `docs/zh-CN` landing pages and topic pages
|
||||
- optional `ai-libs/` consumer evidence when needed
|
||||
|
||||
Always confirm the actual files in the repository.
|
||||
Do not assume the mapping is enough on its own.
|
||||
|
||||
## 3. Decide whether `ai-libs/` is needed
|
||||
|
||||
Use `ai-libs/` when:
|
||||
|
||||
- adoption path is unclear from source and README alone
|
||||
- extension points need a real consumer wiring example
|
||||
- current docs have concepts but lack an end-to-end integration path
|
||||
|
||||
Do not rely on `ai-libs/` for:
|
||||
|
||||
- public API contract definitions
|
||||
- generator diagnostics or semantic guarantees
|
||||
- claims about what the current version officially supports
|
||||
|
||||
If `ai-libs/` conflicts with current source or tests, keep source/tests as the contract and document the migration boundary.
|
||||
|
||||
## 4. Judge the documentation state
|
||||
|
||||
Classify the module into one or more of these states:
|
||||
|
||||
- missing landing page
|
||||
- stale landing page
|
||||
- stale topic page
|
||||
- missing or stale API reference coverage
|
||||
- stale tutorial/example
|
||||
- validation-only
|
||||
|
||||
Base this on evidence, not on the previous docs shape.
|
||||
|
||||
## 5. Choose the output set
|
||||
|
||||
Always prioritize:
|
||||
|
||||
1. README / landing page / adoption path
|
||||
2. topic pages
|
||||
3. API reference
|
||||
4. tutorials
|
||||
|
||||
If the module only needs validation or relinking, do not generate extra pages.
|
||||
|
||||
## 6. Draft or update docs
|
||||
|
||||
Load only the template that matches the output you selected:
|
||||
|
||||
- `templates/module-landing.md`
|
||||
- `templates/topic-refresh.md`
|
||||
- `templates/api-reference.md`
|
||||
- `templates/tutorial.md`
|
||||
|
||||
Keep examples minimal, current, and traceable to source or tests.
|
||||
|
||||
## 7. Validate
|
||||
|
||||
Run the internal validators as needed:
|
||||
|
||||
```bash
|
||||
bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh <file-or-directory>
|
||||
```
|
||||
|
||||
For site-level confirmation after doc edits:
|
||||
|
||||
```bash
|
||||
cd docs && bun run build
|
||||
```
|
||||
|
||||
# Evidence Order
|
||||
|
||||
Use this exact priority:
|
||||
|
||||
1. source code, XML docs, `*.csproj`
|
||||
2. tests and snapshots
|
||||
3. module `README.md`
|
||||
4. current `docs/zh-CN` pages
|
||||
5. verified `ai-libs/` consumers
|
||||
6. archived docs only as fallback context
|
||||
|
||||
# Output Rules
|
||||
|
||||
- Prefer correcting the adoption path over expanding page count.
|
||||
- Do not copy wording from outdated docs just to keep page volume.
|
||||
- Escape generics outside code blocks.
|
||||
- Keep internal links real and current.
|
||||
- Mark code blocks with explicit languages.
|
||||
- Use the smallest example that demonstrates the current contract.
|
||||
- Consumer examples may align with `ai-libs/`, but must not exceed the current module contract.
|
||||
|
||||
# Validation
|
||||
|
||||
Use the shared standards in `.agents/skills/_shared/DOCUMENTATION_STANDARDS.md`.
|
||||
|
||||
When this skill changes public docs, prefer:
|
||||
|
||||
1. focused validator on touched pages
|
||||
2. `cd docs && bun run build`
|
||||
|
||||
When this skill changes the skill system itself:
|
||||
|
||||
1. validate `SKILL.md` frontmatter exists
|
||||
2. run the module scan script for representative modules
|
||||
3. confirm obsolete `vitepress-*` public entries are gone
|
||||
|
||||
# References
|
||||
|
||||
Read these only when needed:
|
||||
|
||||
- `.agents/skills/_shared/DOCUMENTATION_STANDARDS.md`
|
||||
- `.agents/skills/_shared/module-map.json`
|
||||
- `references/module-selection.md`
|
||||
- `references/evidence-and-ai-libs.md`
|
||||
- `references/output-strategy.md`
|
||||
4
.agents/skills/gframework-doc-refresh/agents/openai.yaml
Normal file
4
.agents/skills/gframework-doc-refresh/agents/openai.yaml
Normal file
@ -0,0 +1,4 @@
|
||||
interface:
|
||||
display_name: "GFramework Doc Refresh"
|
||||
short_description: "Refresh module docs from code-first evidence"
|
||||
default_prompt: "Use $gframework-doc-refresh to refresh a GFramework module's docs from source, tests, README, current docs, and ai-libs evidence."
|
||||
@ -0,0 +1,35 @@
|
||||
# Evidence And `ai-libs`
|
||||
|
||||
The evidence order is fixed:
|
||||
|
||||
1. source code, XML docs, `*.csproj`
|
||||
2. tests and snapshots
|
||||
3. module README
|
||||
4. current `docs/zh-CN`
|
||||
5. `ai-libs/`
|
||||
6. archived docs
|
||||
|
||||
## When To Use `ai-libs`
|
||||
|
||||
Use `ai-libs/` to answer questions like:
|
||||
|
||||
- How is this extension point wired in a real project?
|
||||
- What does the minimal project layout look like?
|
||||
- Which project-side files need to exist for this module to work end to end?
|
||||
|
||||
## When Not To Use `ai-libs`
|
||||
|
||||
Do not use `ai-libs/` as the primary source for:
|
||||
|
||||
- public API semantics
|
||||
- exact generator output guarantees
|
||||
- supported package matrix
|
||||
- diagnostics behavior
|
||||
|
||||
## Conflict Rule
|
||||
|
||||
If `ai-libs/` drifts from the current repo:
|
||||
|
||||
- trust source and tests
|
||||
- mention the drift as a compatibility or migration note
|
||||
- do not document old consumer behavior as if it were still the contract
|
||||
@ -0,0 +1,29 @@
|
||||
# Module Selection
|
||||
|
||||
Use `.agents/skills/_shared/module-map.json` as the canonical source for:
|
||||
|
||||
- supported modules
|
||||
- aliases
|
||||
- source paths
|
||||
- test projects
|
||||
- README paths
|
||||
- docs landing/topic/fallback pages
|
||||
- `ai-libs/` reference roots
|
||||
|
||||
Selection rules:
|
||||
|
||||
1. Prefer explicit canonical module names.
|
||||
2. Resolve docs section aliases back to source modules before scanning docs.
|
||||
3. If an alias maps to multiple modules, stop and return the candidate list.
|
||||
4. If a module has no dedicated docs section, fall back to the nearest existing landing page or API index instead of inventing a fake section.
|
||||
|
||||
Representative ambiguous inputs:
|
||||
|
||||
- `source-generators` -> likely one of `Core.SourceGenerators`, `Game.SourceGenerators`, `Cqrs.SourceGenerators`, `Godot.SourceGenerators`
|
||||
- `abstractions` -> likely one of `Core.Abstractions`, `Game.Abstractions`, `Ecs.Arch.Abstractions`
|
||||
|
||||
Representative resolvable aliases:
|
||||
|
||||
- `core-abstractions` -> `Core.Abstractions`
|
||||
- `godot generators` -> `Godot.SourceGenerators`
|
||||
- `ecs` -> `Ecs.Arch`
|
||||
@ -0,0 +1,38 @@
|
||||
# Output Strategy
|
||||
|
||||
The module scan determines the document type.
|
||||
|
||||
Use this priority:
|
||||
|
||||
1. fix README / landing page / adoption path
|
||||
2. fix stale topic pages
|
||||
3. add or refresh API reference coverage
|
||||
4. add or refresh tutorials
|
||||
|
||||
## Landing Page Checklist
|
||||
|
||||
- module purpose
|
||||
- package relationship
|
||||
- minimum adoption path
|
||||
- real entry points
|
||||
- next-reading links
|
||||
|
||||
## Topic Page Checklist
|
||||
|
||||
- current role
|
||||
- public entry points
|
||||
- minimum example
|
||||
- compatibility or migration boundary
|
||||
- related pages
|
||||
|
||||
## API Reference Checklist
|
||||
|
||||
- only for types or members that materially help consumers
|
||||
- grounded in XML docs and source
|
||||
- no speculative examples
|
||||
|
||||
## Tutorial Checklist
|
||||
|
||||
- only after the landing path is accurate
|
||||
- keep the scenario traceable to source/tests or `ai-libs/`
|
||||
- explain why each step exists, not just the code shape
|
||||
@ -0,0 +1,226 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Normalize a GFramework docs module input and report its evidence surface."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||
REPO_ROOT = SCRIPT_DIR.parents[3]
|
||||
MODULE_MAP_PATH = REPO_ROOT / ".agents/skills/_shared/module-map.json"
|
||||
|
||||
|
||||
def load_module_map() -> dict[str, Any]:
|
||||
return json.loads(MODULE_MAP_PATH.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def normalize_key(value: str) -> str:
|
||||
return value.strip().lower().replace("_", "-").replace(" ", "-")
|
||||
|
||||
|
||||
def resolve_module(raw_input: str, module_map: dict[str, Any]) -> dict[str, Any]:
|
||||
modules = module_map["modules"]
|
||||
docs_section_aliases = module_map.get("docs_section_aliases", {})
|
||||
normalized = normalize_key(raw_input)
|
||||
|
||||
for canonical_name in modules:
|
||||
if normalize_key(canonical_name) == normalized:
|
||||
return {"status": "ok", "module": canonical_name, "reason": "canonical"}
|
||||
|
||||
for canonical_name, config in modules.items():
|
||||
aliases = config.get("aliases", [])
|
||||
if normalized in {normalize_key(alias) for alias in aliases}:
|
||||
return {"status": "ok", "module": canonical_name, "reason": "alias"}
|
||||
|
||||
if normalized in docs_section_aliases:
|
||||
candidates = docs_section_aliases[normalized]
|
||||
if len(candidates) == 1:
|
||||
return {"status": "ok", "module": candidates[0], "reason": "docs_section"}
|
||||
return {
|
||||
"status": "ambiguous",
|
||||
"reason": "docs_section",
|
||||
"input": raw_input,
|
||||
"candidates": candidates,
|
||||
}
|
||||
|
||||
fuzzy = [
|
||||
canonical_name
|
||||
for canonical_name in modules
|
||||
if normalized in normalize_key(canonical_name) or normalize_key(canonical_name) in normalized
|
||||
]
|
||||
if fuzzy:
|
||||
return {"status": "unknown", "reason": "closest_match", "input": raw_input, "candidates": fuzzy}
|
||||
|
||||
return {"status": "unknown", "reason": "no_match", "input": raw_input, "candidates": []}
|
||||
|
||||
|
||||
def collect_path_state(paths: list[str]) -> list[dict[str, Any]]:
|
||||
states: list[dict[str, Any]] = []
|
||||
for relative_path in paths:
|
||||
absolute_path = REPO_ROOT / relative_path
|
||||
states.append(
|
||||
{
|
||||
"path": relative_path,
|
||||
"exists": absolute_path.exists(),
|
||||
"kind": "dir" if absolute_path.is_dir() else "file",
|
||||
}
|
||||
)
|
||||
return states
|
||||
|
||||
|
||||
def assess_docs(module_config: dict[str, Any]) -> list[str]:
|
||||
docs_config = module_config["docs"]
|
||||
landing = collect_path_state(docs_config.get("landing", []))
|
||||
topics = collect_path_state(docs_config.get("topics", []))
|
||||
assessment: list[str] = []
|
||||
|
||||
if landing and not any(item["exists"] for item in landing):
|
||||
assessment.append("landing_missing")
|
||||
elif landing:
|
||||
assessment.append("landing_present")
|
||||
|
||||
if not topics:
|
||||
assessment.append("topic_docs_not_mapped")
|
||||
else:
|
||||
existing_topics = sum(1 for item in topics if item["exists"])
|
||||
if existing_topics == 0:
|
||||
assessment.append("topic_docs_missing")
|
||||
elif existing_topics < len(topics):
|
||||
assessment.append("topic_docs_partial")
|
||||
else:
|
||||
assessment.append("topic_docs_present")
|
||||
|
||||
return assessment
|
||||
|
||||
|
||||
def build_report(module_name: str, module_config: dict[str, Any]) -> dict[str, Any]:
|
||||
source_paths = collect_path_state(module_config.get("source_paths", []))
|
||||
test_projects = collect_path_state(module_config.get("test_projects", []))
|
||||
readmes = collect_path_state(module_config.get("readme_paths", []))
|
||||
docs_config = module_config["docs"]
|
||||
ai_libs = module_config.get("ai_libs", {})
|
||||
|
||||
report = {
|
||||
"status": "ok",
|
||||
"module": module_name,
|
||||
"source_paths": source_paths,
|
||||
"project_file": collect_path_state([module_config["project_file"]])[0],
|
||||
"test_projects": test_projects,
|
||||
"readme_paths": readmes,
|
||||
"docs": {
|
||||
"landing": collect_path_state(docs_config.get("landing", [])),
|
||||
"topics": collect_path_state(docs_config.get("topics", [])),
|
||||
"fallback": collect_path_state(docs_config.get("fallback", []))
|
||||
},
|
||||
"ai_libs": {
|
||||
"paths": collect_path_state(ai_libs.get("paths", [])),
|
||||
"search_hints": ai_libs.get("search_hints", []),
|
||||
},
|
||||
"assessment": assess_docs(module_config),
|
||||
}
|
||||
|
||||
if readmes and not any(item["exists"] for item in readmes):
|
||||
report["assessment"].append("readme_missing")
|
||||
|
||||
if test_projects and not any(item["exists"] for item in test_projects):
|
||||
report["assessment"].append("tests_missing")
|
||||
|
||||
if not ai_libs.get("paths"):
|
||||
report["assessment"].append("ai_libs_optional")
|
||||
|
||||
if not docs_config.get("topics"):
|
||||
report["assessment"].append("fallback_docs_only")
|
||||
|
||||
return report
|
||||
|
||||
|
||||
def print_text_report(report: dict[str, Any]) -> None:
|
||||
if report["status"] != "ok":
|
||||
print(json.dumps(report, ensure_ascii=False, indent=2))
|
||||
return
|
||||
|
||||
print(f"module: {report['module']}")
|
||||
print("assessment:")
|
||||
for item in report["assessment"]:
|
||||
print(f" - {item}")
|
||||
|
||||
print("source:")
|
||||
for item in report["source_paths"]:
|
||||
print(f" - {'OK' if item['exists'] else 'MISS'} {item['path']}")
|
||||
|
||||
project_file = report["project_file"]
|
||||
print(f"project: {'OK' if project_file['exists'] else 'MISS'} {project_file['path']}")
|
||||
|
||||
print("tests:")
|
||||
for item in report["test_projects"]:
|
||||
print(f" - {'OK' if item['exists'] else 'MISS'} {item['path']}")
|
||||
|
||||
print("readme:")
|
||||
if report["readme_paths"]:
|
||||
for item in report["readme_paths"]:
|
||||
print(f" - {'OK' if item['exists'] else 'MISS'} {item['path']}")
|
||||
else:
|
||||
print(" - none mapped")
|
||||
|
||||
print("docs landing:")
|
||||
for item in report["docs"]["landing"]:
|
||||
print(f" - {'OK' if item['exists'] else 'MISS'} {item['path']}")
|
||||
|
||||
print("docs topics:")
|
||||
if report["docs"]["topics"]:
|
||||
for item in report["docs"]["topics"]:
|
||||
print(f" - {'OK' if item['exists'] else 'MISS'} {item['path']}")
|
||||
else:
|
||||
print(" - none mapped")
|
||||
|
||||
print("docs fallback:")
|
||||
for item in report["docs"]["fallback"]:
|
||||
print(f" - {'OK' if item['exists'] else 'MISS'} {item['path']}")
|
||||
|
||||
print("ai-libs:")
|
||||
if report["ai_libs"]["paths"]:
|
||||
for item in report["ai_libs"]["paths"]:
|
||||
print(f" - {'OK' if item['exists'] else 'MISS'} {item['path']}")
|
||||
else:
|
||||
print(" - none mapped")
|
||||
|
||||
if report["ai_libs"]["search_hints"]:
|
||||
print("ai-libs search hints:")
|
||||
for item in report["ai_libs"]["search_hints"]:
|
||||
print(f" - {item}")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("module", help="Canonical module name, alias, or docs section name.")
|
||||
parser.add_argument("--json", action="store_true", help="Emit JSON instead of text.")
|
||||
args = parser.parse_args()
|
||||
|
||||
module_map = load_module_map()
|
||||
resolution = resolve_module(args.module, module_map)
|
||||
|
||||
if resolution["status"] != "ok":
|
||||
if args.json:
|
||||
print(json.dumps(resolution, ensure_ascii=False, indent=2))
|
||||
else:
|
||||
print(json.dumps(resolution, ensure_ascii=False, indent=2))
|
||||
return 1
|
||||
|
||||
report = build_report(resolution["module"], module_map["modules"][resolution["module"]])
|
||||
report["resolution"] = resolution
|
||||
|
||||
if args.json:
|
||||
print(json.dumps(report, ensure_ascii=False, indent=2))
|
||||
else:
|
||||
print_text_report(report)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@ -0,0 +1,67 @@
|
||||
#!/bin/bash
|
||||
# 运行统一文档校验脚本集合。
|
||||
|
||||
set -e
|
||||
|
||||
TARGET="$1"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
if [ -z "$TARGET" ]; then
|
||||
echo "用法: $0 <文件或目录路径>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -e "$TARGET" ]; then
|
||||
echo "错误: 路径不存在: $TARGET"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -f "$TARGET" ]; then
|
||||
FILES=("$TARGET")
|
||||
else
|
||||
mapfile -t FILES < <(find "$TARGET" -type f -name "*.md" | sort)
|
||||
fi
|
||||
|
||||
if [ ${#FILES[@]} -eq 0 ]; then
|
||||
echo "未找到 Markdown 文件"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
TOTAL_ERRORS=0
|
||||
FAILED_FILES=0
|
||||
|
||||
for FILE in "${FILES[@]}"; do
|
||||
FILE_ERRORS=0
|
||||
|
||||
echo "验证: $FILE"
|
||||
|
||||
if ! bash "$SCRIPT_DIR/validate-frontmatter.sh" "$FILE"; then
|
||||
FILE_ERRORS=$((FILE_ERRORS + 1))
|
||||
fi
|
||||
|
||||
if ! bash "$SCRIPT_DIR/validate-links.sh" "$FILE"; then
|
||||
FILE_ERRORS=$((FILE_ERRORS + 1))
|
||||
fi
|
||||
|
||||
if ! bash "$SCRIPT_DIR/validate-code-blocks.sh" "$FILE"; then
|
||||
FILE_ERRORS=$((FILE_ERRORS + 1))
|
||||
fi
|
||||
|
||||
if [ $FILE_ERRORS -eq 0 ]; then
|
||||
echo "✓ $FILE"
|
||||
else
|
||||
echo "✗ $FILE"
|
||||
FAILED_FILES=$((FAILED_FILES + 1))
|
||||
fi
|
||||
|
||||
TOTAL_ERRORS=$((TOTAL_ERRORS + FILE_ERRORS))
|
||||
echo ""
|
||||
done
|
||||
|
||||
if [ $TOTAL_ERRORS -eq 0 ]; then
|
||||
echo "✓ 所有验证通过"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "✗ 验证失败:$FAILED_FILES 个文件存在问题"
|
||||
exit 1
|
||||
@ -0,0 +1,62 @@
|
||||
#!/bin/bash
|
||||
# 验证 Markdown 代码块是否闭合并带有语言标记。
|
||||
|
||||
set -e
|
||||
|
||||
FILE="$1"
|
||||
|
||||
if [ -z "$FILE" ]; then
|
||||
echo "用法: $0 <文件路径>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$FILE" ]; then
|
||||
echo "错误: 文件不存在: $FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ERROR_COUNT=0
|
||||
WARNING_COUNT=0
|
||||
CODE_FENCE_COUNT=$(grep -c '^```' "$FILE" || true)
|
||||
|
||||
if [ $((CODE_FENCE_COUNT % 2)) -ne 0 ]; then
|
||||
echo "✗ 错误: 存在未闭合的代码块"
|
||||
ERROR_COUNT=$((ERROR_COUNT + 1))
|
||||
fi
|
||||
|
||||
LINE_NUMBER=0
|
||||
IN_CODE_BLOCK=0
|
||||
while IFS= read -r LINE || [ -n "$LINE" ]; do
|
||||
LINE_NUMBER=$((LINE_NUMBER + 1))
|
||||
|
||||
if [[ "$LINE" =~ ^\`\`\`(cs|c#|C#)$ ]]; then
|
||||
echo "⚠ 警告: 第 $LINE_NUMBER 行使用了非标准 C# 标记,建议改为 csharp"
|
||||
WARNING_COUNT=$((WARNING_COUNT + 1))
|
||||
fi
|
||||
|
||||
if [[ "$LINE" =~ ^\`\`\` ]]; then
|
||||
if [ "$IN_CODE_BLOCK" -eq 0 ]; then
|
||||
if [[ "$LINE" == '```' ]]; then
|
||||
echo "⚠ 警告: 第 $LINE_NUMBER 行的代码块缺少语言标记"
|
||||
WARNING_COUNT=$((WARNING_COUNT + 1))
|
||||
fi
|
||||
|
||||
IN_CODE_BLOCK=1
|
||||
else
|
||||
IN_CODE_BLOCK=0
|
||||
fi
|
||||
fi
|
||||
done < "$FILE"
|
||||
|
||||
if [ $ERROR_COUNT -eq 0 ] && [ $WARNING_COUNT -eq 0 ]; then
|
||||
echo "✓ 代码块验证通过"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ $ERROR_COUNT -eq 0 ]; then
|
||||
echo "⚠ 代码块验证通过,但有 $WARNING_COUNT 个警告"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "✗ 代码块验证失败($ERROR_COUNT 个错误,$WARNING_COUNT 个警告)"
|
||||
exit 1
|
||||
@ -0,0 +1,40 @@
|
||||
#!/bin/bash
|
||||
# 验证 Markdown frontmatter。
|
||||
|
||||
set -e
|
||||
|
||||
FILE="$1"
|
||||
|
||||
if [ -z "$FILE" ]; then
|
||||
echo "用法: $0 <文件路径>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$FILE" ]; then
|
||||
echo "错误: 文件不存在: $FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! head -n 5 "$FILE" | grep -q "^---$"; then
|
||||
echo "✗ 错误: 文件缺少 frontmatter"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
FRONTMATTER=$(sed -n '/^---$/,/^---$/p' "$FILE" | sed '1d;$d')
|
||||
|
||||
if [ -z "$FRONTMATTER" ]; then
|
||||
echo "✗ 错误: frontmatter 为空"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! echo "$FRONTMATTER" | grep -q "^title:"; then
|
||||
echo "✗ 错误: 缺少必需字段: title"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! echo "$FRONTMATTER" | grep -q "^description:"; then
|
||||
echo "✗ 错误: 缺少必需字段: description"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ Frontmatter 验证通过"
|
||||
@ -1,11 +1,9 @@
|
||||
#!/bin/bash
|
||||
# 验证内部链接有效性
|
||||
# 用法: validate-links.sh <文件路径>
|
||||
# 验证 Markdown 内部链接是否指向当前仓库中的真实页面。
|
||||
|
||||
set -e
|
||||
|
||||
FILE="$1"
|
||||
BASE_DIR="docs/zh-CN"
|
||||
|
||||
if [ -z "$FILE" ]; then
|
||||
echo "用法: $0 <文件路径>"
|
||||
@ -17,58 +15,40 @@ if [ ! -f "$FILE" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "验证内部链接: $FILE"
|
||||
|
||||
# 获取文件所在目录
|
||||
FILE_DIR=$(dirname "$FILE")
|
||||
|
||||
# 提取所有 Markdown 链接
|
||||
LINKS=$(grep -oP '\[([^\]]+)\]\(([^)]+)\)' "$FILE" | grep -oP '\(([^)]+)\)' | sed 's/[()]//g' || true)
|
||||
|
||||
if [ -z "$LINKS" ]; then
|
||||
echo "✓ 未找到链接"
|
||||
echo "✓ 未找到需要验证的链接"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
ERROR_COUNT=0
|
||||
|
||||
while IFS= read -r LINK; do
|
||||
# 跳过外部链接
|
||||
if [[ "$LINK" =~ ^https?:// ]]; then
|
||||
if [[ "$LINK" =~ ^https?:// ]] || [[ "$LINK" =~ ^mailto: ]] || [[ "$LINK" =~ ^# ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# 跳过锚点链接(仅 #开头)
|
||||
if [[ "$LINK" =~ ^# ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# 移除锚点部分
|
||||
LINK_PATH=$(echo "$LINK" | sed 's/#.*//')
|
||||
|
||||
# 跳过空路径
|
||||
if [ -z "$LINK_PATH" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# 处理相对路径
|
||||
if [[ "$LINK_PATH" =~ ^\. ]]; then
|
||||
TARGET="$FILE_DIR/$LINK_PATH"
|
||||
# 处理绝对路径
|
||||
elif [[ "$LINK_PATH" =~ ^/ ]]; then
|
||||
if [[ "$LINK_PATH" =~ ^/ ]]; then
|
||||
TARGET="docs$LINK_PATH"
|
||||
# 如果没有扩展名,尝试添加 .md
|
||||
if [[ ! "$TARGET" =~ \. ]]; then
|
||||
if [[ ! "$TARGET" =~ \.[A-Za-z0-9]+$ ]]; then
|
||||
TARGET="$TARGET.md"
|
||||
fi
|
||||
elif [[ "$LINK_PATH" =~ ^\. ]]; then
|
||||
TARGET="$FILE_DIR/$LINK_PATH"
|
||||
else
|
||||
TARGET="$FILE_DIR/$LINK_PATH"
|
||||
fi
|
||||
|
||||
# 规范化路径
|
||||
TARGET=$(realpath -m "$TARGET" 2>/dev/null || echo "$TARGET")
|
||||
|
||||
# 检查文件是否存在
|
||||
if [ ! -f "$TARGET" ] && [ ! -d "$TARGET" ]; then
|
||||
echo "✗ 损坏的链接: $LINK"
|
||||
echo " 目标不存在: $TARGET"
|
||||
@ -77,9 +57,9 @@ while IFS= read -r LINK; do
|
||||
done <<< "$LINKS"
|
||||
|
||||
if [ $ERROR_COUNT -eq 0 ]; then
|
||||
echo "✓ 内部链接验证通过"
|
||||
echo "✓ 链接验证通过"
|
||||
exit 0
|
||||
else
|
||||
echo "✗ 发现 $ERROR_COUNT 个损坏的链接"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✗ 共发现 $ERROR_COUNT 个损坏链接"
|
||||
exit 1
|
||||
@ -0,0 +1,27 @@
|
||||
---
|
||||
title: {{API_TITLE}}
|
||||
description: {{API_DESCRIPTION}}
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# {{API_TITLE}}
|
||||
|
||||
## 概述
|
||||
|
||||
{{API_OVERVIEW}}
|
||||
|
||||
## 适用范围
|
||||
|
||||
{{API_SCOPE}}
|
||||
|
||||
## 关键成员
|
||||
|
||||
{{KEY_MEMBERS}}
|
||||
|
||||
## 最小示例
|
||||
|
||||
{{MINIMUM_EXAMPLE}}
|
||||
|
||||
## 相关类型
|
||||
|
||||
{{RELATED_TYPES}}
|
||||
@ -0,0 +1,30 @@
|
||||
---
|
||||
title: {{MODULE_TITLE}}
|
||||
description: {{MODULE_DESCRIPTION}}
|
||||
---
|
||||
|
||||
# {{MODULE_TITLE}}
|
||||
|
||||
## 模块定位
|
||||
|
||||
{{MODULE_POSITIONING}}
|
||||
|
||||
## 包关系
|
||||
|
||||
{{PACKAGE_RELATIONSHIP}}
|
||||
|
||||
## 最小接入路径
|
||||
|
||||
{{MINIMUM_ADOPTION_PATH}}
|
||||
|
||||
## 关键入口
|
||||
|
||||
{{KEY_ENTRY_POINTS}}
|
||||
|
||||
## 当前边界
|
||||
|
||||
{{CURRENT_BOUNDARIES}}
|
||||
|
||||
## 继续阅读
|
||||
|
||||
{{NEXT_READING}}
|
||||
@ -0,0 +1,26 @@
|
||||
---
|
||||
title: {{TOPIC_TITLE}}
|
||||
description: {{TOPIC_DESCRIPTION}}
|
||||
---
|
||||
|
||||
# {{TOPIC_TITLE}}
|
||||
|
||||
## 当前角色
|
||||
|
||||
{{CURRENT_ROLE}}
|
||||
|
||||
## 公开入口
|
||||
|
||||
{{PUBLIC_ENTRY_POINTS}}
|
||||
|
||||
## 最小示例
|
||||
|
||||
{{MINIMUM_EXAMPLE}}
|
||||
|
||||
## 兼容与迁移边界
|
||||
|
||||
{{COMPATIBILITY_BOUNDARY}}
|
||||
|
||||
## 相关页面
|
||||
|
||||
{{RELATED_PAGES}}
|
||||
30
.agents/skills/gframework-doc-refresh/templates/tutorial.md
Normal file
30
.agents/skills/gframework-doc-refresh/templates/tutorial.md
Normal file
@ -0,0 +1,30 @@
|
||||
---
|
||||
title: {{TUTORIAL_TITLE}}
|
||||
description: {{TUTORIAL_DESCRIPTION}}
|
||||
---
|
||||
|
||||
# {{TUTORIAL_TITLE}}
|
||||
|
||||
## 学习目标
|
||||
|
||||
{{LEARNING_OBJECTIVES}}
|
||||
|
||||
## 前置条件
|
||||
|
||||
{{PREREQUISITES}}
|
||||
|
||||
## 步骤
|
||||
|
||||
{{STEP_SEQUENCE}}
|
||||
|
||||
## 完整代码
|
||||
|
||||
{{FULL_CODE}}
|
||||
|
||||
## 验证结果
|
||||
|
||||
{{EXPECTED_RESULT}}
|
||||
|
||||
## 继续阅读
|
||||
|
||||
{{NEXT_READING}}
|
||||
@ -1,6 +1,6 @@
|
||||
---
|
||||
name: gframework-pr-review
|
||||
description: Repository-specific GitHub PR review workflow for the GFramework repo. Use when Codex needs to inspect the GitHub pull request for the current branch, extract CodeRabbit summary/comments, read failed checks, MegaLinter warnings, or failed test signals from the PR page, and then verify which findings should be fixed in the local codebase. Trigger explicitly with $gframework-pr-review or with prompts such as "look at the current PR", "extract CodeRabbit comments", or "check Failed Tests on the PR".
|
||||
description: Repository-specific GitHub PR review workflow for the GFramework repo. Use when Codex needs to inspect the GitHub pull request for the current branch, extract AI review findings from CodeRabbit or greptile-apps, read failed checks, MegaLinter warnings, or failed test signals from the PR page, and then verify which findings should be fixed in the local codebase. Trigger explicitly with $gframework-pr-review or with prompts such as "look at the current PR", "extract CodeRabbit comments", "extract Greptile comments", or "check Failed Tests on the PR".
|
||||
---
|
||||
|
||||
# GFramework PR Review
|
||||
@ -16,8 +16,10 @@ Shortcut: `$gframework-pr-review`
|
||||
3. Run `scripts/fetch_current_pr_review.py` to:
|
||||
- locate the PR for the current branch through the GitHub PR API
|
||||
- fetch PR metadata, issue comments, reviews, and review comments through the GitHub API
|
||||
- extract `Summary by CodeRabbit`、GitHub Actions bot comments such as `MegaLinter analysis: Success with warnings`、and CTRF test reports from issue comments
|
||||
- extract CodeRabbit-specific summary blocks such as `Summary by CodeRabbit` and actionable-comment rollups when present
|
||||
- parse the latest CodeRabbit review body itself, including folded sections such as `🧹 Nitpick comments (N)` and the overall AI-agent prompt
|
||||
- capture unresolved latest-head review threads for supported AI reviewers, including both `coderabbitai[bot]` and `greptile-apps[bot]`
|
||||
- surface which supported AI reviewers currently have open latest-commit review threads, even when they do not use CodeRabbit-style issue comments
|
||||
- fetch the latest head commit review threads from the GitHub PR API
|
||||
- prefer unresolved review threads on the latest head commit over older summary-only signals
|
||||
- extract failed checks, MegaLinter detailed issues, and test-report signals such as `Failed Tests` or `No failed tests in this run`
|
||||
@ -49,6 +51,7 @@ Shortcut: `$gframework-pr-review`
|
||||
The script should produce:
|
||||
|
||||
- PR metadata: number, title, state, branch, URL
|
||||
- Supported AI reviewer summary, including latest reviews and open-thread counts for `coderabbitai[bot]` and `greptile-apps[bot]`
|
||||
- CodeRabbit summary block from issue comments when available
|
||||
- Folded latest-review sections such as `Nitpick comments (N)` when CodeRabbit puts them in the review body instead of issue comments
|
||||
- Parsed latest head-review threads, with unresolved threads clearly separated
|
||||
@ -66,6 +69,7 @@ The script should produce:
|
||||
- If GitHub access fails because of proxy configuration, rerun the fetch with proxy variables removed.
|
||||
- Prefer GitHub API results over PR HTML. The PR HTML page is now a fallback/debugging source, not the primary source of truth.
|
||||
- If the summary block and the latest head review threads disagree, trust the latest unresolved head-review threads and treat older summary findings as stale until re-verified locally.
|
||||
- Do not assume every AI reviewer behaves like CodeRabbit. `greptile-apps[bot]` findings may exist only as latest-head review threads, without CodeRabbit-style issue comments or folded review-body sections.
|
||||
- Treat GitHub Actions comments with `Success with warnings` as actionable review input when they include concrete linter diagnostics such as `MegaLinter` detailed issues; do not skip them just because the parent check is green.
|
||||
- Do not assume all CodeRabbit findings live in issue comments. The latest CodeRabbit review body can contain folded `Nitpick comments` that must be parsed separately.
|
||||
- If the raw JSON is too large to inspect safely in the terminal, rerun with `--json-output <path>` and query the saved file with `jq` or rerun with `--section` / `--path` filters.
|
||||
@ -76,5 +80,6 @@ The script should produce:
|
||||
- 'Use FPR'
|
||||
- `Use $gframework-pr-review on the current branch`
|
||||
- `Check the current PR and extract CodeRabbit suggestions`
|
||||
- `Check the current PR and extract Greptile suggestions`
|
||||
- `Look for Failed Tests on the PR page`
|
||||
- `先用 $gframework-pr-review 看当前分支 PR`
|
||||
4
.agents/skills/gframework-pr-review/agents/openai.yaml
Normal file
4
.agents/skills/gframework-pr-review/agents/openai.yaml
Normal file
@ -0,0 +1,4 @@
|
||||
interface:
|
||||
display_name: "GFramework PR Review"
|
||||
short_description: "Inspect the current PR and AI review findings"
|
||||
default_prompt: "Use $gframework-pr-review to inspect the current branch PR through the GitHub API, prioritize unresolved review threads on the latest head commit from supported AI reviewers such as CodeRabbit and greptile-apps, and summarize failed checks or failed tests."
|
||||
@ -25,11 +25,26 @@ DEFAULT_WINDOWS_GIT = "/mnt/d/Tool/Development Tools/Git/cmd/git.exe"
|
||||
GIT_ENVIRONMENT_KEY = "GFRAMEWORK_WINDOWS_GIT"
|
||||
USER_AGENT = "codex-gframework-pr-review"
|
||||
CODERABBIT_LOGIN = "coderabbitai[bot]"
|
||||
GREPTILE_LOGIN = "greptile-apps[bot]"
|
||||
GITHUB_ACTIONS_LOGIN = "github-actions[bot]"
|
||||
REVIEW_COMMENT_ADDRESSED_MARKER = "<!-- <review_comment_addressed> -->"
|
||||
VISIBLE_ADDRESSED_IN_COMMIT_PATTERN = re.compile(r"✅\s*Addressed in commit\s+[0-9a-f]{7,40}", re.I)
|
||||
DEFAULT_REQUEST_TIMEOUT_SECONDS = 60
|
||||
REQUEST_TIMEOUT_ENVIRONMENT_KEY = "GFRAMEWORK_PR_REVIEW_TIMEOUT_SECONDS"
|
||||
SUPPORTED_AI_REVIEWERS = (
|
||||
{
|
||||
"slug": "coderabbit",
|
||||
"login": CODERABBIT_LOGIN,
|
||||
"display_name": "CodeRabbit",
|
||||
"supports_review_body_parsing": True,
|
||||
},
|
||||
{
|
||||
"slug": "greptile",
|
||||
"login": GREPTILE_LOGIN,
|
||||
"display_name": "Greptile",
|
||||
"supports_review_body_parsing": False,
|
||||
},
|
||||
)
|
||||
DISPLAY_SECTION_CHOICES = (
|
||||
"pr",
|
||||
"failed-checks",
|
||||
@ -44,6 +59,7 @@ DISPLAY_SECTION_CHOICES = (
|
||||
|
||||
|
||||
def resolve_git_command() -> str:
|
||||
"""Resolve the git executable to use for this repository."""
|
||||
candidates = [
|
||||
os.environ.get(GIT_ENVIRONMENT_KEY),
|
||||
DEFAULT_WINDOWS_GIT,
|
||||
@ -68,6 +84,7 @@ def resolve_git_command() -> str:
|
||||
|
||||
|
||||
def resolve_request_timeout_seconds() -> int:
|
||||
"""Return the GitHub request timeout in seconds."""
|
||||
configured_timeout = os.environ.get(REQUEST_TIMEOUT_ENVIRONMENT_KEY)
|
||||
if not configured_timeout:
|
||||
return DEFAULT_REQUEST_TIMEOUT_SECONDS
|
||||
@ -86,6 +103,7 @@ def resolve_request_timeout_seconds() -> int:
|
||||
|
||||
|
||||
def run_command(args: list[str]) -> str:
|
||||
"""Run a command and return stdout, raising on failure."""
|
||||
process = subprocess.run(args, capture_output=True, text=True, check=False)
|
||||
if process.returncode != 0:
|
||||
stderr = process.stderr.strip()
|
||||
@ -94,10 +112,12 @@ def run_command(args: list[str]) -> str:
|
||||
|
||||
|
||||
def get_current_branch() -> str:
|
||||
"""Return the current git branch name."""
|
||||
return run_command([resolve_git_command(), "rev-parse", "--abbrev-ref", "HEAD"])
|
||||
|
||||
|
||||
def open_url(url: str, accept: str) -> tuple[str, Any]:
|
||||
"""Open a URL with proxy variables disabled and return decoded text plus headers."""
|
||||
opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
|
||||
request = urllib.request.Request(url, headers={"Accept": accept, "User-Agent": USER_AGENT})
|
||||
with opener.open(request, timeout=resolve_request_timeout_seconds()) as response:
|
||||
@ -105,11 +125,13 @@ def open_url(url: str, accept: str) -> tuple[str, Any]:
|
||||
|
||||
|
||||
def fetch_json(url: str) -> tuple[Any, Any]:
|
||||
"""Fetch a JSON payload and its response headers from GitHub."""
|
||||
text, headers = open_url(url, accept="application/vnd.github+json")
|
||||
return json.loads(text), headers
|
||||
|
||||
|
||||
def extract_next_link(headers: Any) -> str | None:
|
||||
"""Extract the next-page link from GitHub pagination headers."""
|
||||
link_header = headers.get("Link")
|
||||
if not link_header:
|
||||
return None
|
||||
@ -119,6 +141,7 @@ def extract_next_link(headers: Any) -> str | None:
|
||||
|
||||
|
||||
def fetch_paged_json(url: str) -> list[dict[str, Any]]:
|
||||
"""Fetch every page from a paginated GitHub API endpoint."""
|
||||
items: list[dict[str, Any]] = []
|
||||
next_url: str | None = url
|
||||
while next_url:
|
||||
@ -133,6 +156,7 @@ def fetch_paged_json(url: str) -> list[dict[str, Any]]:
|
||||
|
||||
|
||||
def fetch_pull_request_metadata(pr_number: int) -> dict[str, Any]:
|
||||
"""Fetch normalized metadata for a pull request."""
|
||||
payload, _ = fetch_json(f"https://api.github.com/repos/{OWNER}/{REPO}/pulls/{pr_number}")
|
||||
if not isinstance(payload, dict):
|
||||
raise RuntimeError("Failed to fetch GitHub PR metadata.")
|
||||
@ -148,6 +172,7 @@ def fetch_pull_request_metadata(pr_number: int) -> dict[str, Any]:
|
||||
|
||||
|
||||
def resolve_pr_number(branch: str) -> int:
|
||||
"""Resolve the most recently updated PR number for a branch."""
|
||||
head_query = urllib.parse.quote(f"{OWNER}:{branch}")
|
||||
payload, _ = fetch_json(f"https://api.github.com/repos/{OWNER}/{REPO}/pulls?state=all&head={head_query}")
|
||||
if not isinstance(payload, list):
|
||||
@ -162,10 +187,12 @@ def resolve_pr_number(branch: str) -> int:
|
||||
|
||||
|
||||
def collapse_whitespace(text: str) -> str:
|
||||
"""Collapse repeated whitespace into single spaces."""
|
||||
return re.sub(r"\s+", " ", text).strip()
|
||||
|
||||
|
||||
def truncate_text(text: str, max_length: int) -> str:
|
||||
"""Collapse whitespace and truncate long text for CLI display."""
|
||||
collapsed = collapse_whitespace(text)
|
||||
if max_length <= 0 or len(collapsed) <= max_length:
|
||||
return collapsed
|
||||
@ -174,14 +201,17 @@ def truncate_text(text: str, max_length: int) -> str:
|
||||
|
||||
|
||||
def strip_tags(text: str) -> str:
|
||||
"""Remove HTML tags and normalize whitespace."""
|
||||
return collapse_whitespace(re.sub(r"<[^>]+>", " ", text))
|
||||
|
||||
|
||||
def strip_markdown_links(text: str) -> str:
|
||||
"""Drop Markdown link targets while keeping visible link text."""
|
||||
return re.sub(r"\[([^\]]+)\]\([^)]+\)", r"\1", text)
|
||||
|
||||
|
||||
def extract_section(text: str, start_marker: str, end_markers: list[str]) -> str | None:
|
||||
"""Extract text between a start marker and the earliest matching end marker."""
|
||||
start = text.find(start_marker)
|
||||
if start < 0:
|
||||
return None
|
||||
@ -196,6 +226,7 @@ def extract_section(text: str, start_marker: str, end_markers: list[str]) -> str
|
||||
|
||||
|
||||
def parse_failed_checks(summary_block: str) -> list[dict[str, str]]:
|
||||
"""Parse CodeRabbit summary rows for failed checks."""
|
||||
failed_section = extract_section(
|
||||
summary_block,
|
||||
"### ❌ Failed checks",
|
||||
@ -227,6 +258,7 @@ def parse_failed_checks(summary_block: str) -> list[dict[str, str]]:
|
||||
|
||||
|
||||
def parse_actionable_comments(actionable_block: str) -> dict[str, Any]:
|
||||
"""Parse CodeRabbit actionable comments from its issue-comment rollup."""
|
||||
comment_count_match = re.search(r"Actionable comments posted:\s*(\d+)", actionable_block)
|
||||
count = int(comment_count_match.group(1)) if comment_count_match else 0
|
||||
|
||||
@ -251,6 +283,7 @@ def parse_actionable_comments(actionable_block: str) -> dict[str, Any]:
|
||||
|
||||
|
||||
def parse_comment_cards(comment_block: str) -> list[dict[str, str]]:
|
||||
"""Parse CodeRabbit comment cards from a grouped Markdown block."""
|
||||
comments: list[dict[str, str]] = []
|
||||
pattern = re.compile(
|
||||
r"<summary>"
|
||||
@ -287,6 +320,7 @@ def parse_comment_cards(comment_block: str) -> list[dict[str, str]]:
|
||||
|
||||
|
||||
def normalize_review_body_for_parsing(review_body: str) -> str:
|
||||
"""Normalize a review body before structured section parsing."""
|
||||
# CodeRabbit sometimes wraps structured HTML sections in markdown blockquotes,
|
||||
# such as the CAUTION block used for outside-diff comments. Remove the quote
|
||||
# prefixes for parsing while leaving the original raw body unchanged for output.
|
||||
@ -294,6 +328,7 @@ def normalize_review_body_for_parsing(review_body: str) -> str:
|
||||
|
||||
|
||||
def find_section_block_end(review_body: str, block_start: int) -> int:
|
||||
"""Find the end boundary for a nested <details> section."""
|
||||
depth = 1
|
||||
for tag_match in re.finditer(r"<details>|</details>", review_body[block_start:]):
|
||||
tag = tag_match.group(0)
|
||||
@ -308,6 +343,7 @@ def find_section_block_end(review_body: str, block_start: int) -> int:
|
||||
|
||||
|
||||
def parse_review_comment_group(review_body: str, section_name: str) -> dict[str, Any]:
|
||||
"""Parse a folded review-body section into structured comments."""
|
||||
section_match = re.search(
|
||||
rf"<summary>[^<]*{re.escape(section_name)} \((?P<count>\d+)\)</summary><blockquote>\s*",
|
||||
review_body,
|
||||
@ -327,6 +363,7 @@ def parse_review_comment_group(review_body: str, section_name: str) -> dict[str,
|
||||
|
||||
|
||||
def parse_latest_review_body(review_body: str) -> dict[str, Any]:
|
||||
"""Parse the latest CodeRabbit review body for grouped comment sections."""
|
||||
normalized_review_body = normalize_review_body_for_parsing(review_body)
|
||||
actionable_count_match = re.search(r"\*\*Actionable comments posted:\s*(\d+)\*\*", normalized_review_body)
|
||||
prompt_match = re.search(
|
||||
@ -348,6 +385,7 @@ def parse_latest_review_body(review_body: str) -> dict[str, Any]:
|
||||
|
||||
|
||||
def parse_megalinter_comment(comment_body: str) -> dict[str, Any]:
|
||||
"""Parse a MegaLinter issue comment into structured report fields."""
|
||||
normalized_body = html.unescape(comment_body).strip()
|
||||
summary_match = re.search(
|
||||
r"##\s*(?P<badges>.*?)\[MegaLinter\]\([^)]+\)\s+analysis:\s+\[(?P<status>[^\]]+)\]\((?P<run_url>[^)]+)\)",
|
||||
@ -402,6 +440,7 @@ def parse_megalinter_comment(comment_body: str) -> dict[str, Any]:
|
||||
|
||||
|
||||
def parse_test_report(block: str) -> dict[str, Any]:
|
||||
"""Parse a CTRF or GitHub test-reporter comment block."""
|
||||
report: dict[str, Any] = {
|
||||
"raw": block.strip(),
|
||||
"stats": {},
|
||||
@ -442,6 +481,7 @@ def parse_test_report(block: str) -> dict[str, Any]:
|
||||
|
||||
|
||||
def fetch_issue_comments(pr_number: int) -> list[dict[str, Any]]:
|
||||
"""Fetch issue comments for a pull request."""
|
||||
return fetch_paged_json(f"https://api.github.com/repos/{OWNER}/{REPO}/issues/{pr_number}/comments?per_page=100")
|
||||
|
||||
|
||||
@ -450,6 +490,7 @@ def select_latest_comment_body(
|
||||
predicate: Any,
|
||||
required_user: str | None = None,
|
||||
) -> str:
|
||||
"""Return the latest matching issue-comment body."""
|
||||
matching_comments = []
|
||||
for comment in comments:
|
||||
body = html.unescape(str(comment.get("body", "")))
|
||||
@ -472,6 +513,7 @@ def select_comment_bodies(
|
||||
predicate: Any,
|
||||
required_user: str | None = None,
|
||||
) -> list[str]:
|
||||
"""Return all matching issue-comment bodies in chronological order."""
|
||||
matching_comments = []
|
||||
for comment in comments:
|
||||
body = html.unescape(str(comment.get("body", "")))
|
||||
@ -487,6 +529,7 @@ def select_comment_bodies(
|
||||
|
||||
|
||||
def summarize_review_comment(comment: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Normalize a GitHub review comment into the output shape used by the skill."""
|
||||
return {
|
||||
"id": comment.get("id"),
|
||||
"path": comment.get("path") or "",
|
||||
@ -502,6 +545,7 @@ def summarize_review_comment(comment: dict[str, Any]) -> dict[str, Any]:
|
||||
|
||||
|
||||
def classify_review_thread_status(latest_comment: dict[str, Any]) -> str:
|
||||
"""Classify whether a review thread is still open or already addressed."""
|
||||
body = latest_comment.get("body") or ""
|
||||
author = latest_comment.get("user") or ""
|
||||
if author == CODERABBIT_LOGIN and REVIEW_COMMENT_ADDRESSED_MARKER in body:
|
||||
@ -510,10 +554,12 @@ def classify_review_thread_status(latest_comment: dict[str, Any]) -> str:
|
||||
|
||||
|
||||
def contains_visible_addressed_commit_text(body: str) -> bool:
|
||||
"""Detect visible addressed-in-commit text that does not close the thread by itself."""
|
||||
return bool(VISIBLE_ADDRESSED_IN_COMMIT_PATTERN.search(body))
|
||||
|
||||
|
||||
def build_latest_commit_review_threads(comments: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
"""Group review comments into normalized latest-commit review threads."""
|
||||
comment_threads: dict[int, dict[str, Any]] = {}
|
||||
|
||||
# GitHub review replies point to the root comment id. Grouping them first lets
|
||||
@ -564,6 +610,7 @@ def select_latest_submitted_review(
|
||||
required_user: str | None = None,
|
||||
prefer_non_empty_body: bool = False,
|
||||
) -> dict[str, Any] | None:
|
||||
"""Select the newest submitted review, optionally filtered by user."""
|
||||
filtered_reviews = [review for review in reviews if review.get("submitted_at")]
|
||||
if required_user is not None:
|
||||
filtered_reviews = [review for review in filtered_reviews if review.get("user", {}).get("login") == required_user]
|
||||
@ -579,7 +626,43 @@ def select_latest_submitted_review(
|
||||
return max(filtered_reviews, key=lambda review: review.get("submitted_at", ""))
|
||||
|
||||
|
||||
def summarize_submitted_review(review: dict[str, Any] | None) -> dict[str, Any]:
|
||||
"""Normalize a submitted review into a stable JSON shape."""
|
||||
if review is None:
|
||||
return {
|
||||
"id": None,
|
||||
"state": "",
|
||||
"submitted_at": "",
|
||||
"commit_id": "",
|
||||
"user": "",
|
||||
"body": "",
|
||||
}
|
||||
|
||||
return {
|
||||
"id": review.get("id"),
|
||||
"state": review.get("state") or "",
|
||||
"submitted_at": review.get("submitted_at") or "",
|
||||
"commit_id": review.get("commit_id") or "",
|
||||
"user": review.get("user", {}).get("login") or "",
|
||||
"body": review.get("body") or "",
|
||||
}
|
||||
|
||||
|
||||
def build_open_thread_counts_by_user(open_threads: list[dict[str, Any]]) -> dict[str, int]:
|
||||
"""Count open latest-commit threads by their root-comment author."""
|
||||
counts: dict[str, int] = {}
|
||||
for thread in open_threads:
|
||||
root_user = str(thread.get("root_comment", {}).get("user") or "")
|
||||
if not root_user:
|
||||
continue
|
||||
|
||||
counts[root_user] = counts.get(root_user, 0) + 1
|
||||
|
||||
return counts
|
||||
|
||||
|
||||
def fetch_latest_commit_review(pr_number: int) -> dict[str, Any]:
|
||||
"""Fetch the latest commit review, grouped threads, and AI-reviewer summaries."""
|
||||
api_base = f"https://api.github.com/repos/{OWNER}/{REPO}/pulls/{pr_number}"
|
||||
commits = fetch_paged_json(f"{api_base}/commits?per_page=100")
|
||||
reviews = fetch_paged_json(f"{api_base}/reviews?per_page=100")
|
||||
@ -600,47 +683,37 @@ def fetch_latest_commit_review(pr_number: int) -> dict[str, Any]:
|
||||
]
|
||||
candidate_reviews = latest_commit_reviews or [review for review in reviews if review.get("submitted_at")]
|
||||
latest_review = select_latest_submitted_review(candidate_reviews)
|
||||
latest_coderabbit_review_with_body = select_latest_submitted_review(
|
||||
candidate_reviews,
|
||||
required_user=CODERABBIT_LOGIN,
|
||||
prefer_non_empty_body=True,
|
||||
)
|
||||
latest_reviews_by_user: dict[str, dict[str, Any]] = {}
|
||||
for agent in SUPPORTED_AI_REVIEWERS:
|
||||
latest_reviews_by_user[agent["login"]] = summarize_submitted_review(
|
||||
select_latest_submitted_review(
|
||||
candidate_reviews,
|
||||
required_user=agent["login"],
|
||||
prefer_non_empty_body=True,
|
||||
)
|
||||
)
|
||||
|
||||
latest_commit_comments = [comment for comment in comments if comment.get("commit_id") == latest_commit_sha]
|
||||
threads = build_latest_commit_review_threads(latest_commit_comments)
|
||||
open_threads = [thread for thread in threads if thread["status"] == "open"]
|
||||
open_thread_counts_by_user = build_open_thread_counts_by_user(open_threads)
|
||||
|
||||
return {
|
||||
"latest_commit": {
|
||||
"sha": latest_commit_sha,
|
||||
"message": latest_commit.get("commit", {}).get("message", ""),
|
||||
},
|
||||
"latest_review": {
|
||||
"id": latest_review.get("id") if latest_review else None,
|
||||
"state": latest_review.get("state") if latest_review else "",
|
||||
"submitted_at": latest_review.get("submitted_at") if latest_review else "",
|
||||
"commit_id": latest_review.get("commit_id") if latest_review else "",
|
||||
"user": latest_review.get("user", {}).get("login") if latest_review else "",
|
||||
"body": latest_review.get("body") if latest_review else "",
|
||||
},
|
||||
"latest_coderabbit_review_with_body": {
|
||||
"id": latest_coderabbit_review_with_body.get("id") if latest_coderabbit_review_with_body else None,
|
||||
"state": latest_coderabbit_review_with_body.get("state") if latest_coderabbit_review_with_body else "",
|
||||
"submitted_at": (
|
||||
latest_coderabbit_review_with_body.get("submitted_at") if latest_coderabbit_review_with_body else ""
|
||||
),
|
||||
"commit_id": latest_coderabbit_review_with_body.get("commit_id") if latest_coderabbit_review_with_body else "",
|
||||
"user": latest_coderabbit_review_with_body.get("user", {}).get("login")
|
||||
if latest_coderabbit_review_with_body
|
||||
else "",
|
||||
"body": latest_coderabbit_review_with_body.get("body") if latest_coderabbit_review_with_body else "",
|
||||
},
|
||||
"latest_review": summarize_submitted_review(latest_review),
|
||||
"latest_coderabbit_review_with_body": latest_reviews_by_user.get(CODERABBIT_LOGIN, {}),
|
||||
"latest_reviews_by_user": latest_reviews_by_user,
|
||||
"open_thread_counts_by_user": open_thread_counts_by_user,
|
||||
"threads": threads,
|
||||
"open_threads": open_threads,
|
||||
}
|
||||
|
||||
|
||||
def build_result(pr_number: int, branch: str) -> dict[str, Any]:
|
||||
"""Build the full review result payload for the selected PR."""
|
||||
warnings: list[str] = []
|
||||
pull_request_metadata = fetch_pull_request_metadata(pr_number)
|
||||
issue_comments = fetch_issue_comments(pr_number)
|
||||
@ -673,8 +746,26 @@ def build_result(pr_number: int, branch: str) -> dict[str, Any]:
|
||||
|
||||
latest_commit_review: dict[str, Any] = {}
|
||||
coderabbit_review: dict[str, Any] = {}
|
||||
review_agents: list[dict[str, Any]] = []
|
||||
try:
|
||||
latest_commit_review = fetch_latest_commit_review(pr_number)
|
||||
latest_reviews_by_user = latest_commit_review.get("latest_reviews_by_user", {})
|
||||
open_thread_counts_by_user = latest_commit_review.get("open_thread_counts_by_user", {})
|
||||
review_agents = [
|
||||
{
|
||||
"slug": agent["slug"],
|
||||
"login": agent["login"],
|
||||
"display_name": agent["display_name"],
|
||||
"supports_review_body_parsing": agent["supports_review_body_parsing"],
|
||||
"latest_review": latest_reviews_by_user.get(agent["login"], {}),
|
||||
"open_thread_count": int(open_thread_counts_by_user.get(agent["login"], 0)),
|
||||
"detected": bool(
|
||||
latest_reviews_by_user.get(agent["login"], {}).get("id")
|
||||
or open_thread_counts_by_user.get(agent["login"], 0)
|
||||
),
|
||||
}
|
||||
for agent in SUPPORTED_AI_REVIEWERS
|
||||
]
|
||||
latest_review = latest_commit_review.get("latest_coderabbit_review_with_body", {})
|
||||
latest_review_body = str(latest_review.get("body") or "")
|
||||
if latest_review.get("user") == CODERABBIT_LOGIN and latest_review_body:
|
||||
@ -723,6 +814,7 @@ def build_result(pr_number: int, branch: str) -> dict[str, Any]:
|
||||
},
|
||||
"coderabbit_comments": parse_actionable_comments(actionable_block) if actionable_block else {},
|
||||
"coderabbit_review": coderabbit_review,
|
||||
"review_agents": review_agents,
|
||||
"latest_commit_review": latest_commit_review,
|
||||
"megalinter_report": parse_megalinter_comment(megalinter_block) if megalinter_block else {},
|
||||
"test_reports": [parse_test_report(block) for block in test_blocks],
|
||||
@ -731,6 +823,7 @@ def build_result(pr_number: int, branch: str) -> dict[str, Any]:
|
||||
|
||||
|
||||
def write_json_output(result: dict[str, Any], output_path: str) -> str:
|
||||
"""Write the full JSON result to disk and return the destination path."""
|
||||
destination_path = Path(output_path).expanduser()
|
||||
destination_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
destination_path.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
@ -738,10 +831,12 @@ def write_json_output(result: dict[str, Any], output_path: str) -> str:
|
||||
|
||||
|
||||
def normalize_path_filters(path_filters: list[str] | None) -> list[str]:
|
||||
"""Normalize CLI path filters to slash-separated fragments."""
|
||||
return [path_filter.replace("\\", "/") for path_filter in (path_filters or []) if path_filter.strip()]
|
||||
|
||||
|
||||
def path_matches_filters(path: str, normalized_path_filters: list[str]) -> bool:
|
||||
"""Return whether a path matches any requested filter fragment."""
|
||||
if not normalized_path_filters:
|
||||
return True
|
||||
|
||||
@ -753,6 +848,7 @@ def filter_comments_by_path(
|
||||
comments: list[dict[str, Any]],
|
||||
normalized_path_filters: list[str],
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Filter parsed comments by CLI path fragment."""
|
||||
return [comment for comment in comments if path_matches_filters(str(comment.get("path") or ""), normalized_path_filters)]
|
||||
|
||||
|
||||
@ -760,6 +856,7 @@ def filter_threads_by_path(
|
||||
threads: list[dict[str, Any]],
|
||||
normalized_path_filters: list[str],
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Filter parsed review threads by CLI path fragment."""
|
||||
return [thread for thread in threads if path_matches_filters(str(thread.get("path") or ""), normalized_path_filters)]
|
||||
|
||||
|
||||
@ -771,6 +868,7 @@ def format_text(
|
||||
max_description_length: int = 400,
|
||||
json_output_path: str | None = None,
|
||||
) -> str:
|
||||
"""Format the result payload into concise text output."""
|
||||
lines: list[str] = []
|
||||
selected_sections = set(sections or DISPLAY_SECTION_CHOICES)
|
||||
normalized_path_filters = normalize_path_filters(path_filters)
|
||||
@ -865,6 +963,7 @@ def format_text(
|
||||
latest_review = latest_commit_review.get("latest_review", {})
|
||||
open_threads = latest_commit_review.get("open_threads", [])
|
||||
visible_open_threads = filter_threads_by_path(open_threads, normalized_path_filters)
|
||||
review_agents = [agent for agent in result.get("review_agents", []) if agent.get("detected")]
|
||||
if latest_commit and "open-threads" in selected_sections:
|
||||
lines.append("")
|
||||
lines.append(f"Latest reviewed commit: {latest_commit.get('sha', '')}")
|
||||
@ -874,6 +973,21 @@ def format_text(
|
||||
f"{latest_review.get('state', '')} by {latest_review.get('user', '')} "
|
||||
f"at {latest_review.get('submitted_at', '')}"
|
||||
)
|
||||
if review_agents:
|
||||
lines.append("Detected AI reviewers on latest commit:")
|
||||
for agent in review_agents:
|
||||
latest_agent_review = agent.get("latest_review", {})
|
||||
lines.append(
|
||||
"- "
|
||||
f"{agent.get('display_name', '')} ({agent.get('login', '')}): "
|
||||
f"open_threads={agent.get('open_thread_count', 0)}"
|
||||
+ (
|
||||
f", latest_review={latest_agent_review.get('state', '')} "
|
||||
f"at {latest_agent_review.get('submitted_at', '')}"
|
||||
if latest_agent_review.get("submitted_at")
|
||||
else ""
|
||||
)
|
||||
)
|
||||
|
||||
lines.append(
|
||||
"Latest commit review threads: "
|
||||
@ -961,6 +1075,7 @@ def format_text(
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
"""Parse CLI arguments."""
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--branch", help="Override the current branch name.")
|
||||
parser.add_argument("--pr", type=int, help="Fetch a specific PR number instead of resolving from branch.")
|
||||
@ -990,6 +1105,7 @@ def parse_args() -> argparse.Namespace:
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Run the CLI entry point."""
|
||||
args = parse_args()
|
||||
if args.pr is not None:
|
||||
pr_number = args.pr
|
||||
@ -1,469 +0,0 @@
|
||||
# VitePress 文档生成 Skills 系统
|
||||
|
||||
为 GFramework 项目提供自动化的 VitePress 文档生成能力。
|
||||
|
||||
## 概述
|
||||
|
||||
这是一套专门为 GFramework 项目设计的文档生成 skills,能够根据 C# 源代码自动生成高质量的 VitePress 文档。系统采用模块化设计,每个 skill 专注于特定的文档生成任务。
|
||||
|
||||
## 可用 Skills
|
||||
|
||||
### 1. vitepress-api-doc - API 文档生成
|
||||
|
||||
为单个 C# 文件生成 API 参考文档。
|
||||
|
||||
**用途**:
|
||||
- 类、接口、枚举的 API 文档
|
||||
- 方法、属性、事件的详细说明
|
||||
- 基于 XML 注释生成文档
|
||||
|
||||
**调用方式**:
|
||||
```bash
|
||||
/vitepress-api-doc <C# 文件路径>
|
||||
```
|
||||
|
||||
**示例**:
|
||||
```bash
|
||||
/vitepress-api-doc GFramework.Core/architecture/Architecture.cs
|
||||
```
|
||||
|
||||
**输出位置**:`docs/zh-CN/api-reference/<模块>/<文件名>.md`
|
||||
|
||||
[详细文档](./vitepress-api-doc/SKILL.md)
|
||||
|
||||
---
|
||||
|
||||
### 2. vitepress-guide - 功能指南生成
|
||||
|
||||
生成功能模块的使用指南文档。
|
||||
|
||||
**用途**:
|
||||
- 核心功能模块的使用说明
|
||||
- 设计模式和架构概念
|
||||
- 最佳实践和常见问题
|
||||
|
||||
**调用方式**:
|
||||
```bash
|
||||
/vitepress-guide <主题> <目标模块>
|
||||
```
|
||||
|
||||
**示例**:
|
||||
```bash
|
||||
/vitepress-guide "事件系统" Core
|
||||
/vitepress-guide "IoC 容器" Core
|
||||
```
|
||||
|
||||
**输出位置**:`docs/zh-CN/<模块>/<主题>.md`
|
||||
|
||||
[详细文档](./vitepress-guide/SKILL.md)
|
||||
|
||||
---
|
||||
|
||||
### 3. vitepress-tutorial - 分步教程生成
|
||||
|
||||
生成分步教程文档,适合初学者学习。
|
||||
|
||||
**用途**:
|
||||
- 框架入门教程
|
||||
- 功能实现教程
|
||||
- 问题解决方案
|
||||
|
||||
**调用方式**:
|
||||
```bash
|
||||
/vitepress-tutorial <教程主题>
|
||||
```
|
||||
|
||||
**示例**:
|
||||
```bash
|
||||
/vitepress-tutorial "创建第一个 System"
|
||||
/vitepress-tutorial "使用事件系统"
|
||||
```
|
||||
|
||||
**输出位置**:`docs/zh-CN/tutorials/<主题>.md`
|
||||
|
||||
[详细文档](./vitepress-tutorial/SKILL.md)
|
||||
|
||||
---
|
||||
|
||||
### 4. vitepress-batch-api - 批量 API 文档生成
|
||||
|
||||
为整个模块批量生成 API 文档。
|
||||
|
||||
**用途**:
|
||||
- 初始化模块文档
|
||||
- 更新整个模块的文档
|
||||
- 快速生成大量文档
|
||||
|
||||
**调用方式**:
|
||||
```bash
|
||||
/vitepress-batch-api <模块名>
|
||||
```
|
||||
|
||||
**示例**:
|
||||
```bash
|
||||
/vitepress-batch-api Core
|
||||
/vitepress-batch-api Godot
|
||||
```
|
||||
|
||||
**输出位置**:`docs/zh-CN/api-reference/<模块>/`
|
||||
|
||||
[详细文档](./vitepress-batch-api/SKILL.md)
|
||||
|
||||
---
|
||||
|
||||
### 5. vitepress-validate - 文档验证
|
||||
|
||||
验证文档的质量和规范性。
|
||||
|
||||
**用途**:
|
||||
- Frontmatter 格式验证
|
||||
- 内部链接有效性检查
|
||||
- 代码块语法验证
|
||||
- 标点符号规范检查
|
||||
|
||||
**调用方式**:
|
||||
```bash
|
||||
/vitepress-validate <文件或目录路径>
|
||||
```
|
||||
|
||||
**示例**:
|
||||
```bash
|
||||
/vitepress-validate docs/zh-CN/api-reference/core/architecture.md
|
||||
/vitepress-validate docs/zh-CN/
|
||||
```
|
||||
|
||||
[详细文档](./vitepress-validate/SKILL.md)
|
||||
|
||||
---
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 生成单个 API 文档
|
||||
|
||||
```bash
|
||||
# 为 Architecture 类生成文档
|
||||
/vitepress-api-doc GFramework.Core/architecture/Architecture.cs
|
||||
```
|
||||
|
||||
### 2. 批量生成模块文档
|
||||
|
||||
```bash
|
||||
# 为整个 Core 模块生成文档
|
||||
/vitepress-batch-api Core
|
||||
```
|
||||
|
||||
### 3. 生成功能指南
|
||||
|
||||
```bash
|
||||
# 生成事件系统使用指南
|
||||
/vitepress-guide "事件系统" Core
|
||||
```
|
||||
|
||||
### 4. 生成教程
|
||||
|
||||
```bash
|
||||
# 生成创建 Model 的教程
|
||||
/vitepress-tutorial "创建第一个 Model"
|
||||
```
|
||||
|
||||
### 5. 验证文档
|
||||
|
||||
```bash
|
||||
# 验证生成的文档
|
||||
/vitepress-validate docs/zh-CN/api-reference/core/
|
||||
```
|
||||
|
||||
## 工作流程
|
||||
|
||||
### 典型工作流程
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[开始] --> B{文档类型?}
|
||||
B -->|API 文档| C[/vitepress-api-doc]
|
||||
B -->|功能指南| D[/vitepress-guide]
|
||||
B -->|教程| E[/vitepress-tutorial]
|
||||
C --> F[/vitepress-validate]
|
||||
D --> F
|
||||
E --> F
|
||||
F --> G{验证通过?}
|
||||
G -->|是| H[完成]
|
||||
G -->|否| I[修复问题]
|
||||
I --> F
|
||||
```
|
||||
|
||||
### 推荐流程
|
||||
|
||||
1. **初始化模块文档**
|
||||
```bash
|
||||
/vitepress-batch-api Core
|
||||
```
|
||||
|
||||
2. **生成功能指南**
|
||||
```bash
|
||||
/vitepress-guide "IoC 容器" Core
|
||||
/vitepress-guide "事件系统" Core
|
||||
```
|
||||
|
||||
3. **生成教程**
|
||||
```bash
|
||||
/vitepress-tutorial "创建第一个 Model"
|
||||
/vitepress-tutorial "使用命令系统"
|
||||
```
|
||||
|
||||
4. **验证所有文档**
|
||||
```bash
|
||||
/vitepress-validate docs/zh-CN/
|
||||
```
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
.claude/skills/
|
||||
├── README.md # 本文件
|
||||
├── _shared/ # 共享资源
|
||||
│ └── scripts/ # 共享脚本
|
||||
│ ├── update-vitepress-nav.sh # 更新导航配置
|
||||
│ ├── parse-csharp-xml.sh # 解析 XML 注释
|
||||
│ └── generate-examples.sh # 生成代码示例
|
||||
│
|
||||
├── vitepress-api-doc/ # API 文档生成
|
||||
│ ├── SKILL.md # Skill 说明
|
||||
│ ├── template.md # 文档模板
|
||||
│ └── examples/ # 示例文档
|
||||
│ ├── class-example.md
|
||||
│ ├── interface-example.md
|
||||
│ └── enum-example.md
|
||||
│
|
||||
├── vitepress-guide/ # 功能指南生成
|
||||
│ ├── SKILL.md
|
||||
│ ├── template.md
|
||||
│ └── examples/
|
||||
│ └── guide-example.md
|
||||
│
|
||||
├── vitepress-tutorial/ # 教程生成
|
||||
│ ├── SKILL.md
|
||||
│ ├── template.md
|
||||
│ └── examples/
|
||||
│ └── tutorial-example.md
|
||||
│
|
||||
├── vitepress-batch-api/ # 批量 API 文档生成
|
||||
│ ├── SKILL.md
|
||||
│ └── scripts/
|
||||
│ └── batch-generate.sh
|
||||
│
|
||||
└── vitepress-validate/ # 文档验证
|
||||
├── SKILL.md
|
||||
└── scripts/
|
||||
├── validate-frontmatter.sh
|
||||
├── validate-links.sh
|
||||
├── validate-code-blocks.sh
|
||||
└── validate-all.sh
|
||||
```
|
||||
|
||||
## 设计原则
|
||||
|
||||
### 1. 单一职责
|
||||
|
||||
每个 skill 专注于一个特定任务:
|
||||
- `vitepress-api-doc` - 单文件 API 文档
|
||||
- `vitepress-guide` - 功能指南
|
||||
- `vitepress-tutorial` - 分步教程
|
||||
- `vitepress-batch-api` - 批量生成
|
||||
- `vitepress-validate` - 质量验证
|
||||
|
||||
### 2. 模块化设计
|
||||
|
||||
- 共享脚本放在 `_shared/scripts/`
|
||||
- 每个 skill 独立维护
|
||||
- 可以单独使用或组合使用
|
||||
|
||||
### 3. 基于源代码
|
||||
|
||||
- 仅使用 XML 注释,不添加 AI 补充
|
||||
- 保持文档与代码同步
|
||||
- 代码示例由 AI 自动生成
|
||||
|
||||
### 4. 质量保证
|
||||
|
||||
- 所有生成的文档都应通过验证
|
||||
- 遵循 VitePress 规范
|
||||
- 保持一致的文档风格
|
||||
|
||||
## 文档规范
|
||||
|
||||
### Frontmatter 格式
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: 文档标题
|
||||
description: 简短描述(1-2 句话)
|
||||
outline: deep # 可选
|
||||
---
|
||||
```
|
||||
|
||||
### 代码块标记
|
||||
|
||||
- C# 代码使用 `csharp`
|
||||
- Bash 脚本使用 `bash`
|
||||
- JSON 使用 `json`
|
||||
- YAML 使用 `yaml`
|
||||
|
||||
### 泛型符号转义
|
||||
|
||||
在正文中使用 HTML 实体:
|
||||
- `List<T>` → `List<T>`
|
||||
- 代码块内保持原样
|
||||
|
||||
### 中文标点符号
|
||||
|
||||
- 中文句子使用全角标点:,。!?
|
||||
- 英文句子使用半角标点:,.!?
|
||||
- 代码周围使用半角符号
|
||||
|
||||
## 共享脚本
|
||||
|
||||
### update-vitepress-nav.sh
|
||||
|
||||
更新 VitePress 侧边栏导航配置。
|
||||
|
||||
**用法**:
|
||||
```bash
|
||||
.claude/skills/_shared/scripts/update-vitepress-nav.sh <文件路径> <标题>
|
||||
```
|
||||
|
||||
### parse-csharp-xml.sh
|
||||
|
||||
解析 C# XML 文档注释。
|
||||
|
||||
**用法**:
|
||||
```bash
|
||||
.claude/skills/_shared/scripts/parse-csharp-xml.sh <C# 文件路径>
|
||||
```
|
||||
|
||||
### generate-examples.sh
|
||||
|
||||
生成代码示例。
|
||||
|
||||
**用法**:
|
||||
```bash
|
||||
.claude/skills/_shared/scripts/generate-examples.sh <类型名> <命名空间>
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 文档生成顺序
|
||||
|
||||
1. 先生成 API 文档(基础)
|
||||
2. 再生成功能指南(概念)
|
||||
3. 最后生成教程(实践)
|
||||
|
||||
### 2. 保持文档同步
|
||||
|
||||
- 修改代码后及时更新文档
|
||||
- 使用单文件生成更新特定文档
|
||||
- 定期批量验证所有文档
|
||||
|
||||
### 3. 质量控制
|
||||
|
||||
- 生成后立即验证
|
||||
- 修复所有错误和警告
|
||||
- 确保链接有效
|
||||
|
||||
### 4. 版本控制
|
||||
|
||||
- 将生成的文档提交到 Git
|
||||
- 在 PR 中包含文档更新
|
||||
- 保持文档与代码版本一致
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 问题:生成的文档缺少内容
|
||||
|
||||
**原因**:源代码缺少 XML 注释
|
||||
|
||||
**解决方案**:
|
||||
1. 在源代码中添加 XML 注释
|
||||
2. 重新生成文档
|
||||
|
||||
### 问题:验证失败
|
||||
|
||||
**原因**:文档格式不符合规范
|
||||
|
||||
**解决方案**:
|
||||
1. 查看验证错误信息
|
||||
2. 根据提示修复问题
|
||||
3. 重新验证
|
||||
|
||||
### 问题:链接损坏
|
||||
|
||||
**原因**:文件路径错误或文件不存在
|
||||
|
||||
**解决方案**:
|
||||
1. 检查链接的目标文件是否存在
|
||||
2. 修正文件路径
|
||||
3. 重新验证
|
||||
|
||||
### 问题:批量生成速度慢
|
||||
|
||||
**原因**:文件数量多
|
||||
|
||||
**解决方案**:
|
||||
1. 使用 `--parallel` 选项(如果支持)
|
||||
2. 分批生成
|
||||
3. 仅生成修改的文件
|
||||
|
||||
## 扩展开发
|
||||
|
||||
### 添加新 Skill
|
||||
|
||||
1. 在 `.claude/skills/` 下创建新目录
|
||||
2. 创建 `SKILL.md` 说明文档
|
||||
3. 创建必要的模板和脚本
|
||||
4. 在本 README 中添加说明
|
||||
|
||||
### 修改现有 Skill
|
||||
|
||||
1. 更新 `SKILL.md` 文档
|
||||
2. 修改模板或脚本
|
||||
3. 更新示例文档
|
||||
4. 测试修改后的功能
|
||||
|
||||
## 贡献指南
|
||||
|
||||
### 报告问题
|
||||
|
||||
在 GitHub Issues 中报告问题,包含:
|
||||
- 使用的 skill 名称
|
||||
- 输入参数
|
||||
- 预期结果
|
||||
- 实际结果
|
||||
- 错误信息
|
||||
|
||||
### 提交改进
|
||||
|
||||
1. Fork 项目
|
||||
2. 创建功能分支
|
||||
3. 提交修改
|
||||
4. 创建 Pull Request
|
||||
|
||||
## 版本历史
|
||||
|
||||
- v1.0.0 (2025-01-XX) - 初始版本
|
||||
- 5 个核心 skills
|
||||
- 3 个共享脚本
|
||||
- 完整的文档和示例
|
||||
|
||||
## 许可证
|
||||
|
||||
与 GFramework 项目保持一致。
|
||||
|
||||
## 联系方式
|
||||
|
||||
如有问题或建议,请通过以下方式联系:
|
||||
- GitHub Issues
|
||||
- 项目讨论区
|
||||
|
||||
---
|
||||
|
||||
**注意**:本 skills 系统专为 GFramework 项目设计,使用前请确保了解项目结构和文档规范。
|
||||
@ -1,205 +0,0 @@
|
||||
# GFramework 文档编写规范
|
||||
|
||||
## Markdown 语法规范
|
||||
|
||||
### 1. 泛型标记转义
|
||||
|
||||
在 Markdown 文档中,所有泛型标记必须转义,否则会被 VitePress 误认为 HTML 标签。
|
||||
|
||||
**错误示例**:
|
||||
```markdown
|
||||
`Option<T>` 是一个泛型类型
|
||||
`Result<TValue, TError>` 表示结果
|
||||
public class Repository<TData> { }
|
||||
```
|
||||
|
||||
**正确示例**:
|
||||
```markdown
|
||||
`Option<T>` 是一个泛型类型
|
||||
`Result<TValue, TError>` 表示结果
|
||||
public class Repository<TData> { }
|
||||
```
|
||||
|
||||
**常见泛型标记**:
|
||||
- `<T>` → `<T>`
|
||||
- `<TResult>` → `<TResult>`
|
||||
- `<TValue>` → `<TValue>`
|
||||
- `<TError>` → `<TError>`
|
||||
- `<TSaveData>` → `<TSaveData>`
|
||||
- `<TData>` → `<TData>`
|
||||
- `<TNode>` → `<TNode>`
|
||||
|
||||
### 2. HTML 标签转义
|
||||
|
||||
如果需要在文档中显示 HTML 标签,必须转义:
|
||||
- `<summary>` → `<summary>`
|
||||
- `<param>` → `<param>`
|
||||
- `<returns>` → `<returns>`
|
||||
|
||||
### 3. 链接验证
|
||||
|
||||
**内部链接规则**:
|
||||
- 使用相对路径: `/zh-CN/core/events`
|
||||
- 确保目标文件存在
|
||||
- 不要链接到尚未创建的页面
|
||||
|
||||
**已存在的文档路径**:
|
||||
|
||||
**Core 模块**:
|
||||
- `/zh-CN/core/architecture` - 架构系统
|
||||
- `/zh-CN/core/ioc` - IoC 容器
|
||||
- `/zh-CN/core/events` - 事件系统
|
||||
- `/zh-CN/core/command` - 命令系统
|
||||
- `/zh-CN/core/query` - 查询系统
|
||||
- `/zh-CN/core/model` - Model 系统
|
||||
- `/zh-CN/core/system` - System 系统
|
||||
- `/zh-CN/core/utility` - Utility 系统
|
||||
- `/zh-CN/core/controller` - Controller 系统
|
||||
- `/zh-CN/core/logging` - 日志系统
|
||||
- `/zh-CN/core/pool` - 对象池
|
||||
- `/zh-CN/core/property` - 可绑定属性
|
||||
- `/zh-CN/core/lifecycle` - 生命周期管理
|
||||
- `/zh-CN/core/coroutine` - 协程系统
|
||||
- `/zh-CN/core/resource` - 资源管理
|
||||
- `/zh-CN/core/state-machine` - 状态机
|
||||
- `/zh-CN/core/cqrs` - CQRS 与 Mediator
|
||||
- `/zh-CN/core/functional` - 函数式编程
|
||||
- `/zh-CN/core/pause` - 暂停管理
|
||||
- `/zh-CN/core/configuration` - 配置管理
|
||||
- `/zh-CN/core/ecs` - ECS 系统集成
|
||||
- `/zh-CN/core/extensions` - 扩展方法
|
||||
- `/zh-CN/core/rule` - 规则系统
|
||||
- `/zh-CN/core/environment` - 环境系统
|
||||
- `/zh-CN/core/context` - 上下文系统
|
||||
- `/zh-CN/core/async-initialization` - 异步初始化
|
||||
|
||||
**Game 模块**:
|
||||
- `/zh-CN/game/scene` - 场景系统
|
||||
- `/zh-CN/game/ui` - UI 系统
|
||||
- `/zh-CN/game/data` - 数据与存档
|
||||
- `/zh-CN/game/storage` - 存储系统
|
||||
- `/zh-CN/game/serialization` - 序列化系统
|
||||
- `/zh-CN/game/setting` - 设置系统
|
||||
|
||||
**Godot 模块**:
|
||||
- `/zh-CN/godot/architecture` - Godot 架构集成
|
||||
- `/zh-CN/godot/scene` - Godot 场景系统
|
||||
- `/zh-CN/godot/ui` - Godot UI 系统
|
||||
- `/zh-CN/godot/pool` - Godot 节点池
|
||||
- `/zh-CN/godot/resource` - Godot 资源仓储
|
||||
- `/zh-CN/godot/logging` - Godot 日志系统
|
||||
- `/zh-CN/godot/pause` - Godot 暂停处理
|
||||
- `/zh-CN/godot/extensions` - Godot 扩展
|
||||
- `/zh-CN/godot/coroutine` - Godot 协程
|
||||
- `/zh-CN/godot/signal` - Godot 信号
|
||||
- `/zh-CN/godot/storage` - Godot 存储
|
||||
|
||||
**教程**:
|
||||
- `/zh-CN/tutorials/coroutine-tutorial` - 协程系统教程
|
||||
- `/zh-CN/tutorials/state-machine-tutorial` - 状态机教程
|
||||
- `/zh-CN/tutorials/resource-management` - 资源管理教程
|
||||
- `/zh-CN/tutorials/save-system` - 存档系统教程
|
||||
- `/zh-CN/tutorials/godot-complete-project` - Godot 完整项目
|
||||
- `/zh-CN/tutorials/functional-programming` - 函数式编程实践
|
||||
- `/zh-CN/tutorials/pause-system` - 暂停系统实现
|
||||
- `/zh-CN/tutorials/data-migration` - 数据迁移实践
|
||||
- `/zh-CN/tutorials/godot-integration` - Godot 集成
|
||||
- `/zh-CN/tutorials/advanced-patterns` - 高级模式
|
||||
|
||||
**其他**:
|
||||
- `/zh-CN/getting-started/quick-start` - 快速开始
|
||||
- `/zh-CN/getting-started/installation` - 安装指南
|
||||
- `/zh-CN/best-practices/architecture-patterns` - 架构模式
|
||||
|
||||
**不存在的路径** (不要链接):
|
||||
- `/zh-CN/best-practices/performance` - 尚未创建
|
||||
- `/zh-CN/core/serializer` - 错误路径,应使用 `/zh-CN/game/serialization`
|
||||
|
||||
## 代码块规范
|
||||
|
||||
### 1. 代码块语言标识
|
||||
|
||||
始终指定代码块的语言:
|
||||
|
||||
```markdown
|
||||
\`\`\`csharp
|
||||
public class Example { }
|
||||
\`\`\`
|
||||
|
||||
\`\`\`bash
|
||||
npm install
|
||||
\`\`\`
|
||||
```
|
||||
|
||||
### 2. 代码注释
|
||||
|
||||
代码示例应包含中文注释:
|
||||
|
||||
```csharp
|
||||
// 创建玩家实体
|
||||
var player = new Player
|
||||
{
|
||||
Name = "玩家1", // 玩家名称
|
||||
Level = 1 // 初始等级
|
||||
};
|
||||
```
|
||||
|
||||
## Frontmatter 规范
|
||||
|
||||
每个文档必须包含正确的 frontmatter:
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: 文档标题
|
||||
description: 简短描述(1-2 句话)
|
||||
---
|
||||
```
|
||||
|
||||
## 文档结构规范
|
||||
|
||||
### 指南文档结构
|
||||
|
||||
1. 概述
|
||||
2. 核心概念
|
||||
3. 基本用法
|
||||
4. 高级用法
|
||||
5. 最佳实践
|
||||
6. 常见问题
|
||||
7. 相关文档
|
||||
|
||||
### 教程文档结构
|
||||
|
||||
1. 学习目标
|
||||
2. 前置条件
|
||||
3. 步骤 1-N (3-7 步)
|
||||
4. 完整代码
|
||||
5. 运行结果
|
||||
6. 下一步
|
||||
7. 相关文档
|
||||
|
||||
## 验证清单
|
||||
|
||||
生成文档后,必须检查:
|
||||
|
||||
- [ ] 所有泛型标记已转义 (`<T>` → `<T>`)
|
||||
- [ ] 所有内部链接指向存在的页面
|
||||
- [ ] Frontmatter 格式正确
|
||||
- [ ] 代码块指定了语言
|
||||
- [ ] 代码包含中文注释
|
||||
- [ ] 文档结构完整
|
||||
- [ ] 没有 HTML 标签错误
|
||||
|
||||
## 自动修复脚本
|
||||
|
||||
如果文档已生成,可以使用以下脚本修复常见问题:
|
||||
|
||||
```bash
|
||||
# 修复泛型标记
|
||||
sed -i 's/<T>/\<T\>/g' file.md
|
||||
sed -i 's/<TResult>/\<TResult\>/g' file.md
|
||||
sed -i 's/<TValue>/\<TValue\>/g' file.md
|
||||
sed -i 's/<TError>/\<TError\>/g' file.md
|
||||
|
||||
# 验证构建
|
||||
cd docs && bun run build
|
||||
```
|
||||
@ -1,84 +0,0 @@
|
||||
#!/bin/bash
|
||||
# 共享的模块配置
|
||||
# 用于统一管理 GFramework 项目的模块映射关系
|
||||
|
||||
# 根据模块名获取源代码目录
|
||||
get_source_dir() {
|
||||
local MODULE="$1"
|
||||
case "$MODULE" in
|
||||
Core)
|
||||
echo "GFramework.Core"
|
||||
;;
|
||||
Game)
|
||||
echo "GFramework.Game"
|
||||
;;
|
||||
Godot)
|
||||
echo "GFramework.Godot"
|
||||
;;
|
||||
SourceGenerators)
|
||||
echo "GFramework.SourceGenerators"
|
||||
;;
|
||||
*)
|
||||
echo ""
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# 根据模块名获取文档输出目录
|
||||
get_docs_dir() {
|
||||
local MODULE="$1"
|
||||
case "$MODULE" in
|
||||
Core)
|
||||
echo "docs/zh-CN/api-reference/core"
|
||||
;;
|
||||
Game)
|
||||
echo "docs/zh-CN/api-reference/game"
|
||||
;;
|
||||
Godot)
|
||||
echo "docs/zh-CN/api-reference/godot"
|
||||
;;
|
||||
SourceGenerators)
|
||||
echo "docs/zh-CN/api-reference/source-generators"
|
||||
;;
|
||||
*)
|
||||
echo ""
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# 根据命名空间推断模块名
|
||||
infer_module_from_namespace() {
|
||||
local NAMESPACE="$1"
|
||||
if [[ "$NAMESPACE" == GFramework.Core* ]]; then
|
||||
echo "Core"
|
||||
elif [[ "$NAMESPACE" == GFramework.Game* ]]; then
|
||||
echo "Game"
|
||||
elif [[ "$NAMESPACE" == GFramework.Godot* ]]; then
|
||||
echo "Godot"
|
||||
elif [[ "$NAMESPACE" == GFramework.SourceGenerators* ]]; then
|
||||
echo "SourceGenerators"
|
||||
else
|
||||
echo ""
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 获取所有可用模块列表
|
||||
get_all_modules() {
|
||||
echo "Core Game Godot SourceGenerators"
|
||||
}
|
||||
|
||||
# 验证模块名是否有效
|
||||
is_valid_module() {
|
||||
local MODULE="$1"
|
||||
case "$MODULE" in
|
||||
Core|Game|Godot|SourceGenerators)
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
@ -1,210 +0,0 @@
|
||||
# VitePress API 文档生成
|
||||
|
||||
为单个 C# 类、接口或枚举生成符合 VitePress 标准的 API 参考文档。
|
||||
|
||||
## 用途
|
||||
|
||||
此 skill 用于从 C# 源代码文件自动生成结构化的 API 文档,包括:
|
||||
- 类型概述和命名空间信息
|
||||
- 构造函数、方法、属性的详细说明
|
||||
- 基于 XML 文档注释的描述
|
||||
- 自动生成的使用示例
|
||||
- 相关类型的交叉引用
|
||||
|
||||
## 调用方式
|
||||
|
||||
```bash
|
||||
/vitepress-api-doc <C# 文件路径>
|
||||
```
|
||||
|
||||
**示例**:
|
||||
```bash
|
||||
/vitepress-api-doc GFramework.Core/architecture/Architecture.cs
|
||||
```
|
||||
|
||||
## 工作流程
|
||||
|
||||
1. **读取源代码文件**
|
||||
- 验证文件存在且为 C# 文件
|
||||
- 读取完整的源代码内容
|
||||
|
||||
2. **解析代码结构**
|
||||
- 提取命名空间、类名、访问修饰符
|
||||
- 识别类型(class/interface/enum/struct)
|
||||
- 解析继承关系和实现的接口
|
||||
- 提取所有公共成员(构造函数、方法、属性、事件、字段)
|
||||
|
||||
3. **提取 XML 文档注释**
|
||||
- 解析 `/// <summary>` 标签(类型和成员描述)
|
||||
- 解析 `/// <param>` 标签(参数说明)
|
||||
- 解析 `/// <returns>` 标签(返回值说明)
|
||||
- 解析 `/// <exception>` 标签(异常说明)
|
||||
- 解析 `/// <example>` 标签(示例代码)
|
||||
- 解析 `/// <see cref=""/>` 标签(交叉引用)
|
||||
|
||||
4. **生成 Markdown 文档**
|
||||
- 根据 `template.md` 填充内容
|
||||
- 转义泛型符号(`<T>` → `<T>`)
|
||||
- 生成使用示例(基于 API 签名)
|
||||
- 添加相关文档链接
|
||||
|
||||
5. **确定输出路径**
|
||||
- 根据命名空间确定模块(Core/Game/Godot/SourceGenerators)
|
||||
- 输出到 `docs/zh-CN/api-reference/<模块>/<类名>.md`
|
||||
|
||||
6. **更新 VitePress 配置**
|
||||
- 调用共享脚本 `update-vitepress-nav.sh`
|
||||
- 在侧边栏配置中添加新文档条目
|
||||
|
||||
7. **验证文档质量**
|
||||
- 检查 Frontmatter 格式
|
||||
- 验证内部链接
|
||||
- 确保代码块语法正确
|
||||
|
||||
## 输出规范
|
||||
|
||||
### Frontmatter 格式
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: 类名
|
||||
description: 从 XML <summary> 提取的简短描述
|
||||
outline: deep
|
||||
---
|
||||
```
|
||||
|
||||
### 文档结构
|
||||
|
||||
1. **标题**:使用类名作为一级标题
|
||||
2. **概述**:XML summary 内容
|
||||
3. **命名空间和程序集信息**
|
||||
4. **继承链**(如果适用)
|
||||
5. **构造函数**(如果有)
|
||||
6. **公共方法**(按字母顺序)
|
||||
7. **公共属性**(按字母顺序)
|
||||
8. **公共事件**(如果有)
|
||||
9. **使用示例**(自动生成)
|
||||
10. **另请参阅**(相关类型链接)
|
||||
|
||||
### 代码块格式
|
||||
|
||||
所有 C# 代码块必须使用:
|
||||
```markdown
|
||||
\`\`\`csharp
|
||||
// 代码内容
|
||||
\`\`\`
|
||||
```
|
||||
|
||||
### 泛型符号转义
|
||||
|
||||
- `List<T>` → `List<T>`
|
||||
- `Dictionary<K, V>` → `Dictionary<K, V>`
|
||||
- `IEnumerable<T>` → `IEnumerable<T>`
|
||||
|
||||
### 内部链接格式
|
||||
|
||||
- 相对路径:`[Architecture](./architecture.md)`
|
||||
- 绝对路径:`[Core 架构](/zh-CN/core/architecture)`
|
||||
- 锚点链接:`[构造函数](#构造函数)`
|
||||
|
||||
## 前置条件
|
||||
|
||||
1. 项目必须有 VitePress 配置文件(`docs/.vitepress/config.mts`)
|
||||
2. 目标 C# 文件必须存在且可读
|
||||
3. C# 文件必须包含 XML 文档注释(`///`)
|
||||
4. 文件必须包含至少一个公共类型
|
||||
|
||||
## 配置选项
|
||||
|
||||
### 自动检测模块
|
||||
|
||||
根据命名空间自动确定模块:
|
||||
- `GFramework.Core.*` → `core`
|
||||
- `GFramework.Game.*` → `game`
|
||||
- `GFramework.Godot.*` → `godot`
|
||||
- `GFramework.SourceGenerators.*` → `source-generators`
|
||||
|
||||
### 示例生成策略
|
||||
|
||||
- **基本用法**:最简单的 API 调用
|
||||
- **常见场景**:实际应用案例
|
||||
- **高级用法**:复杂配置(如果适用)
|
||||
|
||||
## 示例输出
|
||||
|
||||
参考 `examples/` 目录中的示例文档:
|
||||
- `class-example.md` - 类文档示例
|
||||
- `interface-example.md` - 接口文档示例
|
||||
- `enum-example.md` - 枚举文档示例
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **仅使用 XML 注释**:不对缺失的注释进行 AI 补充
|
||||
2. **仅提取公共成员**:忽略 `internal`、`private`、`protected` 成员
|
||||
3. **保持文档同步**:文档内容直接来源于代码,确保准确性
|
||||
4. **遵循项目风格**:参考现有文档的格式和术语
|
||||
|
||||
## 相关 Skills
|
||||
|
||||
- `/vitepress-validate` - 验证生成的文档质量
|
||||
- `/vitepress-batch-api` - 批量生成整个模块的 API 文档
|
||||
|
||||
## 技术细节
|
||||
|
||||
### XML 注释标签映射
|
||||
|
||||
| XML 标签 | Markdown 输出 |
|
||||
|---------|--------------|
|
||||
| `<summary>` | 概述章节 |
|
||||
| `<param name="x">` | 参数列表 |
|
||||
| `<returns>` | 返回值说明 |
|
||||
| `<exception cref="T">` | 异常列表 |
|
||||
| `<example>` | 示例代码块 |
|
||||
| `<see cref="T"/>` | 内部链接 |
|
||||
| `<remarks>` | 备注章节 |
|
||||
|
||||
### 成员签名格式
|
||||
|
||||
**方法**:
|
||||
```markdown
|
||||
### MethodName
|
||||
|
||||
描述内容
|
||||
|
||||
**签名**:
|
||||
\`\`\`csharp
|
||||
public ReturnType MethodName(ParamType param)
|
||||
\`\`\`
|
||||
|
||||
**参数**:
|
||||
- `param` (ParamType): 参数说明
|
||||
|
||||
**返回值**:
|
||||
- (ReturnType): 返回值说明
|
||||
```
|
||||
|
||||
**属性**:
|
||||
```markdown
|
||||
### PropertyName
|
||||
|
||||
描述内容
|
||||
|
||||
**类型**:`PropertyType`
|
||||
|
||||
**访问**:get / set
|
||||
```
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 问题:找不到 XML 注释
|
||||
**解决方案**:确保 C# 文件包含 `///` 注释,而不是 `//` 或 `/* */`
|
||||
|
||||
### 问题:泛型符号显示错误
|
||||
**解决方案**:VitePress 配置中已包含 `safeGenericEscapePlugin`,确保正确转义
|
||||
|
||||
### 问题:侧边栏未更新
|
||||
**解决方案**:检查 `update-vitepress-nav.sh` 脚本是否正确执行
|
||||
|
||||
## 版本历史
|
||||
|
||||
- v1.0.0 - 初始版本,支持类、接口、枚举的文档生成
|
||||
@ -1,252 +0,0 @@
|
||||
---
|
||||
title: Architecture
|
||||
description: 架构基类,提供系统、模型、工具等组件的注册与管理功能。专注于生命周期管理、初始化流程控制和架构阶段转换。
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# Architecture
|
||||
|
||||
## 概述
|
||||
|
||||
架构基类,提供系统、模型、工具等组件的注册与管理功能。专注于生命周期管理、初始化流程控制和架构阶段转换。
|
||||
|
||||
**命名空间**:`GFramework.Core.architecture`
|
||||
**程序集**:`GFramework.Core`
|
||||
**继承**:`Object` → `Architecture`
|
||||
**实现**:`IArchitecture`
|
||||
|
||||
## 构造函数
|
||||
|
||||
### Architecture
|
||||
|
||||
创建架构实例。
|
||||
|
||||
**签名**:
|
||||
```csharp
|
||||
public Architecture(
|
||||
IArchitectureConfiguration? configuration = null,
|
||||
IEnvironment? environment = null,
|
||||
IArchitectureServices? services = null,
|
||||
IArchitectureContext? context = null
|
||||
)
|
||||
```
|
||||
|
||||
**参数**:
|
||||
- `configuration` (IArchitectureConfiguration?): 架构配置对象,为 null 时使用默认配置
|
||||
- `environment` (IEnvironment?): 环境配置对象,为 null 时使用默认环境
|
||||
- `services` (IArchitectureServices?): 架构服务对象,为 null 时创建新实例
|
||||
- `context` (IArchitectureContext?): 架构上下文对象,为 null 时创建新实例
|
||||
|
||||
## 公共方法
|
||||
|
||||
### Initialize
|
||||
|
||||
同步初始化架构,阻塞当前线程直到初始化完成。
|
||||
|
||||
**签名**:
|
||||
```csharp
|
||||
public void Initialize()
|
||||
```
|
||||
|
||||
**特点**:
|
||||
- 阻塞式初始化
|
||||
- 适用于简单场景或控制台应用
|
||||
- 初始化失败时抛出异常并进入 `FailedInitialization` 阶段
|
||||
|
||||
### InitializeAsync
|
||||
|
||||
异步初始化架构,返回 Task 以便调用者可以等待初始化完成。
|
||||
|
||||
**签名**:
|
||||
```csharp
|
||||
public async Task InitializeAsync()
|
||||
```
|
||||
|
||||
**返回值**:
|
||||
- (Task): 表示异步初始化操作的任务
|
||||
|
||||
**特点**:
|
||||
- 非阻塞式初始化
|
||||
- 支持异步组件初始化
|
||||
- 适用于需要异步加载资源的场景
|
||||
|
||||
### InstallModule
|
||||
|
||||
安装架构模块,用于扩展架构功能。
|
||||
|
||||
**签名**:
|
||||
```csharp
|
||||
public IArchitectureModule InstallModule(IArchitectureModule module)
|
||||
```
|
||||
|
||||
**参数**:
|
||||
- `module` (IArchitectureModule): 要安装的模块实例
|
||||
|
||||
**返回值**:
|
||||
- (IArchitectureModule): 返回安装的模块实例
|
||||
|
||||
### RegisterSystem
|
||||
|
||||
注册系统组件到架构中。
|
||||
|
||||
**签名**:
|
||||
```csharp
|
||||
public void RegisterSystem<TSystem>(TSystem system) where TSystem : ISystem
|
||||
```
|
||||
|
||||
**类型参数**:
|
||||
- `TSystem`: 系统类型,必须实现 ISystem 接口
|
||||
|
||||
**参数**:
|
||||
- `system` (TSystem): 要注册的系统实例
|
||||
|
||||
### RegisterModel
|
||||
|
||||
注册模型组件到架构中。
|
||||
|
||||
**签名**:
|
||||
```csharp
|
||||
public void RegisterModel<TModel>(TModel model) where TModel : IModel
|
||||
```
|
||||
|
||||
**类型参数**:
|
||||
- `TModel`: 模型类型,必须实现 IModel 接口
|
||||
|
||||
**参数**:
|
||||
- `model` (TModel): 要注册的模型实例
|
||||
|
||||
### RegisterUtility
|
||||
|
||||
注册工具组件到架构中。
|
||||
|
||||
**签名**:
|
||||
```csharp
|
||||
public void RegisterUtility<TUtility>(TUtility utility) where TUtility : IUtility
|
||||
```
|
||||
|
||||
**类型参数**:
|
||||
- `TUtility`: 工具类型,必须实现 IUtility 接口
|
||||
|
||||
**参数**:
|
||||
- `utility` (TUtility): 要注册的工具实例
|
||||
|
||||
### SendCommand
|
||||
|
||||
发送并执行命令。
|
||||
|
||||
**签名**:
|
||||
```csharp
|
||||
public void SendCommand(ICommand command)
|
||||
```
|
||||
|
||||
**参数**:
|
||||
- `command` (ICommand): 要执行的命令实例
|
||||
|
||||
### SendCommand<TResult>
|
||||
|
||||
发送并执行带返回值的命令。
|
||||
|
||||
**签名**:
|
||||
```csharp
|
||||
public TResult SendCommand<TResult>(ICommand<TResult> command)
|
||||
```
|
||||
|
||||
**类型参数**:
|
||||
- `TResult`: 命令返回值类型
|
||||
|
||||
**参数**:
|
||||
- `command` (ICommand<TResult>): 要执行的命令实例
|
||||
|
||||
**返回值**:
|
||||
- (TResult): 命令执行结果
|
||||
|
||||
## 公共属性
|
||||
|
||||
### CurrentPhase
|
||||
|
||||
获取当前架构的阶段。
|
||||
|
||||
**类型**:`ArchitecturePhase`
|
||||
**访问**:get
|
||||
|
||||
### Context
|
||||
|
||||
获取架构上下文,提供对架构服务的访问。
|
||||
|
||||
**类型**:`IArchitectureContext`
|
||||
**访问**:get
|
||||
|
||||
### IsReady
|
||||
|
||||
获取一个布尔值,指示当前架构是否处于就绪状态。
|
||||
|
||||
**类型**:`bool`
|
||||
**访问**:get
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 基本用法
|
||||
|
||||
```csharp
|
||||
// 1. 定义你的架构(继承 Architecture 基类)
|
||||
public class GameArchitecture : Architecture
|
||||
{
|
||||
protected override void Init()
|
||||
{
|
||||
// 注册 Model
|
||||
RegisterModel(new PlayerModel());
|
||||
RegisterModel(new InventoryModel());
|
||||
|
||||
// 注册 System
|
||||
RegisterSystem(new GameplaySystem());
|
||||
RegisterSystem(new SaveSystem());
|
||||
|
||||
// 注册 Utility
|
||||
RegisterUtility(new StorageUtility());
|
||||
RegisterUtility(new TimeUtility());
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 创建并初始化架构
|
||||
var architecture = new GameArchitecture();
|
||||
architecture.Initialize();
|
||||
|
||||
// 3. 等待架构就绪
|
||||
await architecture.WaitUntilReadyAsync();
|
||||
```
|
||||
|
||||
### 异步初始化
|
||||
|
||||
```csharp
|
||||
var architecture = new GameArchitecture();
|
||||
await architecture.InitializeAsync(); // 异步等待初始化完成
|
||||
|
||||
// 检查架构是否已就绪
|
||||
if (architecture.IsReady)
|
||||
{
|
||||
Console.WriteLine("架构已就绪,可以开始游戏");
|
||||
}
|
||||
```
|
||||
|
||||
### 使用自定义配置
|
||||
|
||||
```csharp
|
||||
var config = new ArchitectureConfiguration
|
||||
{
|
||||
ArchitectureProperties = new ArchitectureProperties
|
||||
{
|
||||
StrictPhaseValidation = true, // 启用严格阶段验证
|
||||
AllowLateRegistration = false // 禁止就绪后注册组件
|
||||
}
|
||||
};
|
||||
|
||||
var architecture = new GameArchitecture(configuration: config);
|
||||
architecture.Initialize();
|
||||
```
|
||||
|
||||
## 另请参阅
|
||||
|
||||
- [IArchitecture](./iarchitecture.md) - 架构接口
|
||||
- [ArchitecturePhase](./architecture-phase.md) - 架构阶段枚举
|
||||
- [IArchitectureModule](./iarchitecture-module.md) - 架构模块接口
|
||||
- [架构组件](/zh-CN/core/architecture) - 架构使用指南
|
||||
@ -1,193 +0,0 @@
|
||||
---
|
||||
title: ArchitecturePhase
|
||||
description: 架构阶段枚举,定义了架构生命周期的各个阶段。
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# ArchitecturePhase
|
||||
|
||||
## 概述
|
||||
|
||||
架构阶段枚举,定义了架构生命周期的各个阶段。
|
||||
|
||||
**命名空间**:`GFramework.Core.Abstractions.enums`
|
||||
**程序集**:`GFramework.Core.Abstractions`
|
||||
**基础类型**:`Enum`
|
||||
|
||||
## 枚举值
|
||||
|
||||
### None
|
||||
|
||||
初始阶段,架构尚未开始初始化。
|
||||
|
||||
**值**:`0`
|
||||
|
||||
### BeforeUtilityInit
|
||||
|
||||
工具初始化前阶段。
|
||||
|
||||
**值**:`1`
|
||||
|
||||
### AfterUtilityInit
|
||||
|
||||
工具初始化后阶段。
|
||||
|
||||
**值**:`2`
|
||||
|
||||
### BeforeModelInit
|
||||
|
||||
模型初始化前阶段。
|
||||
|
||||
**值**:`3`
|
||||
|
||||
### AfterModelInit
|
||||
|
||||
模型初始化后阶段。
|
||||
|
||||
**值**:`4`
|
||||
|
||||
### BeforeSystemInit
|
||||
|
||||
系统初始化前阶段。
|
||||
|
||||
**值**:`5`
|
||||
|
||||
### AfterSystemInit
|
||||
|
||||
系统初始化后阶段。
|
||||
|
||||
**值**:`6`
|
||||
|
||||
### Ready
|
||||
|
||||
就绪状态,架构已完全初始化并可以使用。
|
||||
|
||||
**值**:`7`
|
||||
|
||||
### FailedInitialization
|
||||
|
||||
初始化失败状态。
|
||||
|
||||
**值**:`8`
|
||||
|
||||
### Destroying
|
||||
|
||||
正在销毁阶段。
|
||||
|
||||
**值**:`9`
|
||||
|
||||
### Destroyed
|
||||
|
||||
已销毁阶段。
|
||||
|
||||
**值**:`10`
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 检查架构阶段
|
||||
|
||||
```csharp
|
||||
var architecture = new GameArchitecture();
|
||||
architecture.Initialize();
|
||||
|
||||
// 检查架构是否已就绪
|
||||
if (architecture.CurrentPhase == ArchitecturePhase.Ready)
|
||||
{
|
||||
Console.WriteLine("架构已就绪,可以开始游戏");
|
||||
}
|
||||
```
|
||||
|
||||
### 监听阶段变化
|
||||
|
||||
```csharp
|
||||
public class PhaseMonitor : IArchitectureLifecycle
|
||||
{
|
||||
public void OnPhase(ArchitecturePhase phase, IArchitecture architecture)
|
||||
{
|
||||
switch (phase)
|
||||
{
|
||||
case ArchitecturePhase.BeforeUtilityInit:
|
||||
Console.WriteLine("开始初始化工具");
|
||||
break;
|
||||
case ArchitecturePhase.AfterUtilityInit:
|
||||
Console.WriteLine("工具初始化完成");
|
||||
break;
|
||||
case ArchitecturePhase.BeforeModelInit:
|
||||
Console.WriteLine("开始初始化模型");
|
||||
break;
|
||||
case ArchitecturePhase.AfterModelInit:
|
||||
Console.WriteLine("模型初始化完成");
|
||||
break;
|
||||
case ArchitecturePhase.BeforeSystemInit:
|
||||
Console.WriteLine("开始初始化系统");
|
||||
break;
|
||||
case ArchitecturePhase.AfterSystemInit:
|
||||
Console.WriteLine("系统初始化完成");
|
||||
break;
|
||||
case ArchitecturePhase.Ready:
|
||||
Console.WriteLine("架构就绪");
|
||||
break;
|
||||
case ArchitecturePhase.FailedInitialization:
|
||||
Console.WriteLine("架构初始化失败");
|
||||
break;
|
||||
case ArchitecturePhase.Destroying:
|
||||
Console.WriteLine("架构正在销毁");
|
||||
break;
|
||||
case ArchitecturePhase.Destroyed:
|
||||
Console.WriteLine("架构已销毁");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 注册监听器
|
||||
var architecture = new GameArchitecture();
|
||||
architecture.RegisterLifecycleHook(new PhaseMonitor());
|
||||
architecture.Initialize();
|
||||
```
|
||||
|
||||
### 等待特定阶段
|
||||
|
||||
```csharp
|
||||
public async Task WaitForReady(IArchitecture architecture)
|
||||
{
|
||||
while (architecture.CurrentPhase != ArchitecturePhase.Ready)
|
||||
{
|
||||
if (architecture.CurrentPhase == ArchitecturePhase.FailedInitialization)
|
||||
{
|
||||
throw new Exception("架构初始化失败");
|
||||
}
|
||||
|
||||
await Task.Delay(100);
|
||||
}
|
||||
|
||||
Console.WriteLine("架构已就绪");
|
||||
}
|
||||
```
|
||||
|
||||
## 阶段转换顺序
|
||||
|
||||
正常初始化流程的阶段转换顺序:
|
||||
|
||||
1. `None` → `BeforeUtilityInit`
|
||||
2. `BeforeUtilityInit` → `AfterUtilityInit`
|
||||
3. `AfterUtilityInit` → `BeforeModelInit`
|
||||
4. `BeforeModelInit` → `AfterModelInit`
|
||||
5. `AfterModelInit` → `BeforeSystemInit`
|
||||
6. `BeforeSystemInit` → `AfterSystemInit`
|
||||
7. `AfterSystemInit` → `Ready`
|
||||
|
||||
销毁流程的阶段转换顺序:
|
||||
|
||||
1. `Ready` → `Destroying`
|
||||
2. `Destroying` → `Destroyed`
|
||||
|
||||
异常流程:
|
||||
|
||||
- 任何阶段 → `FailedInitialization`(初始化过程中发生异常)
|
||||
|
||||
## 另请参阅
|
||||
|
||||
- [Architecture](./architecture.md) - 架构基类
|
||||
- [IArchitectureLifecycle](./iarchitecture-lifecycle.md) - 生命周期钩子接口
|
||||
- [架构组件](/zh-CN/core/architecture) - 架构使用指南
|
||||
@ -1,290 +0,0 @@
|
||||
---
|
||||
title: IArchitecture
|
||||
description: 架构接口,定义了框架的核心功能契约。
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# IArchitecture
|
||||
|
||||
## 概述
|
||||
|
||||
架构接口,定义了框架的核心功能契约。
|
||||
|
||||
**命名空间**:`GFramework.Core.Abstractions.architecture`
|
||||
**程序集**:`GFramework.Core.Abstractions`
|
||||
**实现类**:[Architecture](./architecture.md)
|
||||
|
||||
## 公共方法
|
||||
|
||||
### RegisterSystem<TSystem>
|
||||
|
||||
注册系统组件到架构中。
|
||||
|
||||
**签名**:
|
||||
```csharp
|
||||
void RegisterSystem<TSystem>(TSystem system) where TSystem : ISystem
|
||||
```
|
||||
|
||||
**类型参数**:
|
||||
- `TSystem`: 系统类型,必须实现 ISystem 接口
|
||||
|
||||
**参数**:
|
||||
- `system` (TSystem): 要注册的系统实例
|
||||
|
||||
### RegisterModel<TModel>
|
||||
|
||||
注册模型组件到架构中。
|
||||
|
||||
**签名**:
|
||||
```csharp
|
||||
void RegisterModel<TModel>(TModel model) where TModel : IModel
|
||||
```
|
||||
|
||||
**类型参数**:
|
||||
- `TModel`: 模型类型,必须实现 IModel 接口
|
||||
|
||||
**参数**:
|
||||
- `model` (TModel): 要注册的模型实例
|
||||
|
||||
### RegisterUtility<TUtility>
|
||||
|
||||
注册工具组件到架构中。
|
||||
|
||||
**签名**:
|
||||
```csharp
|
||||
void RegisterUtility<TUtility>(TUtility utility) where TUtility : IUtility
|
||||
```
|
||||
|
||||
**类型参数**:
|
||||
- `TUtility`: 工具类型,必须实现 IUtility 接口
|
||||
|
||||
**参数**:
|
||||
- `utility` (TUtility): 要注册的工具实例
|
||||
|
||||
### GetModel<T>
|
||||
|
||||
从容器中获取已注册的模型。
|
||||
|
||||
**签名**:
|
||||
```csharp
|
||||
T GetModel<T>() where T : class, IModel
|
||||
```
|
||||
|
||||
**类型参数**:
|
||||
- `T`: 模型类型
|
||||
|
||||
**返回值**:
|
||||
- (T): 模型实例
|
||||
|
||||
### GetSystem<T>
|
||||
|
||||
从容器中获取已注册的系统。
|
||||
|
||||
**签名**:
|
||||
```csharp
|
||||
T GetSystem<T>() where T : class, ISystem
|
||||
```
|
||||
|
||||
**类型参数**:
|
||||
- `T`: 系统类型
|
||||
|
||||
**返回值**:
|
||||
- (T): 系统实例
|
||||
|
||||
### GetUtility<T>
|
||||
|
||||
从容器中获取已注册的工具。
|
||||
|
||||
**签名**:
|
||||
```csharp
|
||||
T GetUtility<T>() where T : class, IUtility
|
||||
```
|
||||
|
||||
**类型参数**:
|
||||
- `T`: 工具类型
|
||||
|
||||
**返回值**:
|
||||
- (T): 工具实例
|
||||
|
||||
### SendCommand
|
||||
|
||||
发送并执行命令。
|
||||
|
||||
**签名**:
|
||||
```csharp
|
||||
void SendCommand(ICommand command)
|
||||
```
|
||||
|
||||
**参数**:
|
||||
- `command` (ICommand): 要执行的命令实例
|
||||
|
||||
### SendCommand<TResult>
|
||||
|
||||
发送并执行带返回值的命令。
|
||||
|
||||
**签名**:
|
||||
```csharp
|
||||
TResult SendCommand<TResult>(ICommand<TResult> command)
|
||||
```
|
||||
|
||||
**类型参数**:
|
||||
- `TResult`: 命令返回值类型
|
||||
|
||||
**参数**:
|
||||
- `command` (ICommand<TResult>): 要执行的命令实例
|
||||
|
||||
**返回值**:
|
||||
- (TResult): 命令执行结果
|
||||
|
||||
### SendQuery<TResult>
|
||||
|
||||
发送并执行查询。
|
||||
|
||||
**签名**:
|
||||
```csharp
|
||||
TResult SendQuery<TResult>(IQuery<TResult> query)
|
||||
```
|
||||
|
||||
**类型参数**:
|
||||
- `TResult`: 查询返回值类型
|
||||
|
||||
**参数**:
|
||||
- `query` (IQuery<TResult>): 要执行的查询实例
|
||||
|
||||
**返回值**:
|
||||
- (TResult): 查询结果
|
||||
|
||||
### SendEvent<T>
|
||||
|
||||
发送事件(无参数)。
|
||||
|
||||
**签名**:
|
||||
```csharp
|
||||
void SendEvent<T>() where T : new()
|
||||
```
|
||||
|
||||
**类型参数**:
|
||||
- `T`: 事件类型,必须有无参构造函数
|
||||
|
||||
### SendEvent<T>
|
||||
|
||||
发送事件(带参数)。
|
||||
|
||||
**签名**:
|
||||
```csharp
|
||||
void SendEvent<T>(T e)
|
||||
```
|
||||
|
||||
**类型参数**:
|
||||
- `T`: 事件类型
|
||||
|
||||
**参数**:
|
||||
- `e` (T): 事件实例
|
||||
|
||||
### RegisterEvent<T>
|
||||
|
||||
注册事件监听器。
|
||||
|
||||
**签名**:
|
||||
```csharp
|
||||
IUnRegister RegisterEvent<T>(Action<T> onEvent)
|
||||
```
|
||||
|
||||
**类型参数**:
|
||||
- `T`: 事件类型
|
||||
|
||||
**参数**:
|
||||
- `onEvent` (Action<T>): 事件处理回调
|
||||
|
||||
**返回值**:
|
||||
- (IUnRegister): 用于注销事件的对象
|
||||
|
||||
### UnRegisterEvent<T>
|
||||
|
||||
注销事件监听器。
|
||||
|
||||
**签名**:
|
||||
```csharp
|
||||
void UnRegisterEvent<T>(Action<T> onEvent)
|
||||
```
|
||||
|
||||
**类型参数**:
|
||||
- `T`: 事件类型
|
||||
|
||||
**参数**:
|
||||
- `onEvent` (Action<T>): 要注销的事件处理回调
|
||||
|
||||
## 公共属性
|
||||
|
||||
### CurrentPhase
|
||||
|
||||
获取当前架构的阶段。
|
||||
|
||||
**类型**:`ArchitecturePhase`
|
||||
**访问**:get
|
||||
|
||||
### Context
|
||||
|
||||
获取架构上下文。
|
||||
|
||||
**类型**:`IArchitectureContext`
|
||||
**访问**:get
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 在 Controller 中使用
|
||||
|
||||
```csharp
|
||||
public class GameController : IController
|
||||
{
|
||||
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
|
||||
|
||||
public void Start()
|
||||
{
|
||||
// 获取 Model
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
|
||||
// 发送命令
|
||||
this.SendCommand(new StartGameCommand());
|
||||
|
||||
// 发送查询
|
||||
var score = this.SendQuery(new GetScoreQuery());
|
||||
|
||||
// 注册事件
|
||||
this.RegisterEvent<PlayerDiedEvent>(OnPlayerDied);
|
||||
}
|
||||
|
||||
private void OnPlayerDied(PlayerDiedEvent e)
|
||||
{
|
||||
// 处理玩家死亡事件
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 实现自定义架构
|
||||
|
||||
```csharp
|
||||
public class GameArchitecture : Architecture
|
||||
{
|
||||
// 单例访问
|
||||
public static IArchitecture Interface { get; private set; }
|
||||
|
||||
protected override void Init()
|
||||
{
|
||||
Interface = this;
|
||||
|
||||
// 注册组件
|
||||
RegisterModel(new PlayerModel());
|
||||
RegisterSystem(new GameplaySystem());
|
||||
RegisterUtility(new StorageUtility());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 另请参阅
|
||||
|
||||
- [Architecture](./architecture.md) - 架构基类实现
|
||||
- [IModel](./imodel.md) - 模型接口
|
||||
- [ISystem](./isystem.md) - 系统接口
|
||||
- [IUtility](./iutility.md) - 工具接口
|
||||
- [架构组件](/zh-CN/core/architecture) - 架构使用指南
|
||||
@ -1,37 +0,0 @@
|
||||
---
|
||||
title: {{CLASS_NAME}}
|
||||
description: {{XML_SUMMARY}}
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# {{CLASS_NAME}}
|
||||
|
||||
## 概述
|
||||
|
||||
{{XML_SUMMARY}}
|
||||
|
||||
**命名空间**:`{{NAMESPACE}}`
|
||||
**程序集**:`{{ASSEMBLY}}`
|
||||
{{INHERITANCE_CHAIN}}
|
||||
|
||||
## 构造函数
|
||||
|
||||
{{CONSTRUCTORS}}
|
||||
|
||||
## 公共方法
|
||||
|
||||
{{PUBLIC_METHODS}}
|
||||
|
||||
## 公共属性
|
||||
|
||||
{{PUBLIC_PROPERTIES}}
|
||||
|
||||
{{PUBLIC_EVENTS}}
|
||||
|
||||
## 使用示例
|
||||
|
||||
{{AUTO_GENERATED_EXAMPLES}}
|
||||
|
||||
## 另请参阅
|
||||
|
||||
{{RELATED_TYPES}}
|
||||
@ -1,364 +0,0 @@
|
||||
# VitePress 批量 API 文档生成
|
||||
|
||||
为整个模块批量生成 API 参考文档,提高文档生成效率。
|
||||
|
||||
## 用途
|
||||
|
||||
此 skill 用于批量生成模块的 API 文档,适用于:
|
||||
- 初始化模块文档
|
||||
- 更新整个模块的文档
|
||||
- 为新模块快速生成文档
|
||||
- 重新生成所有 API 文档
|
||||
|
||||
## 调用方式
|
||||
|
||||
```bash
|
||||
/vitepress-batch-api <模块名>
|
||||
```
|
||||
|
||||
**示例**:
|
||||
```bash
|
||||
/vitepress-batch-api Core
|
||||
/vitepress-batch-api Game
|
||||
/vitepress-batch-api Godot
|
||||
/vitepress-batch-api SourceGenerators
|
||||
```
|
||||
|
||||
## 工作流程
|
||||
|
||||
1. **扫描模块目录**
|
||||
- 根据模块名确定源代码目录
|
||||
- 递归扫描所有 C# 文件
|
||||
|
||||
2. **过滤目标文件**
|
||||
- 仅包含公共类型(public class/interface/enum/struct)
|
||||
- 排除内部类型(internal)
|
||||
- 排除生成的代码(*.g.cs、*.Designer.cs)
|
||||
- 排除测试文件(*.Tests.cs)
|
||||
|
||||
3. **批量生成文档**
|
||||
- 为每个类型调用 `/vitepress-api-doc`
|
||||
- 显示进度信息
|
||||
- 收集生成结果
|
||||
|
||||
4. **生成模块索引页**
|
||||
- 创建 `index.md` 列出所有 API
|
||||
- 按类别分组(类、接口、枚举)
|
||||
- 添加简短描述
|
||||
|
||||
5. **批量更新导航**
|
||||
- 在 VitePress 配置中添加所有新文档
|
||||
- 保持字母顺序
|
||||
- 更新模块索引
|
||||
|
||||
6. **生成摘要报告**
|
||||
- 统计生成的文档数量
|
||||
- 列出成功和失败的文件
|
||||
- 提供验证建议
|
||||
|
||||
## 输出规范
|
||||
|
||||
### 模块索引页格式
|
||||
|
||||
```markdown
|
||||
---
|
||||
title: Core API 参考
|
||||
description: GFramework.Core 模块的 API 参考文档
|
||||
---
|
||||
|
||||
# Core API 参考
|
||||
|
||||
## 概述
|
||||
|
||||
GFramework.Core 是框架的核心模块,提供架构基础、依赖注入、事件系统等核心功能。
|
||||
|
||||
## 类
|
||||
|
||||
- [Architecture](./architecture.md) - 架构基类
|
||||
- [ArchitectureConfiguration](./architecture-configuration.md) - 架构配置
|
||||
- [IocContainer](./ioc-container.md) - IoC 容器
|
||||
|
||||
## 接口
|
||||
|
||||
- [IArchitecture](./iarchitecture.md) - 架构接口
|
||||
- [IModel](./imodel.md) - 模型接口
|
||||
- [ISystem](./isystem.md) - 系统接口
|
||||
|
||||
## 枚举
|
||||
|
||||
- [ArchitecturePhase](./architecture-phase.md) - 架构阶段
|
||||
```
|
||||
|
||||
### 目录结构
|
||||
|
||||
```
|
||||
docs/zh-CN/api-reference/
|
||||
├── core/
|
||||
│ ├── index.md # 模块索引
|
||||
│ ├── architecture.md
|
||||
│ ├── iarchitecture.md
|
||||
│ └── ...
|
||||
├── game/
|
||||
│ ├── index.md
|
||||
│ └── ...
|
||||
└── godot/
|
||||
├── index.md
|
||||
└── ...
|
||||
```
|
||||
|
||||
## 模块映射
|
||||
|
||||
### 源代码目录映射
|
||||
|
||||
| 模块名 | 源代码目录 | 输出目录 |
|
||||
|--------|-----------|---------|
|
||||
| Core | `GFramework.Core/` | `docs/zh-CN/api-reference/core/` |
|
||||
| Game | `GFramework.Game/` | `docs/zh-CN/api-reference/game/` |
|
||||
| Godot | `GFramework.Godot/` | `docs/zh-CN/api-reference/godot/` |
|
||||
| SourceGenerators | `GFramework.SourceGenerators/` | `docs/zh-CN/api-reference/source-generators/` |
|
||||
|
||||
### 命名空间映射
|
||||
|
||||
- `GFramework.Core.*` → Core 模块
|
||||
- `GFramework.Game.*` → Game 模块
|
||||
- `GFramework.Godot.*` → Godot 模块
|
||||
- `GFramework.SourceGenerators.*` → SourceGenerators 模块
|
||||
|
||||
## 过滤规则
|
||||
|
||||
### 包含的文件
|
||||
|
||||
- 公共类(public class)
|
||||
- 公共接口(public interface)
|
||||
- 公共枚举(public enum)
|
||||
- 公共结构体(public struct)
|
||||
|
||||
### 排除的文件
|
||||
|
||||
- 内部类型(internal)
|
||||
- 生成的代码(`*.g.cs`、`*.Designer.cs`)
|
||||
- 测试文件(`*.Tests.cs`、`*Test.cs`)
|
||||
- 临时文件(`*.tmp.cs`)
|
||||
- 编译器生成的文件(`AssemblyInfo.cs`)
|
||||
|
||||
### 排除的类型
|
||||
|
||||
- 编译器生成的类型(`<>c__DisplayClass`)
|
||||
- 匿名类型
|
||||
- 嵌套的私有类型
|
||||
|
||||
## 批量处理脚本
|
||||
|
||||
### batch-generate.sh
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# 批量生成 API 文档
|
||||
# 用法: batch-generate.sh <模块名>
|
||||
|
||||
set -e
|
||||
|
||||
MODULE="$1"
|
||||
|
||||
if [ -z "$MODULE" ]; then
|
||||
echo "用法: $0 <模块名>"
|
||||
echo "可用模块: Core, Game, Godot, SourceGenerators"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 确定源代码目录
|
||||
case "$MODULE" in
|
||||
Core)
|
||||
SOURCE_DIR="GFramework.Core"
|
||||
;;
|
||||
Game)
|
||||
SOURCE_DIR="GFramework.Game"
|
||||
;;
|
||||
Godot)
|
||||
SOURCE_DIR="GFramework.Godot"
|
||||
;;
|
||||
SourceGenerators)
|
||||
SOURCE_DIR="GFramework.SourceGenerators"
|
||||
;;
|
||||
*)
|
||||
echo "错误: 未知的模块: $MODULE"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ ! -d "$SOURCE_DIR" ]; then
|
||||
echo "错误: 源代码目录不存在: $SOURCE_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=========================================="
|
||||
echo "批量生成 $MODULE 模块的 API 文档"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# 查找所有 C# 文件
|
||||
FILES=$(find "$SOURCE_DIR" -name "*.cs" -type f \
|
||||
! -name "*.g.cs" \
|
||||
! -name "*.Designer.cs" \
|
||||
! -name "*Test.cs" \
|
||||
! -name "*.Tests.cs" \
|
||||
! -name "AssemblyInfo.cs")
|
||||
|
||||
FILE_COUNT=$(echo "$FILES" | wc -l)
|
||||
echo "找到 $FILE_COUNT 个文件"
|
||||
echo ""
|
||||
|
||||
GENERATED=0
|
||||
SKIPPED=0
|
||||
FAILED=0
|
||||
|
||||
for FILE in $FILES; do
|
||||
echo "处理: $FILE"
|
||||
|
||||
# 检查是否包含公共类型
|
||||
if ! grep -q "public \(class\|interface\|enum\|struct\)" "$FILE"; then
|
||||
echo " ⊘ 跳过(无公共类型)"
|
||||
SKIPPED=$((SKIPPED + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
# 调用 vitepress-api-doc(由 AI 执行)
|
||||
# /vitepress-api-doc "$FILE"
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo " ✓ 生成成功"
|
||||
GENERATED=$((GENERATED + 1))
|
||||
else
|
||||
echo " ✗ 生成失败"
|
||||
FAILED=$((FAILED + 1))
|
||||
fi
|
||||
|
||||
echo ""
|
||||
done
|
||||
|
||||
echo "=========================================="
|
||||
echo "批量生成完成"
|
||||
echo "=========================================="
|
||||
echo "总文件数: $FILE_COUNT"
|
||||
echo "生成成功: $GENERATED"
|
||||
echo "跳过: $SKIPPED"
|
||||
echo "失败: $FAILED"
|
||||
echo ""
|
||||
|
||||
if [ $FAILED -eq 0 ]; then
|
||||
echo "✓ 所有文档生成成功"
|
||||
exit 0
|
||||
else
|
||||
echo "✗ 部分文档生成失败"
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
## 配置选项
|
||||
|
||||
### 过滤选项
|
||||
|
||||
```bash
|
||||
# 包含内部类型
|
||||
/vitepress-batch-api Core --include-internal
|
||||
|
||||
# 包含生成的代码
|
||||
/vitepress-batch-api Core --include-generated
|
||||
|
||||
# 自定义过滤规则
|
||||
/vitepress-batch-api Core --exclude "*.Tests.cs" --exclude "*.g.cs"
|
||||
```
|
||||
|
||||
### 输出选项
|
||||
|
||||
```bash
|
||||
# 指定输出目录
|
||||
/vitepress-batch-api Core --output docs/zh-CN/api-reference/core/
|
||||
|
||||
# 覆盖现有文档
|
||||
/vitepress-batch-api Core --force
|
||||
|
||||
# 仅生成索引页
|
||||
/vitepress-batch-api Core --index-only
|
||||
```
|
||||
|
||||
### 并行处理
|
||||
|
||||
```bash
|
||||
# 并行生成(加快速度)
|
||||
/vitepress-batch-api Core --parallel 4
|
||||
```
|
||||
|
||||
## 进度显示
|
||||
|
||||
### 实时进度
|
||||
|
||||
```
|
||||
========================================
|
||||
批量生成 Core 模块的 API 文档
|
||||
========================================
|
||||
|
||||
找到 45 个文件
|
||||
|
||||
[1/45] 处理: GFramework.Core/architecture/Architecture.cs
|
||||
✓ 生成成功
|
||||
|
||||
[2/45] 处理: GFramework.Core/architecture/IArchitecture.cs
|
||||
✓ 生成成功
|
||||
|
||||
[3/45] 处理: GFramework.Core/command/Command.cs
|
||||
⊘ 跳过(无公共类型)
|
||||
|
||||
...
|
||||
|
||||
[45/45] 处理: GFramework.Core/utility/Utility.cs
|
||||
✓ 生成成功
|
||||
|
||||
========================================
|
||||
批量生成完成
|
||||
========================================
|
||||
总文件数: 45
|
||||
生成成功: 38
|
||||
跳过: 5
|
||||
失败: 2
|
||||
|
||||
✗ 部分文档生成失败
|
||||
|
||||
失败的文件:
|
||||
- GFramework.Core/internal/InternalClass.cs (缺少 XML 注释)
|
||||
- GFramework.Core/legacy/LegacyClass.cs (解析错误)
|
||||
```
|
||||
|
||||
## 前置条件
|
||||
|
||||
1. 模块源代码目录存在
|
||||
2. 源代码文件包含 XML 文档注释
|
||||
3. 有足够的磁盘空间存储生成的文档
|
||||
|
||||
## 相关 Skills
|
||||
|
||||
- `/vitepress-api-doc` - 单文件 API 文档生成
|
||||
- `/vitepress-validate` - 验证生成的文档
|
||||
- `/vitepress-guide` - 生成功能指南
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **首次生成**:使用批量生成快速创建所有文档
|
||||
2. **增量更新**:修改代码后使用单文件生成更新对应文档
|
||||
3. **定期验证**:批量生成后运行验证确保质量
|
||||
4. **版本控制**:将生成的文档提交到版本控制系统
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 问题:部分文件生成失败
|
||||
**解决方案**:检查失败文件的 XML 注释是否完整,手动修复后重新生成
|
||||
|
||||
### 问题:生成速度慢
|
||||
**解决方案**:使用 `--parallel` 选项启用并行处理
|
||||
|
||||
### 问题:生成的文档过多
|
||||
**解决方案**:使用过滤选项排除不需要的文件
|
||||
|
||||
## 版本历史
|
||||
|
||||
- v1.0.0 - 初始版本,支持批量 API 文档生成
|
||||
@ -1,81 +0,0 @@
|
||||
#!/bin/bash
|
||||
# 批量生成 API 文档
|
||||
# 用法: batch-generate.sh <模块名>
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=../../_shared/module-config.sh
|
||||
source "$SCRIPT_DIR/../../_shared/module-config.sh"
|
||||
|
||||
MODULE="$1"
|
||||
|
||||
if [ -z "$MODULE" ]; then
|
||||
echo "用法: $0 <模块名>"
|
||||
echo "可用模块: $(get_all_modules)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 验证模块名
|
||||
if ! is_valid_module "$MODULE"; then
|
||||
echo "错误: 未知的模块: $MODULE"
|
||||
echo "可用模块: $(get_all_modules)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 获取源代码目录
|
||||
SOURCE_DIR=$(get_source_dir "$MODULE")
|
||||
|
||||
if [ ! -d "$SOURCE_DIR" ]; then
|
||||
echo "错误: 源代码目录不存在: $SOURCE_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=========================================="
|
||||
echo "批量生成 $MODULE 模块的 API 文档"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# 查找所有 C# 文件
|
||||
mapfile -t FILES < <(find "$SOURCE_DIR" -name "*.cs" -type f \
|
||||
! -name "*.g.cs" \
|
||||
! -name "*.Designer.cs" \
|
||||
! -name "*Test.cs" \
|
||||
! -name "*.Tests.cs" \
|
||||
! -name "AssemblyInfo.cs")
|
||||
|
||||
FILE_COUNT=${#FILES[@]}
|
||||
echo "找到 $FILE_COUNT 个文件"
|
||||
echo ""
|
||||
|
||||
GENERATED=0
|
||||
SKIPPED=0
|
||||
FAILED=0
|
||||
|
||||
for FILE in "${FILES[@]}"; do
|
||||
echo "处理: $FILE"
|
||||
|
||||
# 检查是否包含公共类型
|
||||
if ! grep -q "public \(class\|interface\|enum\|struct\)" "$FILE"; then
|
||||
echo " ⊘ 跳过(无公共类型)"
|
||||
SKIPPED=$((SKIPPED + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
# 注意: 实际的文档生成由 AI 调用 /vitepress-api-doc 完成
|
||||
# 此脚本仅用于扫描和过滤文件
|
||||
|
||||
echo " → 待生成"
|
||||
GENERATED=$((GENERATED + 1))
|
||||
echo ""
|
||||
done
|
||||
|
||||
echo "=========================================="
|
||||
echo "扫描完成"
|
||||
echo "=========================================="
|
||||
echo "总文件数: $FILE_COUNT"
|
||||
echo "待生成: $GENERATED"
|
||||
echo "跳过: $SKIPPED"
|
||||
echo ""
|
||||
|
||||
exit 0
|
||||
@ -1,52 +0,0 @@
|
||||
---
|
||||
name: vitepress-doc-generator
|
||||
description: Generate standardized VitePress documentation from source code.
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
# Role
|
||||
|
||||
You are a technical documentation generator specialized in VitePress.
|
||||
|
||||
# Objective
|
||||
|
||||
Analyze the provided code context and generate a structured VitePress-compatible Markdown document.
|
||||
|
||||
# Output Requirements
|
||||
|
||||
1. Output MUST be valid Markdown only.
|
||||
2. Include frontmatter:
|
||||
|
||||
---
|
||||
title: <Module Name>
|
||||
outline: deep
|
||||
---
|
||||
|
||||
3. No explanations.
|
||||
4. No conversational text.
|
||||
5. No emoji.
|
||||
6. Use Chinese.
|
||||
7. Use structured headings.
|
||||
|
||||
# Required Structure
|
||||
|
||||
# 模块概述
|
||||
- 模块职责
|
||||
- 设计目标
|
||||
|
||||
# 核心类说明
|
||||
## 类名
|
||||
### 职责
|
||||
### 主要方法
|
||||
### 依赖关系
|
||||
|
||||
# 设计模式分析
|
||||
# 可扩展性说明
|
||||
# 使用示例(如适用)
|
||||
|
||||
# Self-Validation
|
||||
|
||||
Before returning output, verify:
|
||||
- Frontmatter exists
|
||||
- All required sections exist
|
||||
- No extra commentary text
|
||||
@ -1,256 +0,0 @@
|
||||
# VitePress 功能指南生成
|
||||
|
||||
生成功能模块的使用指南文档,包括概念说明、用法示例和最佳实践。
|
||||
|
||||
## 用途
|
||||
|
||||
此 skill 用于生成结构化的功能指南文档,适用于:
|
||||
- 核心功能模块的使用说明
|
||||
- 设计模式和架构概念
|
||||
- 系统功能的详细介绍
|
||||
- 最佳实践和常见问题
|
||||
|
||||
## 调用方式
|
||||
|
||||
```bash
|
||||
/vitepress-guide <主题> <目标模块>
|
||||
```
|
||||
|
||||
**示例**:
|
||||
```bash
|
||||
/vitepress-guide "事件系统" Core
|
||||
/vitepress-guide "IoC 容器" Core
|
||||
/vitepress-guide "Godot 节点扩展" Godot
|
||||
```
|
||||
|
||||
## 工作流程
|
||||
|
||||
1. **收集需求**
|
||||
- 询问用户指南主题
|
||||
- 确定目标受众(初学者/进阶/专家)
|
||||
- 了解重点内容(概念/用法/最佳实践)
|
||||
|
||||
2. **搜索相关资源**
|
||||
- 搜索相关代码文件
|
||||
- 查找现有文档
|
||||
- 识别相关类型和接口
|
||||
|
||||
3. **生成指南结构**
|
||||
- 根据 `template.md` 创建文档框架
|
||||
- 填充概述和核心概念
|
||||
- 添加基本用法和高级用法
|
||||
- 补充最佳实践和常见问题
|
||||
|
||||
4. **生成代码示例**
|
||||
- 基本用法示例(最简单的场景)
|
||||
- 常见场景示例(实际应用)
|
||||
- 高级用法示例(复杂配置)
|
||||
|
||||
5. **确定输出路径**
|
||||
- 保存到 `docs/zh-CN/<模块>/`
|
||||
- 文件名使用小写加连字符(如 `event-system.md`)
|
||||
|
||||
6. **更新导航配置**
|
||||
- 在 VitePress 侧边栏中添加新指南
|
||||
|
||||
## 输出规范
|
||||
|
||||
### Frontmatter 格式
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: 指南标题
|
||||
description: 简短描述(1-2 句话)
|
||||
---
|
||||
```
|
||||
|
||||
### 文档结构
|
||||
|
||||
1. **概述**:功能的简介和用途
|
||||
2. **核心概念**:关键概念和术语解释
|
||||
3. **基本用法**:最简单的使用方式
|
||||
4. **高级用法**:复杂场景和配置
|
||||
5. **最佳实践**:推荐的使用方式
|
||||
6. **常见问题**:FAQ 和故障排除
|
||||
|
||||
### 章节示例
|
||||
|
||||
**概述**:
|
||||
```markdown
|
||||
## 概述
|
||||
|
||||
事件系统提供了一种松耦合的组件间通信机制。通过事件,不同的组件可以在不直接引用彼此的情况下进行交互。
|
||||
|
||||
**主要特性**:
|
||||
- 类型安全的事件
|
||||
- 自动内存管理
|
||||
- 支持事件优先级
|
||||
- 线程安全
|
||||
```
|
||||
|
||||
**核心概念**:
|
||||
```markdown
|
||||
## 核心概念
|
||||
|
||||
### 事件类型
|
||||
|
||||
事件是一个普通的 C# 类,用于携带事件数据:
|
||||
|
||||
\`\`\`csharp
|
||||
public class PlayerDiedEvent
|
||||
{
|
||||
public int PlayerId { get; set; }
|
||||
public string Reason { get; set; }
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
### 事件发送
|
||||
|
||||
通过架构发送事件:
|
||||
|
||||
\`\`\`csharp
|
||||
this.SendEvent(new PlayerDiedEvent
|
||||
{
|
||||
PlayerId = 1,
|
||||
Reason = "Fall damage"
|
||||
});
|
||||
\`\`\`
|
||||
|
||||
### 事件监听
|
||||
|
||||
注册事件监听器:
|
||||
|
||||
\`\`\`csharp
|
||||
this.RegisterEvent<PlayerDiedEvent>(OnPlayerDied);
|
||||
\`\`\`
|
||||
```
|
||||
|
||||
## 模板变量
|
||||
|
||||
- `{{GUIDE_TITLE}}` - 指南标题
|
||||
- `{{GUIDE_DESCRIPTION}}` - 简短描述
|
||||
- `{{OVERVIEW}}` - 概述内容
|
||||
- `{{CORE_CONCEPTS}}` - 核心概念
|
||||
- `{{BASIC_USAGE}}` - 基本用法
|
||||
- `{{ADVANCED_USAGE}}` - 高级用法
|
||||
- `{{BEST_PRACTICES}}` - 最佳实践
|
||||
- `{{FAQ}}` - 常见问题
|
||||
|
||||
## 示例输出
|
||||
|
||||
参考 `examples/guide-example.md`,该示例基于现有的 IoC 容器文档创建。
|
||||
|
||||
## 内容要求
|
||||
|
||||
### 概述部分
|
||||
- 1-2 段简介
|
||||
- 列出主要特性(3-5 个)
|
||||
- 说明适用场景
|
||||
|
||||
### 核心概念部分
|
||||
- 使用三级标题(###)分隔不同概念
|
||||
- 每个概念包含简短说明和代码示例
|
||||
- 解释关键术语
|
||||
|
||||
### 基本用法部分
|
||||
- 提供完整的可运行示例
|
||||
- 从最简单的场景开始
|
||||
- 逐步增加复杂度
|
||||
- 包含必要的 using 语句
|
||||
|
||||
### 高级用法部分
|
||||
- 展示复杂场景
|
||||
- 说明配置选项
|
||||
- 提供性能优化建议
|
||||
|
||||
### 最佳实践部分
|
||||
- 使用编号列表
|
||||
- 每条实践包含简短说明
|
||||
- 提供正反示例(✓ 推荐 / ✗ 不推荐)
|
||||
|
||||
### 常见问题部分
|
||||
- 使用问答格式
|
||||
- 提供具体的解决方案
|
||||
- 包含相关链接
|
||||
|
||||
## 写作风格
|
||||
|
||||
### 语气
|
||||
- 友好、专业
|
||||
- 使用第二人称("你")
|
||||
- 避免过于技术化的术语
|
||||
|
||||
### 代码示例
|
||||
- 完整且可运行
|
||||
- 包含注释说明关键步骤
|
||||
- 使用有意义的变量名
|
||||
- 遵循项目代码风格
|
||||
|
||||
### 格式
|
||||
- 使用 Markdown 标准格式
|
||||
- 代码块使用语法高亮
|
||||
- 重要内容使用粗体或引用块
|
||||
- 适当使用列表和表格
|
||||
|
||||
## 配置选项
|
||||
|
||||
### 目标受众
|
||||
|
||||
```bash
|
||||
# 初学者(更多解释,简单示例)
|
||||
/vitepress-guide "事件系统" Core --audience beginner
|
||||
|
||||
# 进阶(平衡解释和示例)
|
||||
/vitepress-guide "事件系统" Core --audience intermediate
|
||||
|
||||
# 专家(简洁说明,复杂示例)
|
||||
/vitepress-guide "事件系统" Core --audience expert
|
||||
```
|
||||
|
||||
### 重点内容
|
||||
|
||||
```bash
|
||||
# 侧重概念
|
||||
/vitepress-guide "事件系统" Core --focus concepts
|
||||
|
||||
# 侧重用法
|
||||
/vitepress-guide "事件系统" Core --focus usage
|
||||
|
||||
# 侧重最佳实践
|
||||
/vitepress-guide "事件系统" Core --focus best-practices
|
||||
```
|
||||
|
||||
## 前置条件
|
||||
|
||||
1. 了解指南主题的基本概念
|
||||
2. 能够访问相关代码文件
|
||||
3. 了解项目的代码风格和术语
|
||||
|
||||
## 相关 Skills
|
||||
|
||||
- `/vitepress-api-doc` - 生成 API 参考文档
|
||||
- `/vitepress-tutorial` - 生成分步教程
|
||||
- `/vitepress-validate` - 验证生成的文档
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **先搜索后编写**:查看现有文档和代码,保持一致性
|
||||
2. **使用真实示例**:基于项目实际代码创建示例
|
||||
3. **保持简洁**:每个章节聚焦一个主题
|
||||
4. **提供完整代码**:确保示例可以直接运行
|
||||
5. **添加交叉引用**:链接到相关的 API 文档和其他指南
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 问题:不确定指南应该包含哪些内容
|
||||
**解决方案**:参考 `examples/guide-example.md` 和现有的指南文档
|
||||
|
||||
### 问题:代码示例过于复杂
|
||||
**解决方案**:将复杂示例拆分为多个简单示例,逐步增加复杂度
|
||||
|
||||
### 问题:概念解释不清晰
|
||||
**解决方案**:使用类比、图表或分步说明来辅助解释
|
||||
|
||||
## 版本历史
|
||||
|
||||
- v1.0.0 - 初始版本,支持功能指南生成
|
||||
@ -1,283 +0,0 @@
|
||||
---
|
||||
title: IoC 容器使用指南
|
||||
description: IoC(控制反转)容器提供了轻量级的依赖注入功能,用于管理框架中各种组件的注册和获取。
|
||||
---
|
||||
|
||||
# IoC 容器使用指南
|
||||
|
||||
## 概述
|
||||
|
||||
IoC(Inversion of Control,控制反转)包提供了一个轻量级的依赖注入容器,用于管理框架中各种组件的注册和获取。通过 IoC 容器,可以实现组件间的解耦,便于测试和维护。
|
||||
|
||||
IoC 容器是 GFramework 架构的核心组件之一,为整个框架提供依赖管理和组件解析服务。
|
||||
|
||||
**主要特性**:
|
||||
- 类型安全的依赖管理
|
||||
- 支持单例和多实例注册
|
||||
- 线程安全操作
|
||||
- 容器冻结保护
|
||||
- 自动接口注册
|
||||
|
||||
## 核心概念
|
||||
|
||||
### 依赖注入
|
||||
|
||||
依赖注入是一种设计模式,通过容器管理对象的创建和依赖关系,而不是在代码中直接创建对象。
|
||||
|
||||
```csharp
|
||||
// 不使用依赖注入
|
||||
public class GameController
|
||||
{
|
||||
private PlayerModel model = new PlayerModel(); // 硬编码依赖
|
||||
}
|
||||
|
||||
// 使用依赖注入
|
||||
public class GameController : IController
|
||||
{
|
||||
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
|
||||
|
||||
public void Start()
|
||||
{
|
||||
var model = this.GetModel<PlayerModel>(); // 从容器获取
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 容器注册
|
||||
|
||||
在架构初始化时,将组件注册到容器中:
|
||||
|
||||
```csharp
|
||||
public class GameArchitecture : Architecture
|
||||
{
|
||||
protected override void Init()
|
||||
{
|
||||
// 注册 Model
|
||||
RegisterModel(new PlayerModel());
|
||||
|
||||
// 注册 System
|
||||
RegisterSystem(new GameplaySystem());
|
||||
|
||||
// 注册 Utility
|
||||
RegisterUtility(new StorageUtility());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 容器解析
|
||||
|
||||
通过扩展方法从容器中获取已注册的组件:
|
||||
|
||||
```csharp
|
||||
// 在 Controller 中
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
var gameplaySystem = this.GetSystem<GameplaySystem>();
|
||||
var storageUtility = this.GetUtility<StorageUtility>();
|
||||
```
|
||||
|
||||
## 基本用法
|
||||
|
||||
### 注册组件
|
||||
|
||||
```csharp
|
||||
var container = new IocContainer();
|
||||
|
||||
// 注册单例(一个类型只能有一个实例)
|
||||
container.RegisterSingleton<IPlayerModel>(new PlayerModel());
|
||||
|
||||
// 注册多实例(一个类型可以有多个实例)
|
||||
container.RegisterPlurality<IEnemy>(new Goblin());
|
||||
container.RegisterPlurality<IEnemy>(new Orc());
|
||||
container.RegisterPlurality<IEnemy>(new Dragon());
|
||||
```
|
||||
|
||||
### 获取组件
|
||||
|
||||
```csharp
|
||||
// 获取单例
|
||||
var playerModel = container.Get<IPlayerModel>();
|
||||
|
||||
// 获取多实例集合
|
||||
var enemies = container.GetAll<IEnemy>(); // 返回 List<IEnemy>
|
||||
```
|
||||
|
||||
### 在架构中使用
|
||||
|
||||
```csharp
|
||||
public class GameArchitecture : Architecture
|
||||
{
|
||||
protected override void Init()
|
||||
{
|
||||
// 注册组件
|
||||
RegisterModel(new PlayerModel());
|
||||
RegisterModel(new InventoryModel());
|
||||
RegisterSystem(new GameplaySystem());
|
||||
}
|
||||
}
|
||||
|
||||
// 在 Controller 中使用
|
||||
public class GameController : IController
|
||||
{
|
||||
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
|
||||
|
||||
public void Start()
|
||||
{
|
||||
// 通过扩展方法获取组件
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
var inventoryModel = this.GetModel<InventoryModel>();
|
||||
var gameplaySystem = this.GetSystem<GameplaySystem>();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 高级用法
|
||||
|
||||
### 容器冻结
|
||||
|
||||
容器在架构初始化完成后会被冻结,防止运行时修改:
|
||||
|
||||
```csharp
|
||||
var container = new IocContainer();
|
||||
container.Register<IPlayerModel>(new PlayerModel());
|
||||
|
||||
// 冻结容器
|
||||
container.Freeze();
|
||||
|
||||
// 以下操作会抛出 InvalidOperationException
|
||||
// container.Register<IGameSystem>(new GameSystem());
|
||||
```
|
||||
|
||||
### 多实例管理
|
||||
|
||||
```csharp
|
||||
// 注册多个同类型实例
|
||||
container.RegisterPlurality<IWeapon>(new Sword());
|
||||
container.RegisterPlurality<IWeapon>(new Bow());
|
||||
container.RegisterPlurality<IWeapon>(new Staff());
|
||||
|
||||
// 获取所有实例
|
||||
var allWeapons = container.GetAll<IWeapon>();
|
||||
foreach (var weapon in allWeapons)
|
||||
{
|
||||
weapon.Attack();
|
||||
}
|
||||
```
|
||||
|
||||
### 接口自动注册
|
||||
|
||||
注册实例时,容器会自动将其注册到所有实现的接口:
|
||||
|
||||
```csharp
|
||||
public class PlayerModel : IModel, IPlayerModel, IDisposable
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
// 注册实例
|
||||
container.Register<PlayerModel>(new PlayerModel());
|
||||
|
||||
// 可以通过任何接口获取
|
||||
var model1 = container.Get<IModel>();
|
||||
var model2 = container.Get<IPlayerModel>();
|
||||
var model3 = container.Get<IDisposable>();
|
||||
// 以上三个变量指向同一个实例
|
||||
```
|
||||
|
||||
### 线程安全操作
|
||||
|
||||
容器的所有操作都是线程安全的:
|
||||
|
||||
```csharp
|
||||
// 多线程环境下安全使用
|
||||
Parallel.For(0, 100, i =>
|
||||
{
|
||||
var model = container.Get<IPlayerModel>();
|
||||
model.DoSomething();
|
||||
});
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **使用接口注册**:优先使用接口类型注册,而不是具体类型
|
||||
```csharp
|
||||
✓ container.Register<IPlayerModel>(new PlayerModel());
|
||||
✗ container.Register<PlayerModel>(new PlayerModel());
|
||||
```
|
||||
|
||||
2. **单例 vs 多实例**:根据需求选择合适的注册方式
|
||||
- 单例:全局唯一的服务(如配置、管理器)
|
||||
- 多实例:可以有多个实例的对象(如敌人、道具)
|
||||
|
||||
3. **避免循环依赖**:组件之间不应该相互依赖
|
||||
```csharp
|
||||
✗ System A 依赖 System B,System B 又依赖 System A
|
||||
✓ 使用事件系统进行通信,避免直接依赖
|
||||
```
|
||||
|
||||
4. **在 Init 中注册**:所有组件应该在架构的 `Init()` 方法中注册
|
||||
```csharp
|
||||
protected override void Init()
|
||||
{
|
||||
// 在这里注册所有组件
|
||||
RegisterModel(new PlayerModel());
|
||||
RegisterSystem(new GameplaySystem());
|
||||
}
|
||||
```
|
||||
|
||||
5. **使用扩展方法**:通过扩展方法获取组件,代码更简洁
|
||||
```csharp
|
||||
✓ var model = this.GetModel<PlayerModel>();
|
||||
✗ var model = this.GetArchitecture().GetModel<PlayerModel>();
|
||||
```
|
||||
|
||||
6. **不要在运行时注册**:容器冻结后不应该再注册新组件
|
||||
```csharp
|
||||
✗ 在游戏运行时动态注册组件
|
||||
✓ 在架构初始化时注册所有需要的组件
|
||||
```
|
||||
|
||||
## 常见问题
|
||||
|
||||
### 问题:如何判断使用单例还是多实例?
|
||||
|
||||
**解答**:
|
||||
- 使用单例(`RegisterSingleton`):全局唯一的服务,如 PlayerModel、GameConfiguration
|
||||
- 使用多实例(`RegisterPlurality`):可以有多个实例的对象,如 Enemy、Weapon
|
||||
|
||||
### 问题:容器冻结后如何添加新组件?
|
||||
|
||||
**解答**:
|
||||
容器冻结是为了保护架构稳定性。如果需要动态添加组件,应该:
|
||||
1. 在架构初始化时预先注册所有可能需要的组件
|
||||
2. 使用对象池模式管理动态对象
|
||||
3. 考虑使用工厂模式创建临时对象
|
||||
|
||||
### 问题:如何处理组件的生命周期?
|
||||
|
||||
**解答**:
|
||||
- 实现 `IDisposable` 接口的组件会在架构销毁时自动释放
|
||||
- 架构会按注册的逆序销毁组件
|
||||
- 不需要手动管理组件的生命周期
|
||||
|
||||
### 问题:可以在容器中注册值类型吗?
|
||||
|
||||
**解答**:
|
||||
可以,但会发生装箱。建议将值类型包装在类中:
|
||||
```csharp
|
||||
// 不推荐
|
||||
container.Register<int>(42);
|
||||
|
||||
// 推荐
|
||||
public class GameConfig
|
||||
{
|
||||
public int MaxPlayers { get; set; } = 42;
|
||||
}
|
||||
container.Register<GameConfig>(new GameConfig());
|
||||
```
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [架构组件](/zh-CN/core/architecture) - 架构基础
|
||||
- [Model 层](/zh-CN/core/model) - 数据模型
|
||||
- [System 层](/zh-CN/core/system) - 业务系统
|
||||
- [Utility 工具类](/zh-CN/core/utility) - 工具类
|
||||
@ -1,34 +0,0 @@
|
||||
---
|
||||
title: {{GUIDE_TITLE}}
|
||||
description: {{GUIDE_DESCRIPTION}}
|
||||
---
|
||||
|
||||
# {{GUIDE_TITLE}}
|
||||
|
||||
## 概述
|
||||
|
||||
{{OVERVIEW}}
|
||||
|
||||
## 核心概念
|
||||
|
||||
{{CORE_CONCEPTS}}
|
||||
|
||||
## 基本用法
|
||||
|
||||
{{BASIC_USAGE}}
|
||||
|
||||
## 高级用法
|
||||
|
||||
{{ADVANCED_USAGE}}
|
||||
|
||||
## 最佳实践
|
||||
|
||||
{{BEST_PRACTICES}}
|
||||
|
||||
## 常见问题
|
||||
|
||||
{{FAQ}}
|
||||
|
||||
## 相关文档
|
||||
|
||||
{{RELATED_DOCS}}
|
||||
@ -1,253 +0,0 @@
|
||||
# VitePress 教程生成
|
||||
|
||||
生成分步教程文档,适合初学者学习框架功能。
|
||||
|
||||
## 用途
|
||||
|
||||
此 skill 用于生成结构化的分步教程,适用于:
|
||||
- 框架入门教程
|
||||
- 功能实现教程
|
||||
- 最佳实践演示
|
||||
- 问题解决方案
|
||||
|
||||
## 调用方式
|
||||
|
||||
```bash
|
||||
/vitepress-tutorial <教程主题>
|
||||
```
|
||||
|
||||
**示例**:
|
||||
```bash
|
||||
/vitepress-tutorial "创建第一个 System"
|
||||
/vitepress-tutorial "实现自定义命令"
|
||||
/vitepress-tutorial "使用事件系统"
|
||||
```
|
||||
|
||||
## 工作流程
|
||||
|
||||
1. **收集需求**
|
||||
- 询问用户教程主题
|
||||
- 确定学习目标
|
||||
- 了解前置知识要求
|
||||
|
||||
2. **设计教程步骤**
|
||||
- 将任务分解为 3-7 个步骤
|
||||
- 每步聚焦一个具体任务
|
||||
- 确保步骤之间逻辑连贯
|
||||
|
||||
3. **生成教程内容**
|
||||
- 根据 `template.md` 创建文档框架
|
||||
- 为每步编写详细说明和代码
|
||||
- 添加完整的可运行代码
|
||||
- 说明预期结果
|
||||
|
||||
4. **确定输出路径**
|
||||
- 保存到 `docs/zh-CN/tutorials/`
|
||||
- 文件名使用小写加连字符
|
||||
|
||||
5. **更新导航配置**
|
||||
- 在 VitePress 侧边栏中添加新教程
|
||||
|
||||
## 输出规范
|
||||
|
||||
### Frontmatter 格式
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: 教程标题
|
||||
description: 简短描述(1 句话说明学习内容)
|
||||
---
|
||||
```
|
||||
|
||||
### 文档结构
|
||||
|
||||
1. **学习目标**:完成教程后能够掌握的技能
|
||||
2. **前置条件**:需要的前置知识和环境
|
||||
3. **步骤 1-N**:分步说明(3-7 步)
|
||||
4. **完整代码**:汇总所有代码
|
||||
5. **运行结果**:预期输出和效果
|
||||
6. **下一步**:后续学习建议
|
||||
|
||||
### 步骤格式
|
||||
|
||||
每个步骤应包含:
|
||||
- 步骤标题(简短、动词开头)
|
||||
- 步骤说明(为什么要这样做)
|
||||
- 代码示例(完整且可运行)
|
||||
- 代码解释(关键部分的说明)
|
||||
|
||||
**示例**:
|
||||
```markdown
|
||||
## 步骤 1:创建 Model 类
|
||||
|
||||
首先,我们需要创建一个 Model 来存储玩家数据。Model 负责管理应用的数据和状态。
|
||||
|
||||
\`\`\`csharp
|
||||
using GFramework.Core.Abstractions.model;
|
||||
using GFramework.Core.Abstractions.property;
|
||||
|
||||
public class PlayerModel : IModel
|
||||
{
|
||||
// 玩家名称(可绑定属性)
|
||||
public BindableProperty<string> Name { get; } = new("Player");
|
||||
|
||||
// 玩家生命值
|
||||
public BindableProperty<int> Health { get; } = new(100);
|
||||
|
||||
// 玩家金币
|
||||
public BindableProperty<int> Gold { get; } = new(0);
|
||||
|
||||
public void Init() { }
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
**代码说明**:
|
||||
- `BindableProperty<T>` 是可绑定属性,值变化时会自动通知监听者
|
||||
- `Init()` 方法在 Model 注册到架构时被调用
|
||||
- 使用属性初始化器设置默认值
|
||||
```
|
||||
|
||||
## 模板变量
|
||||
|
||||
- `{{TUTORIAL_TITLE}}` - 教程标题
|
||||
- `{{TUTORIAL_DESCRIPTION}}` - 简短描述
|
||||
- `{{LEARNING_OBJECTIVES}}` - 学习目标
|
||||
- `{{PREREQUISITES}}` - 前置条件
|
||||
- `{{STEP_N_TITLE}}` - 步骤标题
|
||||
- `{{STEP_N_CONTENT}}` - 步骤内容
|
||||
- `{{FULL_CODE}}` - 完整代码
|
||||
- `{{EXPECTED_OUTPUT}}` - 预期输出
|
||||
- `{{NEXT_STEPS}}` - 下一步建议
|
||||
|
||||
## 示例输出
|
||||
|
||||
参考 `examples/tutorial-example.md`,该示例基于现有的教程文档创建。
|
||||
|
||||
## 内容要求
|
||||
|
||||
### 学习目标
|
||||
- 使用列表格式
|
||||
- 3-5 个具体的学习目标
|
||||
- 使用"能够..."句式
|
||||
|
||||
**示例**:
|
||||
```markdown
|
||||
## 学习目标
|
||||
|
||||
完成本教程后,你将能够:
|
||||
- 创建自定义的 Model 类
|
||||
- 在架构中注册 Model
|
||||
- 从 Controller 中访问 Model
|
||||
- 使用可绑定属性管理数据
|
||||
```
|
||||
|
||||
### 前置条件
|
||||
- 列出必需的知识
|
||||
- 说明环境要求
|
||||
- 提供相关文档链接
|
||||
|
||||
**示例**:
|
||||
```markdown
|
||||
## 前置条件
|
||||
|
||||
- 已安装 GFramework.Core NuGet 包
|
||||
- 了解 C# 基础语法
|
||||
- 阅读过[架构概览](/zh-CN/getting-started)
|
||||
```
|
||||
|
||||
### 步骤内容
|
||||
- 每步 100-300 字说明
|
||||
- 包含完整的代码示例
|
||||
- 解释关键代码的作用
|
||||
- 使用注释标注重要部分
|
||||
|
||||
### 完整代码
|
||||
- 汇总所有步骤的代码
|
||||
- 确保可以直接复制运行
|
||||
- 包含必要的 using 语句
|
||||
- 添加文件结构说明
|
||||
|
||||
### 运行结果
|
||||
- 描述预期的输出
|
||||
- 如果有界面,提供截图或描述
|
||||
- 说明如何验证结果正确
|
||||
|
||||
### 下一步
|
||||
- 推荐 2-3 个后续教程
|
||||
- 提供相关文档链接
|
||||
- 建议进阶学习方向
|
||||
|
||||
## 写作风格
|
||||
|
||||
### 语气
|
||||
- 友好、鼓励性
|
||||
- 使用第二人称("你")
|
||||
- 避免假设读者已有高级知识
|
||||
|
||||
### 步骤说明
|
||||
- 使用主动语态
|
||||
- 步骤标题使用动词开头
|
||||
- 说明"为什么"而不仅是"怎么做"
|
||||
|
||||
### 代码示例
|
||||
- 完整且可运行
|
||||
- 包含详细注释
|
||||
- 使用有意义的变量名
|
||||
- 遵循项目代码风格
|
||||
|
||||
## 配置选项
|
||||
|
||||
### 教程难度
|
||||
|
||||
```bash
|
||||
# 初学者(更多解释,简单示例)
|
||||
/vitepress-tutorial "创建第一个 System" --level beginner
|
||||
|
||||
# 中级(平衡解释和复杂度)
|
||||
/vitepress-tutorial "实现自定义命令" --level intermediate
|
||||
|
||||
# 高级(简洁说明,复杂示例)
|
||||
/vitepress-tutorial "架构模块开发" --level advanced
|
||||
```
|
||||
|
||||
### 步骤数量
|
||||
|
||||
```bash
|
||||
# 指定步骤数量(3-7 步)
|
||||
/vitepress-tutorial "使用事件系统" --steps 5
|
||||
```
|
||||
|
||||
## 前置条件
|
||||
|
||||
1. 了解教程主题的基本概念
|
||||
2. 能够访问相关代码文件
|
||||
3. 了解目标受众的知识水平
|
||||
|
||||
## 相关 Skills
|
||||
|
||||
- `/vitepress-api-doc` - 生成 API 参考文档
|
||||
- `/vitepress-guide` - 生成功能指南
|
||||
- `/vitepress-validate` - 验证生成的文档
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **从简单开始**:第一步应该是最简单的操作
|
||||
2. **逐步增加复杂度**:每步在前一步基础上增加新内容
|
||||
3. **提供完整代码**:确保每步的代码都可以运行
|
||||
4. **解释关键概念**:不要假设读者已经了解所有术语
|
||||
5. **测试教程**:确保按照步骤操作能够得到预期结果
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 问题:步骤过多,教程太长
|
||||
**解决方案**:将教程拆分为多个小教程,或合并相似的步骤
|
||||
|
||||
### 问题:代码示例不完整
|
||||
**解决方案**:在"完整代码"章节提供所有文件的完整代码
|
||||
|
||||
### 问题:读者反馈步骤不清晰
|
||||
**解决方案**:增加更多说明,使用截图或图表辅助
|
||||
|
||||
## 版本历史
|
||||
|
||||
- v1.0.0 - 初始版本,支持分步教程生成
|
||||
@ -1,347 +0,0 @@
|
||||
---
|
||||
title: 创建第一个 Model
|
||||
description: 学习如何创建和使用 Model 来管理应用数据
|
||||
---
|
||||
|
||||
# 创建第一个 Model
|
||||
|
||||
## 学习目标
|
||||
|
||||
完成本教程后,你将能够:
|
||||
- 理解 Model 在架构中的作用
|
||||
- 创建自定义的 Model 类
|
||||
- 在架构中注册 Model
|
||||
- 从 Controller 中访问 Model
|
||||
- 使用可绑定属性管理数据
|
||||
|
||||
## 前置条件
|
||||
|
||||
- 已安装 GFramework.Core NuGet 包
|
||||
- 了解 C# 基础语法
|
||||
- 阅读过[架构概览](/zh-CN/getting-started)
|
||||
|
||||
## 步骤 1:创建 Model 类
|
||||
|
||||
首先,我们需要创建一个 Model 来存储玩家数据。Model 负责管理应用的数据和状态。
|
||||
|
||||
```csharp
|
||||
using GFramework.Core.Abstractions.model;
|
||||
using GFramework.Core.Abstractions.property;
|
||||
|
||||
namespace MyGame.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// 玩家数据模型
|
||||
/// </summary>
|
||||
public class PlayerModel : IModel
|
||||
{
|
||||
// 玩家名称(可绑定属性)
|
||||
public BindableProperty<string> Name { get; } = new("Player");
|
||||
|
||||
// 玩家生命值
|
||||
public BindableProperty<int> Health { get; } = new(100);
|
||||
|
||||
// 玩家金币
|
||||
public BindableProperty<int> Gold { get; } = new(0);
|
||||
|
||||
// 玩家等级
|
||||
public BindableProperty<int> Level { get; } = new(1);
|
||||
|
||||
/// <summary>
|
||||
/// Model 初始化方法
|
||||
/// </summary>
|
||||
public void Init()
|
||||
{
|
||||
// 在这里可以进行初始化操作
|
||||
// 例如:从配置文件加载默认值
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**代码说明**:
|
||||
- `IModel` 接口标识这是一个数据模型
|
||||
- `BindableProperty<T>` 是可绑定属性,值变化时会自动通知监听者
|
||||
- `Init()` 方法在 Model 注册到架构时被调用
|
||||
- 使用属性初始化器设置默认值
|
||||
|
||||
## 步骤 2:在架构中注册 Model
|
||||
|
||||
创建架构类并注册 Model:
|
||||
|
||||
```csharp
|
||||
using GFramework.Core.architecture;
|
||||
using MyGame.Models;
|
||||
|
||||
namespace MyGame
|
||||
{
|
||||
/// <summary>
|
||||
/// 游戏架构
|
||||
/// </summary>
|
||||
public class GameArchitecture : Architecture
|
||||
{
|
||||
// 单例访问点
|
||||
public static IArchitecture Interface { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 初始化架构
|
||||
/// </summary>
|
||||
protected override void Init()
|
||||
{
|
||||
Interface = this;
|
||||
|
||||
// 注册 Model
|
||||
RegisterModel(new PlayerModel());
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**代码说明**:
|
||||
- 继承 `Architecture` 基类
|
||||
- 在 `Init()` 方法中注册 Model
|
||||
- 提供静态属性 `Interface` 用于全局访问架构
|
||||
|
||||
## 步骤 3:创建 Controller 访问 Model
|
||||
|
||||
创建 Controller 来使用 Model:
|
||||
|
||||
```csharp
|
||||
using GFramework.Core.Abstractions.architecture;
|
||||
using GFramework.Core.Abstractions.controller;
|
||||
using GFramework.Core.extensions;
|
||||
using MyGame.Models;
|
||||
|
||||
namespace MyGame.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// 游戏控制器
|
||||
/// </summary>
|
||||
public class GameController : IController
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取架构实例
|
||||
/// </summary>
|
||||
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化玩家数据
|
||||
/// </summary>
|
||||
public void InitializePlayer()
|
||||
{
|
||||
// 获取 PlayerModel
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
|
||||
// 设置玩家数据
|
||||
playerModel.Name.Value = "勇者";
|
||||
playerModel.Health.Value = 100;
|
||||
playerModel.Gold.Value = 50;
|
||||
playerModel.Level.Value = 1;
|
||||
|
||||
// 监听属性变化
|
||||
playerModel.Health.RegisterOnValueChanged(health =>
|
||||
{
|
||||
Console.WriteLine($"玩家生命值变化: {health}");
|
||||
|
||||
if (health <= 0)
|
||||
{
|
||||
Console.WriteLine("玩家死亡!");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 玩家受到伤害
|
||||
/// </summary>
|
||||
public void TakeDamage(int damage)
|
||||
{
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
playerModel.Health.Value -= damage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 玩家获得金币
|
||||
/// </summary>
|
||||
public void AddGold(int amount)
|
||||
{
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
playerModel.Gold.Value += amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**代码说明**:
|
||||
- 实现 `IController` 接口
|
||||
- 通过 `this.GetModel<T>()` 扩展方法获取 Model
|
||||
- 使用 `.Value` 访问和修改属性值
|
||||
- 使用 `RegisterOnValueChanged` 监听属性变化
|
||||
|
||||
## 步骤 4:初始化并使用架构
|
||||
|
||||
在程序入口点初始化架构:
|
||||
|
||||
```csharp
|
||||
using MyGame;
|
||||
using MyGame.Controllers;
|
||||
|
||||
// 1. 创建并初始化架构
|
||||
var architecture = new GameArchitecture();
|
||||
architecture.Initialize();
|
||||
|
||||
// 2. 等待架构就绪
|
||||
await architecture.WaitUntilReadyAsync();
|
||||
|
||||
// 3. 创建 Controller 并使用
|
||||
var gameController = new GameController();
|
||||
|
||||
// 初始化玩家
|
||||
gameController.InitializePlayer();
|
||||
|
||||
// 玩家受到伤害
|
||||
gameController.TakeDamage(20);
|
||||
// 输出: 玩家生命值变化: 80
|
||||
|
||||
// 玩家获得金币
|
||||
gameController.AddGold(100);
|
||||
```
|
||||
|
||||
**代码说明**:
|
||||
- 创建架构实例并调用 `Initialize()`
|
||||
- 使用 `WaitUntilReadyAsync()` 等待架构就绪
|
||||
- 创建 Controller 实例并调用方法
|
||||
|
||||
## 完整代码
|
||||
|
||||
### PlayerModel.cs
|
||||
|
||||
```csharp
|
||||
using GFramework.Core.Abstractions.model;
|
||||
using GFramework.Core.Abstractions.property;
|
||||
|
||||
namespace MyGame.Models
|
||||
{
|
||||
public class PlayerModel : IModel
|
||||
{
|
||||
public BindableProperty<string> Name { get; } = new("Player");
|
||||
public BindableProperty<int> Health { get; } = new(100);
|
||||
public BindableProperty<int> Gold { get; } = new(0);
|
||||
public BindableProperty<int> Level { get; } = new(1);
|
||||
|
||||
public void Init() { }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### GameArchitecture.cs
|
||||
|
||||
```csharp
|
||||
using GFramework.Core.architecture;
|
||||
using MyGame.Models;
|
||||
|
||||
namespace MyGame
|
||||
{
|
||||
public class GameArchitecture : Architecture
|
||||
{
|
||||
public static IArchitecture Interface { get; private set; }
|
||||
|
||||
protected override void Init()
|
||||
{
|
||||
Interface = this;
|
||||
RegisterModel(new PlayerModel());
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### GameController.cs
|
||||
|
||||
```csharp
|
||||
using GFramework.Core.Abstractions.architecture;
|
||||
using GFramework.Core.Abstractions.controller;
|
||||
using GFramework.Core.extensions;
|
||||
using MyGame.Models;
|
||||
|
||||
namespace MyGame.Controllers
|
||||
{
|
||||
public class GameController : IController
|
||||
{
|
||||
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
|
||||
|
||||
public void InitializePlayer()
|
||||
{
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
playerModel.Name.Value = "勇者";
|
||||
playerModel.Health.Value = 100;
|
||||
playerModel.Gold.Value = 50;
|
||||
playerModel.Level.Value = 1;
|
||||
|
||||
playerModel.Health.RegisterOnValueChanged(health =>
|
||||
{
|
||||
Console.WriteLine($"玩家生命值变化: {health}");
|
||||
if (health <= 0)
|
||||
{
|
||||
Console.WriteLine("玩家死亡!");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void TakeDamage(int damage)
|
||||
{
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
playerModel.Health.Value -= damage;
|
||||
}
|
||||
|
||||
public void AddGold(int amount)
|
||||
{
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
playerModel.Gold.Value += amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Program.cs
|
||||
|
||||
```csharp
|
||||
using MyGame;
|
||||
using MyGame.Controllers;
|
||||
|
||||
var architecture = new GameArchitecture();
|
||||
architecture.Initialize();
|
||||
await architecture.WaitUntilReadyAsync();
|
||||
|
||||
var gameController = new GameController();
|
||||
gameController.InitializePlayer();
|
||||
gameController.TakeDamage(20);
|
||||
gameController.AddGold(100);
|
||||
```
|
||||
|
||||
## 运行结果
|
||||
|
||||
运行程序后,你将看到以下输出:
|
||||
|
||||
```
|
||||
玩家生命值变化: 100
|
||||
玩家生命值变化: 80
|
||||
```
|
||||
|
||||
**验证步骤**:
|
||||
1. 程序成功启动,没有异常
|
||||
2. 控制台输出生命值变化信息
|
||||
3. 玩家数据正确更新
|
||||
|
||||
## 下一步
|
||||
|
||||
恭喜!你已经学会了如何创建和使用 Model。接下来可以学习:
|
||||
|
||||
- [创建第一个 System](/zh-CN/tutorials/create-first-system) - 学习如何创建业务逻辑层
|
||||
- [使用命令系统](/zh-CN/tutorials/use-command-system) - 学习如何封装操作
|
||||
- [使用事件系统](/zh-CN/tutorials/use-event-system) - 学习组件间通信
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [Model 层](/zh-CN/core/model) - Model 详细说明
|
||||
- [属性系统](/zh-CN/core/property) - 可绑定属性详解
|
||||
- [架构组件](/zh-CN/core/architecture) - 架构基础
|
||||
- [Controller 层](/zh-CN/core/controller) - Controller 详细说明
|
||||
@ -1,42 +0,0 @@
|
||||
---
|
||||
title: {{TUTORIAL_TITLE}}
|
||||
description: {{TUTORIAL_DESCRIPTION}}
|
||||
---
|
||||
|
||||
# {{TUTORIAL_TITLE}}
|
||||
|
||||
## 学习目标
|
||||
|
||||
{{LEARNING_OBJECTIVES}}
|
||||
|
||||
## 前置条件
|
||||
|
||||
{{PREREQUISITES}}
|
||||
|
||||
## 步骤 1:{{STEP_1_TITLE}}
|
||||
|
||||
{{STEP_1_CONTENT}}
|
||||
|
||||
## 步骤 2:{{STEP_2_TITLE}}
|
||||
|
||||
{{STEP_2_CONTENT}}
|
||||
|
||||
## 步骤 3:{{STEP_3_TITLE}}
|
||||
|
||||
{{STEP_3_CONTENT}}
|
||||
|
||||
## 完整代码
|
||||
|
||||
{{FULL_CODE}}
|
||||
|
||||
## 运行结果
|
||||
|
||||
{{EXPECTED_OUTPUT}}
|
||||
|
||||
## 下一步
|
||||
|
||||
{{NEXT_STEPS}}
|
||||
|
||||
## 相关文档
|
||||
|
||||
{{RELATED_DOCS}}
|
||||
@ -1,297 +0,0 @@
|
||||
# VitePress 文档验证
|
||||
|
||||
验证 VitePress 文档的质量和规范性,确保文档符合项目标准。
|
||||
|
||||
## 用途
|
||||
|
||||
此 skill 用于验证 Markdown 文档的格式和内容,包括:
|
||||
- Frontmatter 格式正确性
|
||||
- 内部链接有效性
|
||||
- 代码块语法标记
|
||||
- 标题层级结构
|
||||
- 中文标点符号规范
|
||||
- 泛型符号转义
|
||||
|
||||
## 调用方式
|
||||
|
||||
```bash
|
||||
# 验证单个文件
|
||||
/vitepress-validate <文件路径>
|
||||
|
||||
# 验证整个目录
|
||||
/vitepress-validate <目录路径>
|
||||
|
||||
# 验证所有文档
|
||||
/vitepress-validate docs/zh-CN/
|
||||
```
|
||||
|
||||
**示例**:
|
||||
```bash
|
||||
/vitepress-validate docs/zh-CN/api-reference/core/architecture.md
|
||||
/vitepress-validate docs/zh-CN/core/
|
||||
```
|
||||
|
||||
## 验证项
|
||||
|
||||
### 1. Frontmatter 验证
|
||||
|
||||
**检查项**:
|
||||
- YAML 语法正确性
|
||||
- 必需字段存在(`title`、`description`)
|
||||
- 字段值类型正确
|
||||
- `outline` 字段值有效(`deep`、`[2,3]` 等)
|
||||
|
||||
**示例**:
|
||||
```yaml
|
||||
---
|
||||
title: Architecture # 必需
|
||||
description: 架构基类说明 # 必需
|
||||
outline: deep # 可选,但值必须有效
|
||||
---
|
||||
```
|
||||
|
||||
### 2. 内部链接验证
|
||||
|
||||
**检查项**:
|
||||
- 相对路径链接指向的文件存在
|
||||
- 绝对路径链接格式正确
|
||||
- 锚点链接对应的标题存在
|
||||
- 没有损坏的链接
|
||||
|
||||
**有效链接格式**:
|
||||
- `[文本](./file.md)` - 相对路径
|
||||
- `[文本](/zh-CN/core/architecture)` - 绝对路径
|
||||
- `[文本](#标题)` - 锚点链接
|
||||
- `[文本](./file.md#标题)` - 组合链接
|
||||
|
||||
### 3. 代码块验证
|
||||
|
||||
**检查项**:
|
||||
- 代码块有语法标记(```csharp、```bash 等)
|
||||
- C# 代码块使用 `csharp` 标记(不是 `cs` 或 `c#`)
|
||||
- 代码块正确闭合
|
||||
- 没有未闭合的反引号
|
||||
|
||||
**正确格式**:
|
||||
```markdown
|
||||
\`\`\`csharp
|
||||
public class Example { }
|
||||
\`\`\`
|
||||
```
|
||||
|
||||
**错误格式**:
|
||||
```markdown
|
||||
\`\`\`cs // 应该使用 csharp
|
||||
public class Example { }
|
||||
\`\`\`
|
||||
```
|
||||
|
||||
### 4. 标题层级验证
|
||||
|
||||
**检查项**:
|
||||
- 标题层级不跳级(不能从 `#` 直接跳到 `###`)
|
||||
- 每个文档只有一个一级标题(`#`)
|
||||
- 标题层级递增合理
|
||||
|
||||
**正确示例**:
|
||||
```markdown
|
||||
# 一级标题
|
||||
## 二级标题
|
||||
### 三级标题
|
||||
## 另一个二级标题
|
||||
```
|
||||
|
||||
**错误示例**:
|
||||
```markdown
|
||||
# 一级标题
|
||||
### 三级标题 ❌ 跳过了二级标题
|
||||
```
|
||||
|
||||
### 5. 中文标点符号验证
|
||||
|
||||
**检查项**:
|
||||
- 中文句子使用全角标点(,。!?)
|
||||
- 英文句子使用半角标点(,.!?)
|
||||
- 代码和技术术语周围使用半角符号
|
||||
- 括号使用规范
|
||||
|
||||
**规范示例**:
|
||||
- "这是一个示例。" ✓(中文全角句号)
|
||||
- "This is an example." ✓(英文半角句号)
|
||||
- "`Architecture` 类提供了..." ✓(代码周围半角)
|
||||
|
||||
### 6. 泛型符号验证
|
||||
|
||||
**检查项**:
|
||||
- 泛型符号正确转义(`<T>` → `<T>`)
|
||||
- 仅在代码块外转义
|
||||
- 代码块内保持原样
|
||||
|
||||
**正确示例**:
|
||||
```markdown
|
||||
`List<T>` 是一个泛型类。
|
||||
|
||||
\`\`\`csharp
|
||||
List<T> items = new List<T>(); // 代码块内不转义
|
||||
\`\`\`
|
||||
```
|
||||
|
||||
## 验证脚本
|
||||
|
||||
### validate-frontmatter.sh
|
||||
|
||||
验证 Frontmatter 格式。
|
||||
|
||||
**用法**:
|
||||
```bash
|
||||
.claude/skills/vitepress-validate/scripts/validate-frontmatter.sh <文件路径>
|
||||
```
|
||||
|
||||
### validate-links.sh
|
||||
|
||||
验证内部链接有效性。
|
||||
|
||||
**用法**:
|
||||
```bash
|
||||
.claude/skills/vitepress-validate/scripts/validate-links.sh <文件路径>
|
||||
```
|
||||
|
||||
### validate-code-blocks.sh
|
||||
|
||||
验证代码块语法。
|
||||
|
||||
**用法**:
|
||||
```bash
|
||||
.claude/skills/vitepress-validate/scripts/validate-code-blocks.sh <文件路径>
|
||||
```
|
||||
|
||||
### validate-all.sh
|
||||
|
||||
执行所有验证。
|
||||
|
||||
**用法**:
|
||||
```bash
|
||||
.claude/skills/vitepress-validate/scripts/validate-all.sh <文件或目录路径>
|
||||
```
|
||||
|
||||
## 输出格式
|
||||
|
||||
### 验证通过
|
||||
|
||||
```
|
||||
✓ docs/zh-CN/core/architecture.md
|
||||
- Frontmatter: 通过
|
||||
- 内部链接: 通过
|
||||
- 代码块: 通过
|
||||
- 标题层级: 通过
|
||||
- 标点符号: 通过
|
||||
- 泛型符号: 通过
|
||||
```
|
||||
|
||||
### 验证失败
|
||||
|
||||
```
|
||||
✗ docs/zh-CN/core/architecture.md
|
||||
- Frontmatter: 失败
|
||||
× 缺少必需字段: description
|
||||
- 内部链接: 失败
|
||||
× 损坏的链接: ./missing-file.md (第 45 行)
|
||||
- 代码块: 警告
|
||||
⚠ 使用了 'cs' 标记,建议使用 'csharp' (第 78 行)
|
||||
- 标题层级: 通过
|
||||
- 标点符号: 警告
|
||||
⚠ 中文句子使用了半角句号 (第 102 行)
|
||||
- 泛型符号: 失败
|
||||
× 未转义的泛型符号: List<T> (第 120 行)
|
||||
```
|
||||
|
||||
## 修复建议
|
||||
|
||||
验证失败时,skill 会提供具体的修复建议:
|
||||
|
||||
**示例**:
|
||||
```
|
||||
修复建议:
|
||||
1. 在 Frontmatter 中添加 description 字段
|
||||
2. 修复或删除损坏的链接: ./missing-file.md
|
||||
3. 将代码块标记从 'cs' 改为 'csharp'
|
||||
4. 将第 102 行的半角句号改为全角句号
|
||||
5. 将第 120 行的 List<T> 改为 List<T>
|
||||
```
|
||||
|
||||
## 配置选项
|
||||
|
||||
### 严格模式
|
||||
|
||||
启用严格模式时,警告也会导致验证失败。
|
||||
|
||||
```bash
|
||||
/vitepress-validate --strict docs/zh-CN/
|
||||
```
|
||||
|
||||
### 忽略特定检查
|
||||
|
||||
```bash
|
||||
# 忽略标点符号检查
|
||||
/vitepress-validate --ignore-punctuation docs/zh-CN/
|
||||
|
||||
# 忽略多个检查
|
||||
/vitepress-validate --ignore-punctuation --ignore-generics docs/zh-CN/
|
||||
```
|
||||
|
||||
## 集成到工作流
|
||||
|
||||
### 生成后自动验证
|
||||
|
||||
```bash
|
||||
# 1. 生成 API 文档
|
||||
/vitepress-api-doc GFramework.Core/architecture/Architecture.cs
|
||||
|
||||
# 2. 自动验证生成的文档
|
||||
/vitepress-validate docs/zh-CN/api-reference/core/architecture.md
|
||||
```
|
||||
|
||||
### 批量验证
|
||||
|
||||
```bash
|
||||
# 验证所有 API 文档
|
||||
/vitepress-validate docs/zh-CN/api-reference/
|
||||
|
||||
# 验证所有文档
|
||||
/vitepress-validate docs/zh-CN/
|
||||
```
|
||||
|
||||
## 退出代码
|
||||
|
||||
- `0` - 所有验证通过
|
||||
- `1` - 存在错误
|
||||
- `2` - 仅存在警告(非严格模式下仍返回 0)
|
||||
|
||||
## 相关 Skills
|
||||
|
||||
- `/vitepress-api-doc` - 生成 API 文档后自动验证
|
||||
- `/vitepress-guide` - 生成指南文档后自动验证
|
||||
- `/vitepress-tutorial` - 生成教程文档后自动验证
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **生成后立即验证**:每次生成文档后立即运行验证
|
||||
2. **定期批量验证**:定期验证所有文档,确保一致性
|
||||
3. **修复所有错误**:不要忽略验证错误,及时修复
|
||||
4. **关注警告**:警告虽不致命,但应该重视并修复
|
||||
5. **使用严格模式**:在 CI/CD 中使用严格模式确保质量
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 问题:误报泛型符号错误
|
||||
**解决方案**:确保泛型符号在代码块外正确转义,代码块内保持原样
|
||||
|
||||
### 问题:中文标点符号检查过于严格
|
||||
**解决方案**:使用 `--ignore-punctuation` 选项,或手动调整规则
|
||||
|
||||
### 问题:链接验证失败但文件确实存在
|
||||
**解决方案**:检查文件路径大小写,确保路径完全匹配
|
||||
|
||||
## 版本历史
|
||||
|
||||
- v1.0.0 - 初始版本,支持 6 项基本验证
|
||||
@ -1,109 +0,0 @@
|
||||
#!/bin/bash
|
||||
# 执行所有验证
|
||||
# 用法: validate-all.sh <文件或目录路径>
|
||||
|
||||
set -e
|
||||
|
||||
TARGET="$1"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
if [ -z "$TARGET" ]; then
|
||||
echo "用法: $0 <文件或目录路径>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -e "$TARGET" ]; then
|
||||
echo "错误: 路径不存在: $TARGET"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=========================================="
|
||||
echo "VitePress 文档验证"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# 收集所有 Markdown 文件
|
||||
if [ -f "$TARGET" ]; then
|
||||
FILES=("$TARGET")
|
||||
elif [ -d "$TARGET" ]; then
|
||||
mapfile -t FILES < <(find "$TARGET" -name "*.md" -type f)
|
||||
else
|
||||
echo "错误: 无效的路径: $TARGET"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ${#FILES[@]} -eq 0 ]; then
|
||||
echo "未找到 Markdown 文件"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "找到 ${#FILES[@]} 个文件"
|
||||
echo ""
|
||||
|
||||
TOTAL_ERRORS=0
|
||||
TOTAL_WARNINGS=0
|
||||
PASSED_FILES=0
|
||||
FAILED_FILES=0
|
||||
|
||||
for FILE in "${FILES[@]}"; do
|
||||
echo "验证: $FILE"
|
||||
echo "----------------------------------------"
|
||||
|
||||
FILE_ERRORS=0
|
||||
FILE_WARNINGS=0
|
||||
|
||||
# 1. Frontmatter 验证
|
||||
if bash "$SCRIPT_DIR/validate-frontmatter.sh" "$FILE" 2>&1 | grep -q "✗"; then
|
||||
FILE_ERRORS=$((FILE_ERRORS + 1))
|
||||
fi
|
||||
|
||||
# 2. 链接验证
|
||||
if bash "$SCRIPT_DIR/validate-links.sh" "$FILE" 2>&1 | grep -q "✗"; then
|
||||
FILE_ERRORS=$((FILE_ERRORS + 1))
|
||||
fi
|
||||
|
||||
# 3. 代码块验证
|
||||
OUTPUT=$(bash "$SCRIPT_DIR/validate-code-blocks.sh" "$FILE" 2>&1 || true)
|
||||
if echo "$OUTPUT" | grep -q "✗"; then
|
||||
FILE_ERRORS=$((FILE_ERRORS + 1))
|
||||
fi
|
||||
if echo "$OUTPUT" | grep -q "⚠"; then
|
||||
FILE_WARNINGS=$((FILE_WARNINGS + 1))
|
||||
fi
|
||||
|
||||
# 统计结果
|
||||
if [ $FILE_ERRORS -eq 0 ]; then
|
||||
echo "✓ 验证通过"
|
||||
PASSED_FILES=$((PASSED_FILES + 1))
|
||||
else
|
||||
echo "✗ 验证失败($FILE_ERRORS 个错误)"
|
||||
FAILED_FILES=$((FAILED_FILES + 1))
|
||||
fi
|
||||
|
||||
if [ $FILE_WARNINGS -gt 0 ]; then
|
||||
echo "⚠ $FILE_WARNINGS 个警告"
|
||||
fi
|
||||
|
||||
TOTAL_ERRORS=$((TOTAL_ERRORS + FILE_ERRORS))
|
||||
TOTAL_WARNINGS=$((TOTAL_WARNINGS + FILE_WARNINGS))
|
||||
|
||||
echo ""
|
||||
done
|
||||
|
||||
echo "=========================================="
|
||||
echo "验证摘要"
|
||||
echo "=========================================="
|
||||
echo "总文件数: ${#FILES[@]}"
|
||||
echo "通过: $PASSED_FILES"
|
||||
echo "失败: $FAILED_FILES"
|
||||
echo "总错误数: $TOTAL_ERRORS"
|
||||
echo "总警告数: $TOTAL_WARNINGS"
|
||||
echo ""
|
||||
|
||||
if [ $TOTAL_ERRORS -eq 0 ]; then
|
||||
echo "✓ 所有验证通过"
|
||||
exit 0
|
||||
else
|
||||
echo "✗ 验证失败"
|
||||
exit 1
|
||||
fi
|
||||
@ -1,64 +0,0 @@
|
||||
#!/bin/bash
|
||||
# 验证代码块语法
|
||||
# 用法: validate-code-blocks.sh <文件路径>
|
||||
|
||||
set -e
|
||||
|
||||
FILE="$1"
|
||||
|
||||
if [ -z "$FILE" ]; then
|
||||
echo "用法: $0 <文件路径>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$FILE" ]; then
|
||||
echo "错误: 文件不存在: $FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "验证代码块语法: $FILE"
|
||||
|
||||
ERROR_COUNT=0
|
||||
WARNING_COUNT=0
|
||||
|
||||
# 检查未闭合的代码块
|
||||
OPEN_COUNT=$(grep -c '^```' "$FILE" || true)
|
||||
if [ $((OPEN_COUNT % 2)) -ne 0 ]; then
|
||||
echo "✗ 错误: 存在未闭合的代码块"
|
||||
ERROR_COUNT=$((ERROR_COUNT + 1))
|
||||
fi
|
||||
|
||||
# 检查 C# 代码块标记
|
||||
LINE_NUM=0
|
||||
while IFS= read -r LINE; do
|
||||
LINE_NUM=$((LINE_NUM + 1))
|
||||
|
||||
# 检查是否使用了错误的 C# 标记
|
||||
if echo "$LINE" | grep -qE '^```(cs|c#|C#)$'; then
|
||||
echo "⚠ 警告: 第 $LINE_NUM 行使用了非标准标记,建议使用 'csharp'"
|
||||
echo " 当前: $LINE"
|
||||
WARNING_COUNT=$((WARNING_COUNT + 1))
|
||||
fi
|
||||
|
||||
# 检查代码块是否有语言标记
|
||||
if echo "$LINE" | grep -qE '^```$'; then
|
||||
# 检查下一行是否是代码(简单启发式:不是空行且不是 ```)
|
||||
NEXT_LINE=$(sed -n "$((LINE_NUM + 1))p" "$FILE")
|
||||
if [ -n "$NEXT_LINE" ] && ! echo "$NEXT_LINE" | grep -qE '^```'; then
|
||||
echo "⚠ 警告: 第 $LINE_NUM 行的代码块缺少语言标记"
|
||||
WARNING_COUNT=$((WARNING_COUNT + 1))
|
||||
fi
|
||||
fi
|
||||
done < "$FILE"
|
||||
|
||||
# 输出结果
|
||||
if [ $ERROR_COUNT -eq 0 ] && [ $WARNING_COUNT -eq 0 ]; then
|
||||
echo "✓ 代码块验证通过"
|
||||
exit 0
|
||||
elif [ $ERROR_COUNT -eq 0 ]; then
|
||||
echo "⚠ 代码块验证通过(有 $WARNING_COUNT 个警告)"
|
||||
exit 0
|
||||
else
|
||||
echo "✗ 代码块验证失败($ERROR_COUNT 个错误,$WARNING_COUNT 个警告)"
|
||||
exit 1
|
||||
fi
|
||||
@ -1,57 +0,0 @@
|
||||
#!/bin/bash
|
||||
# 验证 Frontmatter 格式
|
||||
# 用法: validate-frontmatter.sh <文件路径>
|
||||
|
||||
set -e
|
||||
|
||||
FILE="$1"
|
||||
|
||||
if [ -z "$FILE" ]; then
|
||||
echo "用法: $0 <文件路径>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$FILE" ]; then
|
||||
echo "错误: 文件不存在: $FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "验证 Frontmatter: $FILE"
|
||||
|
||||
# 检查是否有 Frontmatter(限制在前几行,避免匹配正文中的 '---')
|
||||
if ! head -n 5 "$FILE" | grep -q "^---$"; then
|
||||
echo "✗ 错误: 文件缺少 Frontmatter"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 提取 Frontmatter 内容(第一个 --- 到第二个 --- 之间)
|
||||
FRONTMATTER=$(sed -n '/^---$/,/^---$/p' "$FILE" | sed '1d;$d')
|
||||
|
||||
if [ -z "$FRONTMATTER" ]; then
|
||||
echo "✗ 错误: Frontmatter 为空"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查必需字段: title
|
||||
if ! echo "$FRONTMATTER" | grep -q "^title:"; then
|
||||
echo "✗ 错误: 缺少必需字段: title"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查必需字段: description
|
||||
if ! echo "$FRONTMATTER" | grep -q "^description:"; then
|
||||
echo "✗ 错误: 缺少必需字段: description"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查 outline 字段值(如果存在)
|
||||
if echo "$FRONTMATTER" | grep -q "^outline:"; then
|
||||
OUTLINE_VALUE=$(echo "$FRONTMATTER" | grep "^outline:" | sed 's/outline:\s*//')
|
||||
if [ "$OUTLINE_VALUE" != "deep" ] && [ "$OUTLINE_VALUE" != "false" ] && ! echo "$OUTLINE_VALUE" | grep -qE '^\[.*\]$'; then
|
||||
echo "⚠ 警告: outline 字段值可能无效: $OUTLINE_VALUE"
|
||||
echo " 有效值: deep, false, [2,3]"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "✓ Frontmatter 验证通过"
|
||||
exit 0
|
||||
@ -1,4 +0,0 @@
|
||||
interface:
|
||||
display_name: "GFramework PR Review"
|
||||
short_description: "Inspect the current PR and CodeRabbit findings"
|
||||
default_prompt: "Use $gframework-pr-review to inspect the current branch PR through the GitHub API, prioritize unresolved review threads on the latest head commit, and summarize failed checks or failed tests."
|
||||
@ -0,0 +1,73 @@
|
||||
# Documentation Governance And Refresh Trace Archive RP-001 Through RP-008
|
||||
|
||||
> This archive preserves closed recovery-point history that no longer needs to stay in the default boot trace.
|
||||
> The active trace should point here instead of repeating these completed stages.
|
||||
|
||||
## RP-001 Local-Plan Migration
|
||||
|
||||
- 迁移 `local-plan/` 中的 durable recovery state 到
|
||||
`ai-plan/public/documentation-governance-and-refresh/`
|
||||
- 建立 `todos/`、`traces/`、`archive/todos/` 与 `archive/traces/`
|
||||
- 在 `ai-plan/public/README.md` 中建立
|
||||
`docs/sdk-update-documentation` 到 `documentation-governance-and-refresh` 的映射
|
||||
- 同步记录 `ai-plan-governance` 主题的迁移结论
|
||||
|
||||
## RP-002 Column Landing Pages
|
||||
|
||||
- 复核 `docs/zh-CN/core/index.md`、`game/index.md` 与 `source-generators/index.md`
|
||||
- 对照模块 README 与包拆分关系,重写三个栏目 landing page
|
||||
- 修正 VitePress dead-link 检查中指向 `docs/` 目录外 README 的链接方式
|
||||
- 验证:`cd docs && bun run build`
|
||||
|
||||
## RP-003 Core Topic Pages
|
||||
|
||||
- 核对并重写 `architecture.md`、`context.md`、`lifecycle.md`、`command.md`、`query.md` 与 `cqrs.md`
|
||||
- 移除旧 `Init()`、属性式 `CommandBus` / `QueryBus`、旧 `Input` 赋值式示例和已移除的
|
||||
`RegisterMediatorBehavior` 说明
|
||||
- 将旧 command / query 体系说明收口为兼容路径,并把新功能推荐迁到 `GFramework.Cqrs`
|
||||
- 验证:`cd docs && bun run build`
|
||||
|
||||
## RP-004 PR Review Script Follow-Up
|
||||
|
||||
- 修复 `gframework-pr-review` 把空 `APPROVED` review body 误选为 CodeRabbit review body 的解析路径
|
||||
- 改为在同一提交上优先选择最新非空 CodeRabbit review body
|
||||
- 补齐 `docs/zh-CN/core/index.md` 中 `Godot` 与 `Source Generators` 栏目入口链接
|
||||
- 修正 active trace 重复标题,消除 `MD024/no-duplicate-heading` 噪音
|
||||
- 验证:
|
||||
- `python3 .codex/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --format json`
|
||||
- `cd docs && bun run build`
|
||||
|
||||
## RP-005 Remaining Core High-Risk Topics
|
||||
|
||||
- 核对 `events.md`、`property.md`、`state-management.md`、`coroutine.md` 与 `logging.md`
|
||||
- 重写 `events.md`、`property.md` 与 `logging.md`
|
||||
- 明确 `BindableProperty<T>.Comparer` 按闭合泛型共享,不是实例级配置
|
||||
- 确认 `state-management.md` 与 `coroutine.md` 当前仍可保留
|
||||
- 验证:`cd docs && bun run build`
|
||||
|
||||
## RP-006 Game Scene And UI Topics
|
||||
|
||||
- 核对 `docs/zh-CN/game/scene.md` 与 `docs/zh-CN/game/ui.md`
|
||||
- 重写场景路由文档,明确 `ISceneFactory`、`ISceneRoot`、项目侧 router 与过渡处理器的职责边界
|
||||
- 重写 UI 文档,明确 Page 栈、层级 UI、输入仲裁、World 阻断与暂停语义
|
||||
- 验证:`cd docs && bun run build`
|
||||
|
||||
## RP-007 Core Source Generator Topics
|
||||
|
||||
- 核对 `context-aware-generator.md` 与 `priority-generator.md`
|
||||
- 重写 `[ContextAware]` 文档,说明当前生成成员、provider/实例缓存语义与 `ContextAwareBase` 边界
|
||||
- 重写 `[Priority]` 文档,说明只生成 `IPrioritized`,排序效果取决于调用方是否走 priority-aware API
|
||||
- 验证:`cd docs && bun run build`
|
||||
|
||||
## RP-008 Unified Documentation Refresh Skill
|
||||
|
||||
- 删除旧 `vitepress-*` 公开 skill 定义,建立统一 `.agents/skills/gframework-doc-refresh/`
|
||||
- 新增 `.agents/skills/_shared/module-map.json`,按源码模块而不是文档类型驱动刷新
|
||||
- 重写共享文档标准,固定证据顺序:源码 / XML docs / `*.csproj`、测试、README、当前 docs、`ai-libs/`、归档文档
|
||||
- 新增 `scan_module_evidence.py`,支持模块别名归一化、docs 栏目歧义检测和证据面扫描
|
||||
- 更新 `.agents/skills/README.md`,将统一入口作为推荐工作流
|
||||
- 验证:
|
||||
- `python3 -B .agents/skills/gframework-doc-refresh/scripts/scan_module_evidence.py Core`
|
||||
- `python3 -B .agents/skills/gframework-doc-refresh/scripts/scan_module_evidence.py Godot.SourceGenerators`
|
||||
- `python3 -B .agents/skills/gframework-doc-refresh/scripts/scan_module_evidence.py Cqrs`
|
||||
- `python3 -B .agents/skills/gframework-doc-refresh/scripts/scan_module_evidence.py source-generators --json`
|
||||
@ -7,27 +7,27 @@
|
||||
|
||||
## 当前恢复点
|
||||
|
||||
- 恢复点编号:`DOCUMENTATION-GOVERNANCE-REFRESH-RP-007`
|
||||
- 恢复点编号:`DOCUMENTATION-GOVERNANCE-REFRESH-RP-010`
|
||||
- 当前阶段:`Phase 3`
|
||||
- 当前焦点:
|
||||
- 已完成 `docs/zh-CN/core/events.md`、`property.md` 与 `logging.md` 的专题页重写
|
||||
- 已按源码与测试复核 `docs/zh-CN/core/state-management.md`、`coroutine.md`,当前内容与实现基本一致,无需再做
|
||||
机械改写
|
||||
- 已完成 `docs/zh-CN/game/scene.md` 与 `ui.md` 的专题页重写,当前内容已回到“项目自接 factory/root + router 基类”的真实边界
|
||||
- 已完成 `docs/zh-CN/source-generators/context-aware-generator.md` 与 `priority-generator.md` 的专题页重写,当前内容已回到“真实生成成员、推荐 API 与兼容边界”的结构
|
||||
- 下一轮需要把重心转到 Godot 相关生成器页面核对
|
||||
- 已建立统一公开 skill:`.agents/skills/gframework-doc-refresh/`
|
||||
- 文档重构入口已从“按 guide/tutorial/api 类型拆 skill”收口为“按源码模块驱动文档刷新”
|
||||
- PR #268 的当前未解决 review 线程已进入收口:Scene/UI 标题层级修正、共享脚本 review 修复、`gframework-pr-review` 多 AI reviewer 支持补齐
|
||||
- 下一轮需要用统一 skill 推进 Godot 相关生成器页面核对
|
||||
|
||||
## 当前状态摘要
|
||||
|
||||
- 文档治理规则已收口到仓库规范,README、站点入口与采用链路不再依赖旧文档自证
|
||||
- 高优先级模块入口与 `core` 关键专题页已回到可作为默认导航入口的状态,本轮计划中的 `core` 剩余高风险页面已完成收口
|
||||
- 当前主题仍是 active topic,因为 `source-generators` 栏目下的 Godot 相关页面仍可能包含与实现漂移的旧内容
|
||||
- 当前主题仍是 active topic,因为 `source-generators` 栏目下的 Godot 相关页面仍可能包含与实现漂移的旧内容,且统一 skill 还需要在该场景上继续落地使用
|
||||
|
||||
## 当前活跃事实
|
||||
|
||||
- 旧 `local-plan/` 的详细 todo 与 trace 已迁入主题内 `archive/`
|
||||
- 当前分支 `docs/sdk-update-documentation` 已在 `ai-plan/public/README.md` 建立 topic 映射
|
||||
- active 跟踪文件只保留当前恢复点、活跃事实、风险与下一步,不再重复保存已完成阶段的长篇历史
|
||||
- active trace 已把 RP-001 到 RP-008 的闭环历史归档到
|
||||
`ai-plan/public/documentation-governance-and-refresh/archive/traces/documentation-governance-and-refresh-rp-001-through-rp-008.md`
|
||||
- `core`、`game` 与 `source-generators` 三个栏目入口页现在都以模块 README 与当前包拆分为准
|
||||
- `docs` 站点构建已验证通过,修正了 VitePress 对 `docs/` 目录外相对链接的 dead-link 检查问题
|
||||
- `core` 关键专题页已移除 `Init()`、属性式 `CommandBus` / `QueryBus`、旧 `Input` 赋值式示例和已移除的
|
||||
@ -36,21 +36,39 @@
|
||||
- `documentation-governance-and-refresh` active trace 已把重复的 `### 下一步` 标题改成带恢复点标识的唯一标题,消除
|
||||
`MD024/no-duplicate-heading` 告警
|
||||
- `gframework-pr-review` 脚本已修复“空 `APPROVED` review 覆盖非空 CodeRabbit review body”的解析路径,当前分支可重新提取 Nitpick comments
|
||||
- `gframework-pr-review` 现在显式把 `coderabbitai[bot]` 与 `greptile-apps[bot]` 视为支持的 AI reviewer,并在输出中单独列出
|
||||
reviewer 元数据与 latest-head open thread 计数,不再只把 `greptile-apps` 混在通用 thread 列表里
|
||||
- `.agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py` 已为全部函数补齐 docstring;本地 AST 统计为
|
||||
`44/44`,文件级 docstring coverage 为 `100%`
|
||||
- `docs/zh-CN/core/events.md`、`property.md` 与 `logging.md` 已改成“当前角色、最常用入口、边界和迁移建议”的结构,
|
||||
不再复刻旧版大而全 API 列表
|
||||
- `docs/zh-CN/core/property.md` 已明确记录 `BindableProperty<T>.Comparer` 的闭合泛型级共享语义,避免文档继续误导读者把
|
||||
`WithComparer(...)` 当成实例级配置
|
||||
- `docs/zh-CN/core/state-management.md` 与 `coroutine.md` 已按当前 runtime / 测试重新核对,当前内容可继续保留
|
||||
- `docs/zh-CN/game/scene.md` 已改成“真实公开入口、场景栈语义、factory/root 装配、过渡处理器与守卫扩展点”的结构,
|
||||
不再暗示框架自带统一场景注册与完整引擎装配
|
||||
不再暗示框架自带统一场景注册与完整引擎装配;本轮已补充项目侧目录布局、文件命名、最小 wiring 与兼容说明,并把
|
||||
“推荐目录与文件约定(项目侧)” 收口为 “最小接入路径” 下的子节
|
||||
- `docs/zh-CN/game/ui.md` 已改成“Page 栈、layer UI、输入动作仲裁、World 阻断与暂停语义”的结构,明确 `Show(...)`
|
||||
不适用于 `UiLayer.Page`
|
||||
不适用于 `UiLayer.Page`;本轮已补充 router、factory、root、page behavior、params 与 views 的推荐放置约定,并修复
|
||||
“最小接入路径” 空节与标题层级错位问题
|
||||
- 本轮重写后再次执行 `cd docs && bun run build` 通过,当前 `game` 栏目入口与专题页改动没有破坏站点构建
|
||||
- `docs/zh-CN/source-generators/context-aware-generator.md` 已改成“真实生成成员、provider/实例缓存语义、与 `ContextAwareBase` 的边界、测试接法”的结构,
|
||||
不再用旧版简化生成代码替代当前实现
|
||||
- `docs/zh-CN/source-generators/priority-generator.md` 已改成“生成 `IPrioritized`、priority-aware 检索 API、动态优先级边界与诊断”的结构,
|
||||
不再把 `GetAllByPriority<T>()` / `system.Init()` 当作所有场景的默认示例
|
||||
- 本轮重写后再次执行 `cd docs && bun run build` 通过,当前 `source-generators` 栏目改动没有破坏站点构建
|
||||
- `.agents/skills/gframework-doc-refresh/SKILL.md` 已改成标准 YAML frontmatter skill,并明确支持模块输入、证据顺序、输出优先级与验证步骤
|
||||
- `.agents/skills/gframework-doc-refresh/SKILL.md` 的 `description` 已加引号,修复 `Recommended command:` 中冒号导致的
|
||||
invalid YAML skill 加载警告
|
||||
- `.agents/skills/gframework-doc-refresh/scripts/validate-code-blocks.sh` 已改成基于 `IN_CODE_BLOCK` 跟踪 opening /
|
||||
closing fence,避免把 closing fence 误报成缺少语言标记
|
||||
- `.agents/skills/_shared/module-config.sh` 的 `get_readme_paths()` 已补齐 `Core.SourceGenerators.Abstractions`、
|
||||
`Godot.SourceGenerators.Abstractions`、`Ecs.Arch.Abstractions` 与 `SourceGenerators.Common`,并在未映射模块时返回
|
||||
非零退出码
|
||||
- `.agents/skills/_shared/module-map.json` 已收口为源码模块映射表,覆盖源码目录、测试项目、README、`docs/zh-CN` 栏目与 `ai-libs/` 参考入口
|
||||
- 旧 `vitepress-api-doc`、`vitepress-batch-api`、`vitepress-doc-generator`、`vitepress-guide`、`vitepress-tutorial`、`vitepress-validate`
|
||||
已不再保留为可用公开 skill 定义文件
|
||||
- `ai-libs/` 已纳入统一 skill 的标准证据链,只作为消费者接入参考,不再替代源码与测试契约
|
||||
|
||||
## 当前风险
|
||||
|
||||
@ -59,27 +77,48 @@
|
||||
继续按源码、测试、`*.csproj` 与 `ai-libs/` 下已验证参考实现核对剩余 Godot 相关页面,不把旧文档当事实来源
|
||||
- 采用路径误导风险:根聚合包与模块边界若再次被写错,会继续误导消费者的包选择
|
||||
- 缓解措施:保持“源码与包关系优先”的证据顺序,改动采用说明时同步核对包依赖与生成器 wiring
|
||||
- 模块映射不全风险:统一 skill 若遗漏模块别名、测试项目或 docs 栏目映射,会让后续扫描阶段直接失焦
|
||||
- 缓解措施:以当前 `*.csproj` 族为 canonical module list,统一维护 `.agents/skills/_shared/module-map.json`
|
||||
- `ai-libs/` 漂移风险:参考项目若滞后于当前实现,可能把过时 wiring 重新带回文档
|
||||
- 缓解措施:在 skill 中固定“源码/测试优先,`ai-libs/` 只补 adoption path”的证据顺序
|
||||
- 旧模板迁移失真风险:旧 `vitepress-*` skill 的模板和规范若原样沿用,可能继续输出过时结构
|
||||
- 缓解措施:只迁移可复用骨架,把输出优先级和证据规则重写进统一 skill
|
||||
- 统一入口过宽风险:若 `gframework-doc-refresh` 的触发描述过宽,可能在模块不明确时误进入文档生成
|
||||
- 缓解措施:要求先做模块归一化;遇到栏目别名歧义时只返回建议,不直接生成文档
|
||||
- Active 入口回膨胀风险:后续若把栏目级重写过程直接追加到 active 文档,会再次拖慢恢复
|
||||
- 缓解措施:阶段完成并验证后,继续把细节迁入本 topic 的 `archive/`
|
||||
- review 跟进遗漏风险:如果 PR review 抓取继续优先选中空 review body,会漏掉 CodeRabbit 的 Nitpick 和
|
||||
linter 跟进项
|
||||
- 缓解措施:保持当前“最新提交 + 最新非空 CodeRabbit review body”解析策略,并在有疑点时以 API 实抓结果复核
|
||||
- reviewer 适配漂移风险:若后续新增 AI reviewer 但脚本仍只维护固定 bot 名单,可能再次出现“线程能看见、skill 却未声明覆盖”的偏差
|
||||
- 缓解措施:当前已显式支持 `coderabbitai[bot]` 与 `greptile-apps[bot]`;新增 reviewer 时同步更新
|
||||
`.agents/skills/gframework-pr-review/SKILL.md`、`agents/openai.yaml` 与抓取脚本常量表
|
||||
|
||||
## 活跃文档
|
||||
|
||||
- 历史跟踪归档:[documentation-governance-and-refresh-history-through-2026-04-18.md](../archive/todos/documentation-governance-and-refresh-history-through-2026-04-18.md)
|
||||
- 历史 trace 归档:[documentation-governance-and-refresh-history-through-2026-04-18.md](../archive/traces/documentation-governance-and-refresh-history-through-2026-04-18.md)
|
||||
- RP-001 到 RP-008 trace 归档:[documentation-governance-and-refresh-rp-001-through-rp-008.md](../archive/traces/documentation-governance-and-refresh-rp-001-through-rp-008.md)
|
||||
|
||||
## 验证说明
|
||||
|
||||
- 旧 `local-plan/` 的详细实施历史与文档站构建结果已迁入主题内归档
|
||||
- active 跟踪文件已按 `ai-plan` 治理规则精简为当前恢复入口
|
||||
- `cd docs && bun run build`
|
||||
- `python3 .codex/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --format json`
|
||||
- `python3 -B .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --branch docs/sdk-update-documentation --format json --json-output /tmp/current-pr-review.json`
|
||||
- `python3 -B .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --branch docs/sdk-update-documentation --section open-threads`
|
||||
- `python3 -B -c "import ast, pathlib; path=pathlib.Path('.agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py'); tree=ast.parse(path.read_text(encoding='utf-8')); funcs=[node for node in ast.walk(tree) if isinstance(node,(ast.FunctionDef, ast.AsyncFunctionDef))]; documented=sum(1 for node in funcs if ast.get_docstring(node)); print(f'functions={len(funcs)} documented={documented} coverage={documented/len(funcs):.2%}')"`
|
||||
- `bash .agents/skills/gframework-doc-refresh/scripts/validate-code-blocks.sh docs/zh-CN/game/scene.md`
|
||||
- `bash .agents/skills/gframework-doc-refresh/scripts/validate-code-blocks.sh docs/zh-CN/game/ui.md`
|
||||
- `bash -lc 'source .agents/skills/_shared/module-config.sh && get_readme_paths Core.SourceGenerators.Abstractions && if get_readme_paths Not.Real.Module; then exit 1; else echo unmapped-ok; fi'`
|
||||
- `python3 -c "import pathlib, yaml; text = pathlib.Path('.agents/skills/gframework-doc-refresh/SKILL.md').read_text(); yaml.safe_load(text.split('---', 2)[1]); print('yaml-ok')"`
|
||||
- `python3 .agents/skills/gframework-doc-refresh/scripts/scan_module_evidence.py Core`
|
||||
- `python3 .agents/skills/gframework-doc-refresh/scripts/scan_module_evidence.py Godot.SourceGenerators`
|
||||
- `python3 .agents/skills/gframework-doc-refresh/scripts/scan_module_evidence.py Cqrs`
|
||||
|
||||
## 下一步
|
||||
|
||||
1. 继续核对 Godot 相关生成器页面,优先处理 `godot-project-generator.md`、`get-node-generator.md` 与
|
||||
`bind-node-signal-generator.md`
|
||||
2. 重点确认 `project.godot`、`AutoLoad` / `InputActions`、`GetNode` / `BindNodeSignal` 示例仍与当前包关系和生成器入口一致
|
||||
3. 若 active trace 再积累新的已完成阶段,按恢复点粒度迁入 `archive/traces/`,避免默认启动入口再次膨胀
|
||||
`bind-node-signal-generator.md`,优先用 `gframework-doc-refresh` 的模块扫描结果驱动判断
|
||||
2. 下一次推送后先重新执行 `$gframework-pr-review`,确认 PR #268 的 CodeRabbit / Greptile open thread 是否按预期收敛
|
||||
3. 再继续确认 `project.godot`、`AutoLoad` / `InputActions`、`GetNode` / `BindNodeSignal` 示例仍与当前包关系和生成器入口一致
|
||||
|
||||
@ -1,183 +1,41 @@
|
||||
# Documentation Governance And Refresh 追踪
|
||||
# Documentation Governance And Refresh Trace
|
||||
|
||||
## 2026-04-19
|
||||
## 2026-04-22
|
||||
|
||||
### 阶段:local-plan 迁移收口(RP-001)
|
||||
### 当前恢复点:RP-010
|
||||
|
||||
- 复核当前工作树后确认:worktree 根目录仅剩一个 legacy `local-plan/`,其内容属于文档治理与重写主题的
|
||||
durable recovery state,不应继续作为独立根目录入口存在
|
||||
- 按 `ai-plan` 治理规则建立 `ai-plan/public/documentation-governance-and-refresh/` 主题目录,并补齐:
|
||||
- `todos/`
|
||||
- `traces/`
|
||||
- `archive/todos/`
|
||||
- `archive/traces/`
|
||||
- 将原 `local-plan` 中的详细 tracking / trace 迁入主题内历史归档,并为 active 入口只保留当前恢复点、
|
||||
活跃事实、风险与下一步
|
||||
- 在 `ai-plan/public/README.md` 中建立
|
||||
`docs/sdk-update-documentation` -> `documentation-governance-and-refresh` 的 worktree 映射
|
||||
- 同步更新 `ai-plan-governance` 的 tracking / trace,记录本次迁移已验证当前工作树不再依赖 worktree-root
|
||||
`local-plan/`
|
||||
- 本轮从 PR #268 的最新 review 数据恢复,未发现失败检查;CTRF 报告显示 2139 个测试全部通过
|
||||
- 本轮复核确认当前 PR 的 latest-head open thread 同时来自 `coderabbitai[bot]` 与 `greptile-apps[bot]`
|
||||
- 已本地修复仍然成立的 review:
|
||||
- `docs/zh-CN/game/scene.md` 把“推荐目录与文件约定(项目侧)”降为“最小接入路径”下的子节
|
||||
- `docs/zh-CN/game/ui.md` 为“最小接入路径”补充导语,并修复同级标题错位
|
||||
- `.agents/skills/gframework-doc-refresh/scripts/validate-code-blocks.sh` 改成 opening / closing fence 状态机
|
||||
- `.agents/skills/_shared/module-config.sh` 补齐缺失模块映射,并让未映射模块返回非零退出码
|
||||
- `gframework-pr-review` 已从文案和输出模型两侧补齐多 reviewer 支持:当前 JSON 会单独给出 `review_agents`
|
||||
以及 `open_thread_counts_by_user`,文本输出会显式列出 CodeRabbit / Greptile
|
||||
- `fetch_current_pr_review.py` 的本地函数 docstring 覆盖率已补到 `44/44`
|
||||
- 已闭环 RP-001 到 RP-008 的执行细节已归档到
|
||||
`ai-plan/public/documentation-governance-and-refresh/archive/traces/documentation-governance-and-refresh-rp-001-through-rp-008.md`
|
||||
|
||||
### Archive Context
|
||||
### 当前决策
|
||||
|
||||
- 历史跟踪归档:
|
||||
- `ai-plan/public/documentation-governance-and-refresh/archive/todos/documentation-governance-and-refresh-history-through-2026-04-18.md`
|
||||
- 历史 trace 归档:
|
||||
- `ai-plan/public/documentation-governance-and-refresh/archive/traces/documentation-governance-and-refresh-history-through-2026-04-18.md`
|
||||
- active trace 只保留当前恢复点、关键事实、验证和下一步;完成阶段继续进入 `archive/traces/`
|
||||
- `scene.md` 与 `ui.md` 的集成说明除目录布局外,也要保证标题层级能真实反映采用路径语义
|
||||
- `gframework-pr-review` 继续以 latest-head unresolved thread 为主信号,同时显式声明支持的 AI reviewer 名单,避免 skill
|
||||
声明与实际抓取能力再次漂移
|
||||
|
||||
### 下一步(RP-001)
|
||||
### 验证
|
||||
|
||||
1. 后续继续该主题时,只从 `ai-plan/public/documentation-governance-and-refresh/` 进入,不再恢复 `local-plan/`
|
||||
2. 若 active 入口再次积累多轮已完成且已验证阶段,继续按同一模式迁入该主题自己的 `archive/`
|
||||
|
||||
## 2026-04-21
|
||||
|
||||
### 阶段:栏目 landing page 收口(RP-002)
|
||||
|
||||
- 依据 `ai-plan/public/README.md` 的 worktree 映射恢复 `documentation-governance-and-refresh` 主题,并确认该分支下一步应优先处理 `docs/zh-CN/core/*`、`game/*` 与 `source-generators/*`
|
||||
- 复核 `docs/zh-CN/core/index.md`、`docs/zh-CN/game/index.md`、`docs/zh-CN/source-generators/index.md` 后确认:这三页仍保留旧版“大而全教程”结构,与当前模块 README、包拆分关系和推荐接入路径明显漂移
|
||||
- 对照 `GFramework.Core/README.md`、`GFramework.Game/README.md`、`GFramework.Core.SourceGenerators/README.md`、
|
||||
`GFramework.Game.SourceGenerators/README.md`、`GFramework.Cqrs.SourceGenerators/README.md` 与
|
||||
`GFramework.Godot.SourceGenerators/README.md`,重写三个栏目 landing page,使其回到“模块定位、包关系、最小接入路径、继续阅读”的可信入口形态
|
||||
- 首次执行 `cd docs && bun run build` 时发现 VitePress 会把跳到 `docs/` 目录外的相对链接判定为 dead link,因此将 landing page 末尾的模块 README 入口改为纯文本路径提示而非站内链接
|
||||
- 第二次执行 `cd docs && bun run build` 通过,说明当前 landing page 重写没有破坏站点构建
|
||||
|
||||
### 当前结论
|
||||
|
||||
- 当前默认导航入口已显著收敛,但专题页仍需逐页按源码与测试继续核对
|
||||
- 后续优先级应从 `core` 专题页开始,再向 `game` 与 `source-generators` 扩展
|
||||
|
||||
### 下一步(RP-002)
|
||||
|
||||
1. 审核 `docs/zh-CN/core/architecture.md`、`context.md`、`lifecycle.md`、`command.md`、`query.md`、`cqrs.md`
|
||||
2. 记录每页的失真点、真实 API 名称与应保留的最小示例
|
||||
3. 完成一轮专题页重写后再次执行 `cd docs && bun run build`
|
||||
|
||||
### 补充:2026-04-21 内容引用迁移
|
||||
|
||||
- 按当前文档治理主题,继续清理活跃规范与面向读者的内容入口中的旧参考仓库命名
|
||||
- `AGENTS.md` 已把“secondary evidence source”从特定项目名收口为 `ai-libs/` 下的已验证只读参考实现
|
||||
- `GFramework.Game/README.md`、`GFramework.Game.Abstractions/README.md` 与
|
||||
`docs/zh-CN/game/index.md` 已同步改为 `ai-libs/` 参考表述,并去掉特定参考项目名称与项目内类型名线索
|
||||
- `documentation-governance-and-refresh` active tracking 已同步把风险缓解中的参考来源更新为
|
||||
`ai-libs/` 下已验证参考实现
|
||||
- 下一次专题页重写时,继续沿用同一表述,不再把特定参考项目名写入新的活跃文档入口
|
||||
|
||||
### 补充:2026-04-21 Core 专题页收口(RP-003)
|
||||
|
||||
- 复核 `docs/zh-CN/core/architecture.md`、`context.md`、`lifecycle.md`、`command.md`、`query.md` 与 `cqrs.md`
|
||||
后确认:这些页面仍大量保留旧 API 叙述,例如 `Init()`、属性式 `CommandBus` / `QueryBus`、旧 `Input`
|
||||
赋值式命令/查询示例,以及已移除的 `RegisterMediatorBehavior`
|
||||
- 对照 `Architecture`、`ArchitectureContext`、`IArchitectureContext`、`ContextAwareBase`、旧
|
||||
`AbstractCommand` / `AbstractQuery` 基类和 `GFramework.Cqrs/README.md` 后,重写上述六个页面
|
||||
- 新版专题页将结构统一为“当前角色、真实公开入口、最小示例、兼容边界、迁移方向”,避免继续复刻旧版大而全教程
|
||||
- `core/context.md` 已明确把 `GameContext` 收束为兼容回退路径,而不是新代码的推荐接法
|
||||
- `core/command.md` 与 `core/query.md` 已明确旧体系仍可用,但新功能应优先走 `GFramework.Cqrs`
|
||||
- `core/cqrs.md` 已与当前 runtime / generator / handler 注册语义对齐,并明确 `RegisterCqrsPipelineBehavior<TBehavior>()`
|
||||
是公开入口
|
||||
- 执行 `cd docs && bun run build` 通过,说明本轮 `core` 专题页重写没有破坏文档站构建
|
||||
|
||||
### 下一步(RP-003)
|
||||
|
||||
### 补充:2026-04-21 PR review 跟进收口(RP-004)
|
||||
|
||||
- 通过 `gframework-pr-review` 复查当前分支 PR 时发现:脚本把同一 head commit 上空 body 的 `APPROVED`
|
||||
review 误当成“最新 review body”,导致 `Nitpick comments` 未被结构化提取
|
||||
- 对照 GitHub API 的 review 列表后,确认真正包含 `Nitpick comments (2)` 的是更早 3 秒提交的
|
||||
`COMMENTED` review;因此调整脚本为“保持最新 review 元数据输出不变,但解析时优先选择同一提交上的最新非空
|
||||
CodeRabbit review body”
|
||||
- 根据重新提取的 Nitpick 内容,补齐 `docs/zh-CN/core/index.md` 里 `Godot` 与 `Source Generators`
|
||||
栏目的可点击链接
|
||||
- 顺手修正 active trace 中重复的 `### 下一步` 标题,消除 `MD024/no-duplicate-heading` 告警,避免后续 PR
|
||||
review 再次把文档治理入口本身标成噪音
|
||||
|
||||
### 验证(RP-004)
|
||||
|
||||
- `python3 .codex/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --format json`
|
||||
- `python3 -B .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --branch docs/sdk-update-documentation --format json --json-output /tmp/current-pr-review.json`
|
||||
- `python3 -B .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --branch docs/sdk-update-documentation --section open-threads`
|
||||
- `python3 -B -c "import ast, pathlib; path=pathlib.Path('.agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py'); tree=ast.parse(path.read_text(encoding='utf-8')); funcs=[node for node in ast.walk(tree) if isinstance(node,(ast.FunctionDef, ast.AsyncFunctionDef))]; documented=sum(1 for node in funcs if ast.get_docstring(node)); print(f'functions={len(funcs)} documented={documented} coverage={documented/len(funcs):.2%}')"`
|
||||
- `bash .agents/skills/gframework-doc-refresh/scripts/validate-code-blocks.sh docs/zh-CN/game/scene.md`
|
||||
- `bash .agents/skills/gframework-doc-refresh/scripts/validate-code-blocks.sh docs/zh-CN/game/ui.md`
|
||||
- `bash -lc 'source .agents/skills/_shared/module-config.sh && get_readme_paths Core.SourceGenerators.Abstractions && if get_readme_paths Not.Real.Module; then exit 1; else echo unmapped-ok; fi'`
|
||||
- `cd docs && bun run build`
|
||||
|
||||
### 下一步(RP-004)
|
||||
### 下一步
|
||||
|
||||
1. 继续处理 `docs/zh-CN/core/events.md`、`property.md`、`state-management.md`、`coroutine.md`、`logging.md`
|
||||
2. 若 active trace 继续累计多个已完成恢复点,按 `archive/traces/` 粒度归档旧阶段细节
|
||||
3. 保持 PR review 跟进时优先验证最新未解决线程、非空 CodeRabbit review body 与 MegaLinter 明确告警
|
||||
|
||||
### 阶段:Core 剩余高风险专题页核对(RP-005)
|
||||
|
||||
- 依据 `documentation-governance-and-refresh` active tracking 的恢复点,继续核对
|
||||
`docs/zh-CN/core/events.md`、`property.md`、`state-management.md`、`coroutine.md`、`logging.md`
|
||||
- 对照 `GFramework.Core/Events/*`、`Property/*`、`Logging/*`、`StateManagement/*`、`Coroutine/*` 以及对应测试后确认:
|
||||
- `events.md`、`property.md` 与 `logging.md` 仍带有旧版“大而全 API 列表”写法,与当前公开入口和推荐边界不匹配
|
||||
- `state-management.md` 与 `coroutine.md` 已和当前 runtime / 测试语义基本对齐,本轮无需为了统一文风做额外重写
|
||||
- 重写 `events.md`,使其回到“上下文入口、`EventBus` / `EnhancedEventBus`、优先级传播、局部事件对象、与 Store / CQRS
|
||||
的边界”的当前结构
|
||||
- 重写 `property.md`,使其回到“字段级响应式值、何时继续使用 `BindableProperty<T>`、何时切到 `Store<TState>`”的当前结构,
|
||||
并补充 `BindableProperty<T>.Comparer` 按闭合泛型共享的兼容注意点
|
||||
- 重写 `logging.md`,使其回到“`LoggerFactoryResolver` 默认行为、`ArchitectureConfiguration` 日志 provider 配置、
|
||||
`IStructuredLogger` / `LogContext`、provider 替换边界”的当前结构
|
||||
- 执行 `cd docs && bun run build` 通过,说明本轮 `core` 专题页收口没有破坏文档站构建
|
||||
|
||||
### 当前结论(RP-005)
|
||||
|
||||
- 本轮计划中的 `core` 剩余高风险页面已完成核对;`state-management` 与 `coroutine` 经复核后可继续保留
|
||||
- `core` 栏目下一步不再需要围绕这五页反复停留,后续重心应转到 `docs/zh-CN/game/*` 与 `docs/zh-CN/source-generators/*`
|
||||
|
||||
### 下一步(RP-005)
|
||||
|
||||
1. 继续核对 `docs/zh-CN/game/*`,优先处理仍引用旧安装方式、旧状态系统或旧 UI / Scene 接法的页面
|
||||
2. 再推进 `docs/zh-CN/source-generators/*`,重点核对生成器 wiring、包关系与最小接入示例
|
||||
3. 若 active trace 继续累计多个已完成恢复点,按 `archive/traces/` 粒度归档旧阶段细节
|
||||
|
||||
### 阶段:Game Scene / UI 专题页收口(RP-006)
|
||||
|
||||
- 依据 `documentation-governance-and-refresh` active tracking 的下一步,优先复核 `docs/zh-CN/game/scene.md` 与
|
||||
`docs/zh-CN/game/ui.md`
|
||||
- 对照 `GFramework.Game.Abstractions/Scene/*`、`GFramework.Game.Abstractions/UI/*`、`GFramework.Game/Scene/SceneRouterBase.cs`、
|
||||
`GFramework.Game/UI/UiRouterBase.cs`、`GFramework.Game/README.md` 与 `ai-libs/CoreGrid` 参考接法后确认:
|
||||
- `scene.md` 仍把场景系统写成框架自带完整注册/装配的一体化方案,没有突出 `ISceneFactory`、`ISceneRoot` 和项目侧
|
||||
router 派生类的责任边界
|
||||
- `ui.md` 仍按旧教程式结构展开,没有清楚区分 `Page` 栈与 `Overlay/Modal/Toast/Topmost` 层级 UI,也缺少当前
|
||||
`UiInteractionProfile`、`TryDispatchUiAction(...)` 与 World 输入阻断语义
|
||||
- 重写 `scene.md`,使其回到“当前公开入口、场景栈语义、最小接入路径、守卫/过渡处理器扩展点、与旧写法的边界”的结构
|
||||
- 重写 `ui.md`,使其回到“页面栈与层级 UI 分流、输入仲裁、暂停/阻断语义、最小接入路径、扩展点”的结构
|
||||
- 新版两页都明确了:factory、root、引擎节点与注册表仍由项目或适配层提供,框架当前提供的是 router 基类与通用编排
|
||||
|
||||
### 验证(RP-006)
|
||||
|
||||
- `cd docs && bun run build`
|
||||
|
||||
### 下一步(RP-006)
|
||||
|
||||
1. 继续核对 `docs/zh-CN/source-generators/*`,优先处理仍引用旧初始化方式、旧聚合包名或过时 generator wiring 的页面
|
||||
2. 重点复核 `priority-generator.md`、`context-aware-generator.md` 与 Godot 相关生成器页面,确认示例仍与当前 runtime /
|
||||
generator 入口一致
|
||||
3. 若 `source-generators` 出现多页连续收口结果,再按恢复点粒度整理 active trace,避免默认入口继续膨胀
|
||||
|
||||
### 阶段:Core Source Generator 关键专题页收口(RP-007)
|
||||
|
||||
- 依据 `documentation-governance-and-refresh` active tracking 的下一步,优先复核
|
||||
`docs/zh-CN/source-generators/context-aware-generator.md` 与 `priority-generator.md`
|
||||
- 对照 `GFramework.Core.SourceGenerators/Rule/ContextAwareGenerator.cs`、`GFramework.Core/Rule/ContextAwareBase.cs`、
|
||||
`GFramework.Core/Extensions/ContextAwareServiceExtensions.cs`、`GFramework.Core.SourceGenerators/Bases/PriorityGenerator.cs`、
|
||||
`GFramework.Core.Abstractions/Architectures/IArchitectureContext.cs` 与相关诊断定义后确认:
|
||||
- `context-aware-generator.md` 仍在展示旧版简化生成代码,没有说明当前实例缓存、类型级共享 provider、同步锁以及
|
||||
`ContextAwareBase` 的不同默认回退路径
|
||||
- `priority-generator.md` 仍把 `[Priority]` 写成“标了就自动改变顺序”的教程式功能说明,并大量使用
|
||||
`GetAllByPriority<T>()`、`system.Init()` 这类不适合作为当前 `IContextAware` 路径默认示例的旧写法
|
||||
- 重写 `context-aware-generator.md`,使其回到“最小用法、当前生成成员、provider 与实例缓存语义、与 `ContextAwareBase`
|
||||
和 Context Get 注入的关系、测试边界”的结构
|
||||
- 重写 `priority-generator.md`,使其回到“只生成 `IPrioritized`、priority-aware API 在不同层上的入口、动态优先级边界、
|
||||
诊断与约束”的结构
|
||||
- 新版两页都明确了:排序效果取决于调用方是否走 priority-aware API;`[ContextAware]` 生成路径与
|
||||
`ContextAwareBase` 不是同一套默认行为
|
||||
|
||||
### 验证(RP-007)
|
||||
|
||||
- `cd docs && bun run build`
|
||||
|
||||
### 下一步(RP-007)
|
||||
|
||||
1. 继续核对 Godot 相关生成器页面,优先处理 `godot-project-generator.md`、`get-node-generator.md` 与
|
||||
`bind-node-signal-generator.md`
|
||||
2. 重点确认 `project.godot`、`AutoLoad` / `InputActions`、`GetNode` / `BindNodeSignal` 示例仍与当前包关系和生成器入口一致
|
||||
3. 若 Godot 页面也出现连续收口结果,再按恢复点粒度整理 active trace,避免默认入口继续膨胀
|
||||
1. 下一次推送后重新执行 `$gframework-pr-review`,确认 PR #268 的 CodeRabbit / Greptile open thread 是否关闭或减少
|
||||
2. 继续使用 `gframework-doc-refresh` 对 `Godot.SourceGenerators` 做真实模块扫描
|
||||
3. 优先刷新 `godot-project-generator.md`、`get-node-generator.md` 与 `bind-node-signal-generator.md`
|
||||
|
||||
@ -85,6 +85,44 @@ description: 说明 GFramework.Game 场景路由的当前入口、项目侧接
|
||||
|
||||
推荐按下面的顺序接入。
|
||||
|
||||
### 推荐目录与文件约定(项目侧)
|
||||
|
||||
场景系统的目录结构不由框架强制,但建议把“路由编排、实例创建、引擎挂载、业务场景”分开放置,避免后续把
|
||||
`SceneRouterBase` 派生类写成巨型协调器。
|
||||
|
||||
```text
|
||||
Game/Scene/
|
||||
GameSceneRouter.cs
|
||||
GameSceneFactory.cs
|
||||
SceneRoot.cs
|
||||
Scenes/
|
||||
GameplayScene.cs
|
||||
PauseMenuScene.cs
|
||||
Params/
|
||||
GameplayEnterParam.cs
|
||||
Registry/
|
||||
SceneRegistry.cs
|
||||
```
|
||||
|
||||
推荐约定如下:
|
||||
|
||||
- `GameSceneRouter.cs`:项目侧 router,继承 `SceneRouterBase`,只注册 guard、transition handler 和 around handler
|
||||
- `GameSceneFactory.cs`:实现 `ISceneFactory`,负责 `sceneKey -> ISceneBehavior` 的映射与实例创建
|
||||
- `SceneRoot.cs`:实现 `ISceneRoot`,负责把行为对象对应的引擎节点挂到场景容器并移除
|
||||
- `Scenes/*`:放具体业务场景、行为包装器或引擎节点包装类型
|
||||
- `Params/*`:放实现 `ISceneEnterParam` 的进入参数,按业务场景拆分
|
||||
- `Registry/*`:如果项目已有场景表或资源表,建议收口在这里,再由 `GameSceneFactory` 使用
|
||||
|
||||
最小 wiring 通常是:
|
||||
|
||||
```csharp
|
||||
architecture.RegisterUtility<ISceneFactory>(new GameSceneFactory());
|
||||
architecture.RegisterSystem(new GameSceneRouter());
|
||||
```
|
||||
|
||||
然后在 `SceneRoot` 的引擎生命周期就绪点调用 `BindRoot(this)`。如果项目已有不同的资源目录、节点层级或场景注册表,
|
||||
保留原结构即可;只要最终能提供 `ISceneFactory`、`ISceneRoot` 和 `ISceneBehavior`,就不需要为了框架重排所有文件。
|
||||
|
||||
### 1. 准备项目自己的 router
|
||||
|
||||
```csharp
|
||||
|
||||
@ -138,6 +138,46 @@ description: 说明 GFramework.Game UI 路由当前的页面栈、层级 UI、
|
||||
|
||||
## 最小接入路径
|
||||
|
||||
推荐按下面的顺序接入。
|
||||
|
||||
### 推荐目录与文件约定(项目侧)
|
||||
|
||||
UI 系统的接入文件建议按“路由、工厂、根节点、页面行为、入参”拆开。这样可以让 `UiRouterBase` 只承担编排职责,
|
||||
把引擎节点创建和页面业务逻辑留在项目侧。
|
||||
|
||||
```text
|
||||
Game/UI/
|
||||
GameUiRouter.cs
|
||||
GameUiFactory.cs
|
||||
UiRoot.cs
|
||||
Pages/
|
||||
MainMenuPageBehavior.cs
|
||||
SettingsPageBehavior.cs
|
||||
Params/
|
||||
SettingsEnterParam.cs
|
||||
Views/
|
||||
MainMenuView.cs
|
||||
```
|
||||
|
||||
推荐约定如下:
|
||||
|
||||
- `GameUiRouter.cs`:项目侧 router,继承 `UiRouterBase`,只注册 UI transition handler 与 guard
|
||||
- `GameUiFactory.cs`:实现 `IUiFactory`,负责 `uiKey -> IUiPageBehavior` 的映射与实例创建
|
||||
- `UiRoot.cs`:实现 `IUiRoot`,负责按 `UiLayer` 把页面行为挂到真实 UI 容器
|
||||
- `Pages/*PageBehavior.cs`:放实现 `IUiPageBehavior` 的页面行为;使用 Godot 生成器时可由 `AutoUiPage` 相关样板补齐
|
||||
- `Params/*EnterParam.cs`:放实现 `IUiPageEnterParam` 的页面入参
|
||||
- `Views/*`:放项目引擎层视图包装或节点引用,不建议把导航决策写在视图里
|
||||
|
||||
最小 wiring 通常是:
|
||||
|
||||
```csharp
|
||||
architecture.RegisterUtility<IUiFactory>(new GameUiFactory());
|
||||
architecture.RegisterSystem(new GameUiRouter());
|
||||
```
|
||||
|
||||
随后在 `UiRoot` 的引擎生命周期就绪点调用 `_uiRouter.BindRoot(this)`。如果项目已经按功能域组织 UI 文件,也可以保留
|
||||
原目录;关键是让 `*Router` 只做编排、`*Factory` 只做映射与创建、`*Root` 只做容器挂载,页面行为只表达页面自身语义。
|
||||
|
||||
### 1. 提供项目自己的 router
|
||||
|
||||
```csharp
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user