mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-12 22:03:30 +08:00
Compare commits
27 Commits
79934f79b0
...
617e0bffd2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
617e0bffd2 | ||
|
|
f777cdebd7 | ||
|
|
195658a217 | ||
|
|
524d54d3de | ||
|
|
9f04c0b5f8 | ||
|
|
3ca095e987 | ||
|
|
16e04cc541 | ||
|
|
d7ddff9f53 | ||
|
|
b194238385 | ||
|
|
3446896118 | ||
|
|
0c7552e629 | ||
|
|
65d3634181 | ||
|
|
3a3359b495 | ||
|
|
c39cb5c9dc | ||
|
|
d3d62cf454 | ||
|
|
1b199e9f17 | ||
|
|
67675a02f7 | ||
|
|
a75194337e | ||
|
|
b96565ffa3 | ||
|
|
10daba3add | ||
|
|
4edfe53cd9 | ||
|
|
58ba6c011e | ||
|
|
9f6204d6e2 | ||
|
|
a7fa70e4fe | ||
|
|
1b85b53292 | ||
|
|
be26640b58 | ||
|
|
6a704f3aa7 |
@ -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 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".
|
||||
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, greptile-apps, or gemini-code-assist, 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", "extract Gemini comments", or "check Failed Tests on the PR".
|
||||
---
|
||||
|
||||
# GFramework PR Review
|
||||
@ -20,7 +20,7 @@ Shortcut: `$gframework-pr-review`
|
||||
- fetch PR metadata, issue comments, reviews, and review comments through the GitHub API
|
||||
- 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]`
|
||||
- capture unresolved latest-head review threads for supported AI reviewers, including `coderabbitai[bot]`, `greptile-apps[bot]`, and `gemini-code-assist[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
|
||||
@ -53,7 +53,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]`
|
||||
- Supported AI reviewer summary, including latest reviews and open-thread counts for `coderabbitai[bot]`, `greptile-apps[bot]`, and `gemini-code-assist[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
|
||||
@ -72,7 +72,7 @@ The script should produce:
|
||||
- If the current WSL session resolves `git.exe` but cannot execute it cleanly, keep using the explicit Linux worktree binding instead of retrying Windows Git.
|
||||
- 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.
|
||||
- Do not assume every AI reviewer behaves like CodeRabbit. `greptile-apps[bot]` and `gemini-code-assist[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.
|
||||
@ -84,5 +84,6 @@ The script should produce:
|
||||
- `Use $gframework-pr-review on the current branch`
|
||||
- `Check the current PR and extract CodeRabbit suggestions`
|
||||
- `Check the current PR and extract Greptile suggestions`
|
||||
- `Check the current PR and extract Gemini Code Assist suggestions`
|
||||
- `Look for Failed Tests on the PR page`
|
||||
- `先用 $gframework-pr-review 看当前分支 PR`
|
||||
|
||||
@ -29,6 +29,7 @@ WORK_TREE_ENVIRONMENT_KEY = "GFRAMEWORK_WORK_TREE"
|
||||
USER_AGENT = "codex-gframework-pr-review"
|
||||
CODERABBIT_LOGIN = "coderabbitai[bot]"
|
||||
GREPTILE_LOGIN = "greptile-apps[bot]"
|
||||
GEMINI_CODE_ASSIST_LOGIN = "gemini-code-assist[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)
|
||||
@ -47,6 +48,12 @@ SUPPORTED_AI_REVIEWERS = (
|
||||
"display_name": "Greptile",
|
||||
"supports_review_body_parsing": False,
|
||||
},
|
||||
{
|
||||
"slug": "gemini-code-assist",
|
||||
"login": GEMINI_CODE_ASSIST_LOGIN,
|
||||
"display_name": "Gemini Code Assist",
|
||||
"supports_review_body_parsing": False,
|
||||
},
|
||||
)
|
||||
DISPLAY_SECTION_CHOICES = (
|
||||
"pr",
|
||||
|
||||
66
.github/actions/validate-pat/action.yml
vendored
Normal file
66
.github/actions/validate-pat/action.yml
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
name: Validate PAT
|
||||
description: Validate that the release PAT can access the repository and push tags.
|
||||
|
||||
inputs:
|
||||
pat-token:
|
||||
description: Personal access token used by semantic-release.
|
||||
required: true
|
||||
repo-api-url:
|
||||
description: GitHub repository API URL, for example https://api.github.com/repos/owner/repo.
|
||||
required: true
|
||||
repository:
|
||||
description: Repository slug used in error messages.
|
||||
required: true
|
||||
missing-token-message:
|
||||
description: Error message emitted when the PAT is absent.
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Validate PAT can push
|
||||
shell: bash
|
||||
env:
|
||||
PAT_TOKEN: ${{ inputs.pat-token }}
|
||||
REPO_API_URL: ${{ inputs.repo-api-url }}
|
||||
REPOSITORY: ${{ inputs.repository }}
|
||||
MISSING_TOKEN_MESSAGE: ${{ inputs.missing-token-message }}
|
||||
run: |
|
||||
if [ -z "${PAT_TOKEN}" ]; then
|
||||
echo "::error::${MISSING_TOKEN_MESSAGE}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
response_file="$(mktemp)"
|
||||
trap 'rm -f "${response_file}"' EXIT
|
||||
|
||||
status_code="$(
|
||||
curl -sS -o "${response_file}" -w "%{http_code}" \
|
||||
-H "Authorization: Bearer ${PAT_TOKEN}" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
"${REPO_API_URL}"
|
||||
)"
|
||||
|
||||
case "${status_code}" in
|
||||
200)
|
||||
# The repository endpoint returns 200 for read-only tokens as well.
|
||||
# semantic-release still performs a remote push probe, so require push permission here.
|
||||
push_ok="$(jq -r '.permissions.push // false' "${response_file}")"
|
||||
if [ "${push_ok}" != "true" ]; then
|
||||
echo "::error::PAT_TOKEN can read ${REPOSITORY} but lacks push permission. semantic-release requires contents:write."
|
||||
cat "${response_file}"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
401|403)
|
||||
echo "::error::PAT_TOKEN is invalid or lacks access to ${REPOSITORY} (HTTP ${status_code})."
|
||||
cat "${response_file}"
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
echo "::error::Failed to validate PAT_TOKEN against ${REPO_API_URL} (HTTP ${status_code})."
|
||||
cat "${response_file}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
179
.github/workflows/auto-tag.yml
vendored
179
.github/workflows/auto-tag.yml
vendored
@ -1,66 +1,159 @@
|
||||
name: Auto Increment Version and Tag
|
||||
name: Semantic Release Version and Tag
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["CI - Build & Test"]
|
||||
types:
|
||||
- completed
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: auto-tag-main
|
||||
group: semantic-release-main
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
auto-tag:
|
||||
preview:
|
||||
if: >
|
||||
(
|
||||
github.event_name == 'workflow_run' &&
|
||||
github.event.workflow_run.conclusion == 'success' &&
|
||||
contains(github.event.workflow_run.head_commit.message, '[release ci]')
|
||||
)
|
||||
||
|
||||
(
|
||||
github.event_name == 'workflow_dispatch' &&
|
||||
github.ref == 'refs/heads/main'
|
||||
)
|
||||
|
||||
github.ref == 'refs/heads/main'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
contents: read
|
||||
outputs:
|
||||
tagged: ${{ steps.create_tag.outcome == 'success' }}
|
||||
published: ${{ steps.semantic_release.outputs.new_release_published }}
|
||||
last_tag: ${{ steps.semantic_release.outputs.last_release_git_tag }}
|
||||
next_version: ${{ steps.semantic_release.outputs.new_release_version }}
|
||||
next_tag: ${{ steps.semantic_release.outputs.new_release_git_tag }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
ref: ${{ github.sha }}
|
||||
|
||||
- name: Get next version
|
||||
id: version
|
||||
run: |
|
||||
LATEST_TAG=$(git tag --list "v*" --sort=-v:refname | head -n 1)
|
||||
LATEST_TAG=${LATEST_TAG:-v0.0.0}
|
||||
VERSION=${LATEST_TAG#v}
|
||||
IFS=. read MAJOR MINOR PATCH <<< "$VERSION"
|
||||
PATCH=$((PATCH+1))
|
||||
echo "new_tag=v$MAJOR.$MINOR.$PATCH" >> $GITHUB_OUTPUT
|
||||
# semantic-release 在 dry-run 中仍会执行一次 git push --dry-run 权限探测。
|
||||
# 这里提前要求与正式 release 相同的 PAT,避免 github-actions[bot] 因只读上下文触发误导性的 403。
|
||||
- name: Validate PAT token
|
||||
uses: ./.github/actions/validate-pat
|
||||
with:
|
||||
pat-token: ${{ secrets.PAT_TOKEN }}
|
||||
repo-api-url: ${{ github.api_url }}/repos/${{ github.repository }}
|
||||
repository: ${{ github.repository }}
|
||||
missing-token-message: PAT_TOKEN is required because semantic-release preview performs a git push --dry-run permission check.
|
||||
|
||||
- name: Create tag
|
||||
# preview 始终先运行,用于给当前 SHA 生成待发布版本预览。
|
||||
- name: Semantic release preview
|
||||
id: semantic_release
|
||||
uses: cycjimmy/semantic-release-action@v6
|
||||
with:
|
||||
dry_run: true
|
||||
ci: false
|
||||
extra_plugins: |
|
||||
conventional-changelog-conventionalcommits@9.1.0
|
||||
env:
|
||||
PAT: ${{ secrets.PAT_TOKEN }}
|
||||
TAG: ${{ steps.version.outputs.new_tag }}
|
||||
GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }}
|
||||
|
||||
- name: Show preview result
|
||||
run: |
|
||||
set -e
|
||||
git config user.name "GitHub Action"
|
||||
git config user.email "action@github.com"
|
||||
echo "published=${{ steps.semantic_release.outputs.new_release_published }}"
|
||||
echo "last_tag=${{ steps.semantic_release.outputs.last_release_git_tag }}"
|
||||
echo "next_version=${{ steps.semantic_release.outputs.new_release_version }}"
|
||||
echo "next_tag=${{ steps.semantic_release.outputs.new_release_git_tag }}"
|
||||
|
||||
if git show-ref --tags --verify --quiet "refs/tags/$TAG"; then
|
||||
echo "Tag $TAG already exists, skipping"
|
||||
exit 0
|
||||
fi
|
||||
- name: Write preview summary
|
||||
env:
|
||||
RELEASE_PUBLISHED: ${{ steps.semantic_release.outputs.new_release_published }}
|
||||
RELEASE_NOTES: ${{ steps.semantic_release.outputs.new_release_notes }}
|
||||
run: |
|
||||
{
|
||||
echo "## Semantic Release Preview"
|
||||
echo
|
||||
echo "- Commit: \`${{ github.sha }}\`"
|
||||
echo "- Release needed: \`${{ steps.semantic_release.outputs.new_release_published }}\`"
|
||||
echo "- Last tag: \`${{ steps.semantic_release.outputs.last_release_git_tag }}\`"
|
||||
echo "- Next version: \`${{ steps.semantic_release.outputs.new_release_version }}\`"
|
||||
echo "- Next tag: \`${{ steps.semantic_release.outputs.new_release_git_tag }}\`"
|
||||
echo "- Preview auth: uses \`PAT_TOKEN\` because semantic-release dry-run still performs a remote push permission probe."
|
||||
echo "- Snapshot semantics: this preview is pinned to dispatch SHA \`${{ github.sha }}\`; commits added to \`main\` after the run starts are not included."
|
||||
if [ "${RELEASE_PUBLISHED}" = "true" ] && [ -n "${RELEASE_NOTES}" ]; then
|
||||
echo
|
||||
echo "<details><summary>Preview release notes</summary>"
|
||||
echo
|
||||
printf '%s\n' "${RELEASE_NOTES}"
|
||||
echo
|
||||
echo "</details>"
|
||||
fi
|
||||
echo
|
||||
echo "If the version looks correct, approve the \`release-approval\` environment to continue."
|
||||
} >> "${GITHUB_STEP_SUMMARY}"
|
||||
|
||||
git tag -a "$TAG" -m "Auto tag $TAG"
|
||||
git push "https://x-access-token:${PAT}@github.com/${{ github.repository }}.git" "$TAG"
|
||||
release:
|
||||
if: >
|
||||
github.ref == 'refs/heads/main' &&
|
||||
needs.preview.result == 'success' &&
|
||||
needs.preview.outputs.published == 'true'
|
||||
needs:
|
||||
- preview
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
environment:
|
||||
name: release-approval
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
ref: ${{ github.sha }}
|
||||
|
||||
- name: Validate PAT token
|
||||
uses: ./.github/actions/validate-pat
|
||||
with:
|
||||
pat-token: ${{ secrets.PAT_TOKEN }}
|
||||
repo-api-url: ${{ github.api_url }}/repos/${{ github.repository }}
|
||||
repository: ${{ github.repository }}
|
||||
missing-token-message: PAT_TOKEN is required because a tag created with GITHUB_TOKEN will not trigger publish.yml.
|
||||
|
||||
- name: Semantic release
|
||||
id: semantic_release
|
||||
uses: cycjimmy/semantic-release-action@v6
|
||||
with:
|
||||
dry_run: false
|
||||
extra_plugins: |
|
||||
conventional-changelog-conventionalcommits@9.1.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }}
|
||||
|
||||
- name: Show release result
|
||||
run: |
|
||||
echo "published=${{ steps.semantic_release.outputs.new_release_published }}"
|
||||
echo "preview_last_tag=${{ needs.preview.outputs.last_tag }}"
|
||||
echo "preview_next_version=${{ needs.preview.outputs.next_version }}"
|
||||
echo "preview_next_tag=${{ needs.preview.outputs.next_tag }}"
|
||||
echo "last_tag=${{ steps.semantic_release.outputs.last_release_git_tag }}"
|
||||
echo "next_version=${{ steps.semantic_release.outputs.new_release_version }}"
|
||||
echo "next_tag=${{ steps.semantic_release.outputs.new_release_git_tag }}"
|
||||
|
||||
- name: Write release summary
|
||||
env:
|
||||
RELEASE_PUBLISHED: ${{ steps.semantic_release.outputs.new_release_published }}
|
||||
RELEASE_NOTES: ${{ steps.semantic_release.outputs.new_release_notes }}
|
||||
run: |
|
||||
{
|
||||
echo "## Semantic Release Publish"
|
||||
echo
|
||||
echo "- Commit: \`${{ github.sha }}\`"
|
||||
echo "- Preview last tag: \`${{ needs.preview.outputs.last_tag }}\`"
|
||||
echo "- Preview next version: \`${{ needs.preview.outputs.next_version }}\`"
|
||||
echo "- Preview next tag: \`${{ needs.preview.outputs.next_tag }}\`"
|
||||
echo "- Published: \`${{ steps.semantic_release.outputs.new_release_published }}\`"
|
||||
echo "- Last tag: \`${{ steps.semantic_release.outputs.last_release_git_tag }}\`"
|
||||
echo "- Next version: \`${{ steps.semantic_release.outputs.new_release_version }}\`"
|
||||
echo "- Next tag: \`${{ steps.semantic_release.outputs.new_release_git_tag }}\`"
|
||||
echo "- Snapshot semantics: this publish run still uses dispatch SHA \`${{ github.sha }}\`; commits added to \`main\` after the preview started are excluded."
|
||||
if [ "${RELEASE_PUBLISHED}" = "true" ] && [ -n "${RELEASE_NOTES}" ]; then
|
||||
echo
|
||||
echo "<details><summary>Published release notes</summary>"
|
||||
echo
|
||||
printf '%s\n' "${RELEASE_NOTES}"
|
||||
echo
|
||||
echo "</details>"
|
||||
fi
|
||||
} >> "${GITHUB_STEP_SUMMARY}"
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -23,5 +23,6 @@ ai-plan/public/*
|
||||
!ai-plan/public/**/*.md
|
||||
ai-plan/private/
|
||||
ai-libs/
|
||||
.codex
|
||||
# tool
|
||||
.venv/
|
||||
|
||||
82
.releaserc.json
Normal file
82
.releaserc.json
Normal file
@ -0,0 +1,82 @@
|
||||
{
|
||||
"branches": [
|
||||
"main"
|
||||
],
|
||||
"tagFormat": "v${version}",
|
||||
"plugins": [
|
||||
[
|
||||
"@semantic-release/commit-analyzer",
|
||||
{
|
||||
"preset": "conventionalcommits",
|
||||
"releaseRules": [
|
||||
{
|
||||
"breaking": true,
|
||||
"release": "major"
|
||||
},
|
||||
{
|
||||
"revert": true,
|
||||
"release": "patch"
|
||||
},
|
||||
{
|
||||
"type": "feat",
|
||||
"release": "minor"
|
||||
},
|
||||
{
|
||||
"type": "fix",
|
||||
"release": "patch"
|
||||
},
|
||||
{
|
||||
"type": "perf",
|
||||
"release": "patch"
|
||||
},
|
||||
{
|
||||
"type": "refactor",
|
||||
"release": "patch"
|
||||
},
|
||||
{
|
||||
"type": "docs",
|
||||
"release": false
|
||||
},
|
||||
{
|
||||
"type": "test",
|
||||
"release": false
|
||||
},
|
||||
{
|
||||
"type": "chore",
|
||||
"release": false
|
||||
},
|
||||
{
|
||||
"type": "build",
|
||||
"release": false
|
||||
},
|
||||
{
|
||||
"type": "ci",
|
||||
"release": false
|
||||
},
|
||||
{
|
||||
"type": "style",
|
||||
"release": false
|
||||
}
|
||||
],
|
||||
"parserOpts": {
|
||||
"noteKeywords": [
|
||||
"BREAKING CHANGE",
|
||||
"BREAKING CHANGES"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
"@semantic-release/release-notes-generator",
|
||||
{
|
||||
"preset": "conventionalcommits",
|
||||
"parserOpts": {
|
||||
"noteKeywords": [
|
||||
"BREAKING CHANGE",
|
||||
"BREAKING CHANGES"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
24
AGENTS.md
24
AGENTS.md
@ -33,6 +33,13 @@ All AI agents and contributors must follow these rules when writing, reviewing,
|
||||
baseline from a non-incremental repository-root build by running `dotnet clean` and then `dotnet build`.
|
||||
- Contributors MUST NOT treat a repeated incremental `dotnet build` result as authoritative for warning inspection when
|
||||
a clean baseline has not been captured in the same round.
|
||||
- If a direct `dotnet clean`, `dotnet build`, or `dotnet test` command fails inside the agent sandbox with missing
|
||||
diagnostics, `Permission denied`, MSBuild pipe/socket errors, or other environment-only noise that does not match a
|
||||
normal shell invocation, contributors MUST request permission and rerun the same direct command outside the sandbox
|
||||
before concluding that the repository or toolchain is broken.
|
||||
- For repository truth, contributors MUST prefer the result of the original direct command executed outside the sandbox
|
||||
over sandbox-only failures, workaround-heavy variants, or speculative environment flags unless the user explicitly
|
||||
asks for a non-default command shape.
|
||||
- If the task changes multiple projects or shared abstractions, prefer a solution-level or affected-project
|
||||
`dotnet build ... -c Release`; otherwise use the smallest build command that still proves the result compiles.
|
||||
- When a task adds a feature or modifies code, contributors MUST run a Release build for every directly affected
|
||||
@ -48,6 +55,21 @@ All AI agents and contributors must follow these rules when writing, reviewing,
|
||||
- The commit `body` MUST use unordered list items, and each item MUST start with a verb such as `新增`、`修复`、`优化`、
|
||||
`更新`、`补充`、`重构`.
|
||||
- Each commit body bullet MUST describe one independent change point; avoid repeated or redundant descriptions.
|
||||
- Commit `type` MUST reflect release semantics instead of author intent:
|
||||
- Use `feat` only for user-facing or consumer-facing capability additions that should raise the next released version's
|
||||
`minor` segment.
|
||||
- Use `fix` for behavior corrections, `perf` for observable performance improvements, and `refactor` only for
|
||||
non-feature code restructuring; these should raise the next released version's `patch` segment.
|
||||
- Use `docs`、`test`、`chore`、`build`、`ci`、`style` for their literal categories; do not encode these changes as
|
||||
`feat` just because they feel important. These categories MUST NOT trigger a release.
|
||||
- Use `BREAKING CHANGE` in the commit footer or `!` after the type / scope header (for example `feat!:` or
|
||||
`feat(core)!:`) when the change should raise the next released version's `major` segment.
|
||||
- Documentation-only changes MUST NOT use `feat`, including new guides, refreshed examples, navigation updates, and
|
||||
adoption notes for existing capabilities. If a commit changes both product behavior and related docs, either split the
|
||||
commit or use `feat` only when the code/package behavior is the primary released change.
|
||||
- Contributors MUST avoid ambiguous scopes such as `feat(docs)` for documentation work. If the change only affects docs,
|
||||
prefer `docs(<module-or-area>)`; if it adds a real capability in a docs-related toolchain, use the scope of that
|
||||
actual subsystem instead of `docs`.
|
||||
- Keep technical terms in English when they are established project terms, such as `API`、`Model`、`System`.
|
||||
- When composing a multi-line commit body from shell commands, contributors MUST NOT rely on Bash `$"..."` quoting for
|
||||
newline escapes, because it passes literal `\n` sequences to Git. Use multiple `-m` flags or ANSI-C `$'...'`
|
||||
@ -235,6 +257,8 @@ All generated or modified code MUST include clear and meaningful comments where
|
||||
### Validation Commands
|
||||
|
||||
Use the smallest command set that proves the change, then expand if the change is cross-cutting.
|
||||
If a sandboxed agent run reports environment-specific .NET failures, rerun the same direct command outside the sandbox
|
||||
and treat that unsandboxed result as authoritative for validation and warning baselines.
|
||||
|
||||
```bash
|
||||
# Check warnings from the default repository build entrypoint
|
||||
|
||||
57
GFramework.Core.SourceGenerators.Abstractions/README.md
Normal file
57
GFramework.Core.SourceGenerators.Abstractions/README.md
Normal file
@ -0,0 +1,57 @@
|
||||
# GFramework.Core.SourceGenerators.Abstractions
|
||||
|
||||
`GFramework.Core.SourceGenerators.Abstractions` 存放 `Core` 侧源码生成器公开使用的 attribute 和最小契约定义。
|
||||
它不是单独推广的生成器包,而是 `GFramework.Core.SourceGenerators` 的支撑层。
|
||||
|
||||
## 目录定位
|
||||
|
||||
这里当前主要提供:
|
||||
|
||||
- 架构注册相关特性
|
||||
- `AutoRegisterModuleAttribute`
|
||||
- `RegisterModelAttribute`
|
||||
- `RegisterSystemAttribute`
|
||||
- `RegisterUtilityAttribute`
|
||||
- 规则与上下文注入特性
|
||||
- `ContextAwareAttribute`
|
||||
- `GetModelAttribute`
|
||||
- `GetModelsAttribute`
|
||||
- `GetSystemAttribute`
|
||||
- `GetSystemsAttribute`
|
||||
- `GetUtilityAttribute`
|
||||
- `GetUtilitiesAttribute`
|
||||
- `GetServiceAttribute`
|
||||
- `GetServicesAttribute`
|
||||
- `GetAllAttribute`
|
||||
- 其他通用生成器特性
|
||||
- `LogAttribute`
|
||||
- `PriorityAttribute`
|
||||
- `GenerateEnumExtensionsAttribute`
|
||||
|
||||
这些类型定义“消费端代码可以写什么 attribute”,而实际生成逻辑和 diagnostics 仍在
|
||||
`GFramework.Core.SourceGenerators` 中。
|
||||
|
||||
## 与相邻模块的关系
|
||||
|
||||
- `GFramework.Core.SourceGenerators`
|
||||
- 实际读取这里定义的 attribute,并生成代码或 diagnostics。
|
||||
- `GFramework.SourceGenerators.Common`
|
||||
- 为 `Core` 侧生成器提供共享基类和公共约束。
|
||||
|
||||
当前目录本身 `IsPackable=false`。对 NuGet 使用者来说,更实际的入口仍然是
|
||||
`GeWuYou.GFramework.Core.SourceGenerators`;这个 abstractions DLL 会跟随对应 analyzer 包一起交付。
|
||||
|
||||
## 什么时候需要读这里
|
||||
|
||||
- 你在确认某个 `Core` 侧 attribute 的可用参数和命名
|
||||
- 你在排查“文档写法”和 attribute 实际公开面是否一致
|
||||
- 你在扩展 `Core.SourceGenerators`,需要先确认契约层边界
|
||||
|
||||
如果你的目标只是开始使用生成器,优先回到 `Core.SourceGenerators` README 和 `source-generators` 栏目。
|
||||
|
||||
## 继续阅读
|
||||
|
||||
- [Core 源码生成器说明](../GFramework.Core.SourceGenerators/README.md)
|
||||
- [源码生成器总览](../docs/zh-CN/source-generators/index.md)
|
||||
- [Context Get 注入生成器](../docs/zh-CN/source-generators/context-get-generator.md)
|
||||
- [枚举扩展生成器](../docs/zh-CN/source-generators/enum-generator.md)
|
||||
@ -157,7 +157,7 @@ public class LogContextTests
|
||||
using (LogContext.Push("TaskId", "Task1"))
|
||||
{
|
||||
task1Values.Add(LogContext.Current["TaskId"]);
|
||||
await Task.Delay(50);
|
||||
await Task.Delay(50).ConfigureAwait(false);
|
||||
task1Values.Add(LogContext.Current["TaskId"]);
|
||||
}
|
||||
});
|
||||
@ -167,12 +167,12 @@ public class LogContextTests
|
||||
using (LogContext.Push("TaskId", "Task2"))
|
||||
{
|
||||
task2Values.Add(LogContext.Current["TaskId"]);
|
||||
await Task.Delay(50);
|
||||
await Task.Delay(50).ConfigureAwait(false);
|
||||
task2Values.Add(LogContext.Current["TaskId"]);
|
||||
}
|
||||
});
|
||||
|
||||
await Task.WhenAll(task1, task2);
|
||||
await Task.WhenAll(task1, task2).ConfigureAwait(false);
|
||||
|
||||
Assert.That(task1Values, Has.All.EqualTo("Task1"));
|
||||
Assert.That(task2Values, Has.All.EqualTo("Task2"));
|
||||
|
||||
@ -425,6 +425,8 @@ public class LoggerTests
|
||||
/// </summary>
|
||||
public sealed class TestLogger : AbstractLogger
|
||||
{
|
||||
private readonly List<LogEntry> _logs = new();
|
||||
|
||||
/// <summary>
|
||||
/// 初始化TestLogger的新实例
|
||||
/// </summary>
|
||||
@ -435,9 +437,9 @@ public sealed class TestLogger : AbstractLogger
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取记录的日志条目列表
|
||||
/// 获取按写入顺序保存的日志条目只读视图
|
||||
/// </summary>
|
||||
public List<LogEntry> Logs { get; } = new();
|
||||
public IReadOnlyList<LogEntry> Logs => _logs;
|
||||
|
||||
/// <summary>
|
||||
/// 将日志信息写入内部存储
|
||||
@ -447,7 +449,7 @@ public sealed class TestLogger : AbstractLogger
|
||||
/// <param name="exception">相关异常(可选)</param>
|
||||
protected override void Write(LogLevel level, string message, Exception? exception)
|
||||
{
|
||||
Logs.Add(new LogEntry(level, message, exception));
|
||||
_logs.Add(new LogEntry(level, message, exception));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -457,4 +459,4 @@ public sealed class TestLogger : AbstractLogger
|
||||
/// <param name="Message">日志消息</param>
|
||||
/// <param name="Exception">相关异常(可选)</param>
|
||||
public sealed record LogEntry(LogLevel Level, string Message, Exception? Exception);
|
||||
}
|
||||
}
|
||||
|
||||
@ -327,27 +327,7 @@ internal sealed class CqrsHandlerRegistrarTests
|
||||
[Test]
|
||||
public void RegisterHandlers_Should_Cache_Assembly_Metadata_Across_Containers()
|
||||
{
|
||||
var generatedAssembly = new Mock<Assembly>();
|
||||
generatedAssembly
|
||||
.SetupGet(static assembly => assembly.FullName)
|
||||
.Returns("GFramework.Core.Tests.Cqrs.CachedMetadataAssembly, Version=1.0.0.0");
|
||||
generatedAssembly
|
||||
.Setup(static assembly => assembly.GetCustomAttributes(typeof(CqrsHandlerRegistryAttribute), false))
|
||||
.Returns([new CqrsHandlerRegistryAttribute(typeof(PartialGeneratedNotificationHandlerRegistry))]);
|
||||
generatedAssembly
|
||||
.Setup(static assembly => assembly.GetCustomAttributes(typeof(CqrsReflectionFallbackAttribute), false))
|
||||
.Returns(
|
||||
[
|
||||
new CqrsReflectionFallbackAttribute(
|
||||
ReflectionFallbackNotificationContainer.ReflectionOnlyHandlerType.FullName!)
|
||||
]);
|
||||
generatedAssembly
|
||||
.Setup(static assembly => assembly.GetType(
|
||||
ReflectionFallbackNotificationContainer.ReflectionOnlyHandlerType.FullName!,
|
||||
false,
|
||||
false))
|
||||
.Returns(ReflectionFallbackNotificationContainer.ReflectionOnlyHandlerType);
|
||||
|
||||
var generatedAssembly = CreateCachedMetadataAssembly();
|
||||
var firstContainer = new MicrosoftDiContainer();
|
||||
var secondContainer = new MicrosoftDiContainer();
|
||||
|
||||
@ -356,43 +336,8 @@ internal sealed class CqrsHandlerRegistrarTests
|
||||
firstContainer.Freeze();
|
||||
secondContainer.Freeze();
|
||||
|
||||
var firstRegistrations = firstContainer.GetAll<INotificationHandler<GeneratedRegistryNotification>>()
|
||||
.Select(static handler => handler.GetType())
|
||||
.ToArray();
|
||||
var secondRegistrations = secondContainer.GetAll<INotificationHandler<GeneratedRegistryNotification>>()
|
||||
.Select(static handler => handler.GetType())
|
||||
.ToArray();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(
|
||||
firstRegistrations,
|
||||
Is.EqualTo(
|
||||
[
|
||||
typeof(GeneratedRegistryNotificationHandler),
|
||||
ReflectionFallbackNotificationContainer.ReflectionOnlyHandlerType
|
||||
]));
|
||||
Assert.That(
|
||||
secondRegistrations,
|
||||
Is.EqualTo(
|
||||
[
|
||||
typeof(GeneratedRegistryNotificationHandler),
|
||||
ReflectionFallbackNotificationContainer.ReflectionOnlyHandlerType
|
||||
]));
|
||||
});
|
||||
|
||||
generatedAssembly.Verify(
|
||||
static assembly => assembly.GetCustomAttributes(typeof(CqrsHandlerRegistryAttribute), false),
|
||||
Times.Once);
|
||||
generatedAssembly.Verify(
|
||||
static assembly => assembly.GetCustomAttributes(typeof(CqrsReflectionFallbackAttribute), false),
|
||||
Times.Once);
|
||||
generatedAssembly.Verify(
|
||||
static assembly => assembly.GetType(
|
||||
ReflectionFallbackNotificationContainer.ReflectionOnlyHandlerType.FullName!,
|
||||
false,
|
||||
false),
|
||||
Times.Once);
|
||||
AssertGeneratedRegistryNotificationHandlers(firstContainer, secondContainer);
|
||||
VerifyCachedMetadataAssemblyLookups(generatedAssembly);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -526,6 +471,93 @@ internal sealed class CqrsHandlerRegistrarTests
|
||||
ClearCache(GetRegistrarCacheField("SupportedHandlerInterfacesCache"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个携带 generated registry 与 reflection fallback 元数据的程序集替身,
|
||||
/// 用于验证 registrar 是否会跨容器复用程序集级元数据。
|
||||
/// </summary>
|
||||
private static Mock<Assembly> CreateCachedMetadataAssembly()
|
||||
{
|
||||
var generatedAssembly = new Mock<Assembly>();
|
||||
generatedAssembly
|
||||
.SetupGet(static assembly => assembly.FullName)
|
||||
.Returns("GFramework.Core.Tests.Cqrs.CachedMetadataAssembly, Version=1.0.0.0");
|
||||
generatedAssembly
|
||||
.Setup(static assembly => assembly.GetCustomAttributes(typeof(CqrsHandlerRegistryAttribute), false))
|
||||
.Returns([new CqrsHandlerRegistryAttribute(typeof(PartialGeneratedNotificationHandlerRegistry))]);
|
||||
generatedAssembly
|
||||
.Setup(static assembly => assembly.GetCustomAttributes(typeof(CqrsReflectionFallbackAttribute), false))
|
||||
.Returns(
|
||||
[
|
||||
new CqrsReflectionFallbackAttribute(
|
||||
ReflectionFallbackNotificationContainer.ReflectionOnlyHandlerType.FullName!)
|
||||
]);
|
||||
generatedAssembly
|
||||
.Setup(static assembly => assembly.GetType(
|
||||
ReflectionFallbackNotificationContainer.ReflectionOnlyHandlerType.FullName!,
|
||||
false,
|
||||
false))
|
||||
.Returns(ReflectionFallbackNotificationContainer.ReflectionOnlyHandlerType);
|
||||
return generatedAssembly;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 断言两个容器都获得了相同的 generated-registry 与 reflection-fallback 处理器集合。
|
||||
/// </summary>
|
||||
private static void AssertGeneratedRegistryNotificationHandlers(
|
||||
MicrosoftDiContainer firstContainer,
|
||||
MicrosoftDiContainer secondContainer)
|
||||
{
|
||||
var firstRegistrations = GetGeneratedRegistryNotificationHandlerTypes(firstContainer);
|
||||
var secondRegistrations = GetGeneratedRegistryNotificationHandlerTypes(secondContainer);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(firstRegistrations, Is.EqualTo(GetExpectedGeneratedRegistryNotificationHandlerTypes()));
|
||||
Assert.That(secondRegistrations, Is.EqualTo(GetExpectedGeneratedRegistryNotificationHandlerTypes()));
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取容器中针对 generated notification 的 handler 运行时类型列表。
|
||||
/// </summary>
|
||||
private static Type[] GetGeneratedRegistryNotificationHandlerTypes(MicrosoftDiContainer container)
|
||||
{
|
||||
return container.GetAll<INotificationHandler<GeneratedRegistryNotification>>()
|
||||
.Select(static handler => handler.GetType())
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取 generated registry 与 reflection fallback 共同组成的预期 handler 顺序。
|
||||
/// </summary>
|
||||
private static Type[] GetExpectedGeneratedRegistryNotificationHandlerTypes()
|
||||
{
|
||||
return
|
||||
[
|
||||
typeof(GeneratedRegistryNotificationHandler),
|
||||
ReflectionFallbackNotificationContainer.ReflectionOnlyHandlerType
|
||||
];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 断言程序集级 generated registry / fallback 元数据只会被读取一次。
|
||||
/// </summary>
|
||||
private static void VerifyCachedMetadataAssemblyLookups(Mock<Assembly> generatedAssembly)
|
||||
{
|
||||
generatedAssembly.Verify(
|
||||
static assembly => assembly.GetCustomAttributes(typeof(CqrsHandlerRegistryAttribute), false),
|
||||
Times.Once);
|
||||
generatedAssembly.Verify(
|
||||
static assembly => assembly.GetCustomAttributes(typeof(CqrsReflectionFallbackAttribute), false),
|
||||
Times.Once);
|
||||
generatedAssembly.Verify(
|
||||
static assembly => assembly.GetType(
|
||||
ReflectionFallbackNotificationContainer.ReflectionOnlyHandlerType.FullName!,
|
||||
false,
|
||||
false),
|
||||
Times.Once);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过反射读取 registrar 的静态缓存对象。
|
||||
/// </summary>
|
||||
|
||||
@ -21,6 +21,8 @@ namespace GFramework.Cqrs.Tests.Logging;
|
||||
/// </summary>
|
||||
public sealed class TestLogger : AbstractLogger
|
||||
{
|
||||
private readonly List<LogEntry> _logs = [];
|
||||
|
||||
/// <summary>
|
||||
/// 初始化测试日志记录器。
|
||||
/// </summary>
|
||||
@ -33,7 +35,7 @@ public sealed class TestLogger : AbstractLogger
|
||||
/// <summary>
|
||||
/// 获取当前测试期间捕获到的日志条目。
|
||||
/// </summary>
|
||||
public List<LogEntry> Logs { get; } = [];
|
||||
public IReadOnlyList<LogEntry> Logs => _logs;
|
||||
|
||||
/// <summary>
|
||||
/// 将日志写入内存,供断言使用。
|
||||
@ -43,7 +45,7 @@ public sealed class TestLogger : AbstractLogger
|
||||
/// <param name="exception">关联异常。</param>
|
||||
protected override void Write(LogLevel level, string message, Exception? exception)
|
||||
{
|
||||
Logs.Add(new LogEntry(level, message, exception));
|
||||
_logs.Add(new LogEntry(level, message, exception));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -366,11 +366,6 @@ public sealed class TestBehaviorRequestHandler : IRequestHandler<TestBehaviorReq
|
||||
}
|
||||
}
|
||||
|
||||
public static class TestLoggingBehavior
|
||||
{
|
||||
public static List<string> LoggedMessages { get; set; } = new();
|
||||
}
|
||||
|
||||
public sealed record TestValidatedRequest : IRequest<string>
|
||||
{
|
||||
public int Value { get; init; }
|
||||
@ -467,8 +462,19 @@ public sealed record TestCircuitBreakerRequest : IRequest<string>
|
||||
// 复杂场景相关类
|
||||
public class SagaData
|
||||
{
|
||||
public List<int> CompletedSteps { get; } = new();
|
||||
public List<int> CompensatedSteps { get; } = new();
|
||||
/// <summary>
|
||||
/// 获取 Saga 已成功执行的步骤集合。
|
||||
/// </summary>
|
||||
public IList<int> CompletedSteps { get; } = new List<int>();
|
||||
|
||||
/// <summary>
|
||||
/// 获取 Saga 失败后已执行补偿的步骤集合。
|
||||
/// </summary>
|
||||
public IList<int> CompensatedSteps { get; } = new List<int>();
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置 Saga 是否已经完整结束。
|
||||
/// </summary>
|
||||
public bool IsCompleted { get; set; }
|
||||
}
|
||||
|
||||
@ -489,7 +495,11 @@ public sealed record TestExternalServiceRequest : IRequest<string>
|
||||
public sealed record TestDatabaseRequest : IRequest<string>
|
||||
{
|
||||
public string Data { get; init; } = string.Empty;
|
||||
public List<string> Storage { get; init; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 获取或初始化用于模拟数据库写入的可变存储集合,同时避免泄漏具体集合实现。
|
||||
/// </summary>
|
||||
public IList<string> Storage { get; init; } = new List<string>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@ -60,11 +60,8 @@ public class EcsAdvancedTests
|
||||
InitializeEcsModule();
|
||||
_world!.Create(new Position(0, 0));
|
||||
|
||||
Assert.DoesNotThrow(() =>
|
||||
{
|
||||
World.Destroy(_world);
|
||||
_world = null;
|
||||
});
|
||||
World.Destroy(_world);
|
||||
_world = null;
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -124,7 +121,7 @@ public class EcsAdvancedTests
|
||||
|
||||
_world = _container!.Get<World>();
|
||||
|
||||
Assert.DoesNotThrow(() => _ecsModule.Update(1.0f));
|
||||
_ecsModule.Update(1.0f);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -211,4 +208,4 @@ public class EcsAdvancedTests
|
||||
Assert.That(_world.Has<Position>(entity3), Is.False);
|
||||
Assert.That(_world.Has<Velocity>(entity3), Is.True);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -167,7 +167,7 @@ public class GameConfigBootstrapTests
|
||||
|
||||
continueInitialization.Set();
|
||||
|
||||
Assert.DoesNotThrowAsync(() => firstInitializeTask);
|
||||
Assert.That(async () => await firstInitializeTask.ConfigureAwait(false), Throws.Nothing);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
|
||||
@ -198,11 +198,10 @@ public class GeneratedConfigConsumerIntegrationTests
|
||||
Assert.That(yaml.EndsWith("\n", StringComparison.Ordinal), Is.True);
|
||||
});
|
||||
|
||||
Assert.DoesNotThrow(() =>
|
||||
MonsterConfigBindings.ValidateYaml(_rootPath, "monster/generated.yaml", yaml));
|
||||
|
||||
Assert.DoesNotThrowAsync(async () =>
|
||||
await MonsterConfigBindings.ValidateYamlAsync(_rootPath, "monster/generated.yaml", yaml).ConfigureAwait(false));
|
||||
MonsterConfigBindings.ValidateYaml(_rootPath, "monster/generated.yaml", yaml);
|
||||
Assert.That(
|
||||
async () => await MonsterConfigBindings.ValidateYamlAsync(_rootPath, "monster/generated.yaml", yaml).ConfigureAwait(false),
|
||||
Throws.Nothing);
|
||||
|
||||
var invalidYaml = """
|
||||
id: 3
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using System.IO;
|
||||
using GFramework.Core.Abstractions.Events;
|
||||
using GFramework.Game.Abstractions.Config;
|
||||
using GFramework.Game.Config;
|
||||
|
||||
@ -2789,83 +2790,15 @@ public class YamlConfigLoaderTests
|
||||
[Test]
|
||||
public async Task EnableHotReload_Should_Keep_Previous_State_When_Contains_Reference_Dependency_Breaks()
|
||||
{
|
||||
CreateConfigFile(
|
||||
"item/potion.yaml",
|
||||
"""
|
||||
id: potion
|
||||
name: Potion
|
||||
""");
|
||||
CreateConfigFile(
|
||||
"monster/slime.yaml",
|
||||
"""
|
||||
id: 1
|
||||
name: Slime
|
||||
dropItemIds:
|
||||
- potion
|
||||
""");
|
||||
CreateSchemaFile(
|
||||
"schemas/item.schema.json",
|
||||
"""
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["id", "name"],
|
||||
"properties": {
|
||||
"id": { "type": "string" },
|
||||
"name": { "type": "string" }
|
||||
}
|
||||
}
|
||||
""");
|
||||
CreateSchemaFile(
|
||||
"schemas/monster.schema.json",
|
||||
"""
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["id", "name", "dropItemIds"],
|
||||
"properties": {
|
||||
"id": { "type": "integer" },
|
||||
"name": { "type": "string" },
|
||||
"dropItemIds": {
|
||||
"type": "array",
|
||||
"minContains": 1,
|
||||
"contains": {
|
||||
"type": "string",
|
||||
"x-gframework-ref-table": "item"
|
||||
},
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
var loader = new YamlConfigLoader(_rootPath)
|
||||
.RegisterTable<string, ItemConfigStub>("item", "item", "schemas/item.schema.json",
|
||||
static config => config.Id)
|
||||
.RegisterTable<int, MonsterDropArrayConfigStub>("monster", "monster", "schemas/monster.schema.json",
|
||||
static config => config.Id);
|
||||
var registry = new ConfigRegistry();
|
||||
await loader.LoadAsync(registry);
|
||||
|
||||
var reloadFailureTaskSource =
|
||||
new TaskCompletionSource<(string TableName, Exception Exception)>(TaskCreationOptions
|
||||
.RunContinuationsAsynchronously);
|
||||
var hotReload = loader.EnableHotReload(
|
||||
registry,
|
||||
onTableReloadFailed: (tableName, exception) =>
|
||||
reloadFailureTaskSource.TrySetResult((tableName, exception)),
|
||||
debounceDelay: TimeSpan.FromMilliseconds(150));
|
||||
var (loader, registry) = await CreateLoadedContainsReferenceHotReloadScenarioAsync().ConfigureAwait(false);
|
||||
var (reloadFailureTaskSource, hotReload) = EnableHotReloadWithFailureCapture(loader, registry);
|
||||
|
||||
try
|
||||
{
|
||||
CreateConfigFile(
|
||||
"item/potion.yaml",
|
||||
"""
|
||||
id: elixir
|
||||
name: Elixir
|
||||
""");
|
||||
CreateConfigFile("item/potion.yaml", UpdatedItemConfigContent);
|
||||
|
||||
var failure = await WaitForTaskWithinAsync(reloadFailureTaskSource.Task, TimeSpan.FromSeconds(5));
|
||||
var failure = await WaitForTaskWithinAsync(reloadFailureTaskSource.Task, TimeSpan.FromSeconds(5))
|
||||
.ConfigureAwait(false);
|
||||
var diagnosticException = failure.Exception as ConfigLoadException;
|
||||
|
||||
Assert.Multiple(() =>
|
||||
@ -2958,65 +2891,17 @@ public class YamlConfigLoaderTests
|
||||
[Test]
|
||||
public async Task EnableHotReload_Should_Support_Options_Object()
|
||||
{
|
||||
CreateConfigFile(
|
||||
"monster/slime.yaml",
|
||||
"""
|
||||
id: 1
|
||||
name: Slime
|
||||
hp: 10
|
||||
""");
|
||||
CreateSchemaFile(
|
||||
"schemas/monster.schema.json",
|
||||
"""
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["id", "name"],
|
||||
"properties": {
|
||||
"id": { "type": "integer" },
|
||||
"name": { "type": "string" },
|
||||
"hp": { "type": "integer" }
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
var loader = new YamlConfigLoader(_rootPath)
|
||||
.RegisterTable(
|
||||
new YamlConfigTableRegistrationOptions<int, MonsterConfigStub>(
|
||||
"monster",
|
||||
"monster",
|
||||
static config => config.Id)
|
||||
{
|
||||
SchemaRelativePath = "schemas/monster.schema.json"
|
||||
});
|
||||
var registry = new ConfigRegistry();
|
||||
await loader.LoadAsync(registry);
|
||||
|
||||
var reloadTaskSource = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
var hotReload = loader.EnableHotReload(
|
||||
registry,
|
||||
new YamlConfigHotReloadOptions
|
||||
{
|
||||
OnTableReloaded = tableName => reloadTaskSource.TrySetResult(tableName),
|
||||
DebounceDelay = TimeSpan.FromMilliseconds(150)
|
||||
});
|
||||
var (loader, registry) = await CreateLoadedMonsterHotReloadScenarioAsync(useOptionsObject: true)
|
||||
.ConfigureAwait(false);
|
||||
var (reloadTaskSource, hotReload) = EnableHotReloadWithReloadCapture(loader, registry, useOptionsObject: true);
|
||||
|
||||
try
|
||||
{
|
||||
CreateConfigFile(
|
||||
"monster/slime.yaml",
|
||||
"""
|
||||
id: 1
|
||||
name: Slime
|
||||
hp: 25
|
||||
""");
|
||||
CreateConfigFile("monster/slime.yaml", UpdatedMonsterConfigContent);
|
||||
|
||||
var tableName = await WaitForTaskWithinAsync(reloadTaskSource.Task, TimeSpan.FromSeconds(5));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(tableName, Is.EqualTo("monster"));
|
||||
Assert.That(registry.GetTable<int, MonsterConfigStub>("monster").Get(1).Hp, Is.EqualTo(25));
|
||||
});
|
||||
var tableName = await WaitForTaskWithinAsync(reloadTaskSource.Task, TimeSpan.FromSeconds(5))
|
||||
.ConfigureAwait(false);
|
||||
AssertMonsterHotReloadUpdated(tableName, registry);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@ -3050,60 +2935,15 @@ public class YamlConfigLoaderTests
|
||||
[Test]
|
||||
public async Task EnableHotReload_Should_Keep_Previous_Table_When_Schema_Change_Makes_Reload_Fail()
|
||||
{
|
||||
CreateConfigFile(
|
||||
"monster/slime.yaml",
|
||||
"""
|
||||
id: 1
|
||||
name: Slime
|
||||
hp: 10
|
||||
""");
|
||||
CreateSchemaFile(
|
||||
"schemas/monster.schema.json",
|
||||
"""
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["id", "name"],
|
||||
"properties": {
|
||||
"id": { "type": "integer" },
|
||||
"name": { "type": "string" },
|
||||
"hp": { "type": "integer" }
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
var loader = new YamlConfigLoader(_rootPath)
|
||||
.RegisterTable<int, MonsterConfigStub>("monster", "monster", "schemas/monster.schema.json",
|
||||
static config => config.Id);
|
||||
var registry = new ConfigRegistry();
|
||||
await loader.LoadAsync(registry);
|
||||
|
||||
var reloadFailureTaskSource =
|
||||
new TaskCompletionSource<(string TableName, Exception Exception)>(TaskCreationOptions
|
||||
.RunContinuationsAsynchronously);
|
||||
var hotReload = loader.EnableHotReload(
|
||||
registry,
|
||||
onTableReloadFailed: (tableName, exception) =>
|
||||
reloadFailureTaskSource.TrySetResult((tableName, exception)),
|
||||
debounceDelay: TimeSpan.FromMilliseconds(150));
|
||||
var (loader, registry) = await CreateLoadedMonsterHotReloadScenarioAsync().ConfigureAwait(false);
|
||||
var (reloadFailureTaskSource, hotReload) = EnableHotReloadWithFailureCapture(loader, registry);
|
||||
|
||||
try
|
||||
{
|
||||
CreateSchemaFile(
|
||||
"schemas/monster.schema.json",
|
||||
"""
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["id", "name", "rarity"],
|
||||
"properties": {
|
||||
"id": { "type": "integer" },
|
||||
"name": { "type": "string" },
|
||||
"hp": { "type": "integer" },
|
||||
"rarity": { "type": "string" }
|
||||
}
|
||||
}
|
||||
""");
|
||||
CreateSchemaFile("schemas/monster.schema.json", MonsterSchemaWithRarityContent);
|
||||
|
||||
var failure = await WaitForTaskWithinAsync(reloadFailureTaskSource.Task, TimeSpan.FromSeconds(5));
|
||||
var failure = await WaitForTaskWithinAsync(reloadFailureTaskSource.Task, TimeSpan.FromSeconds(5))
|
||||
.ConfigureAwait(false);
|
||||
var diagnosticException = failure.Exception as ConfigLoadException;
|
||||
|
||||
Assert.Multiple(() =>
|
||||
@ -3130,75 +2970,15 @@ public class YamlConfigLoaderTests
|
||||
[Test]
|
||||
public async Task EnableHotReload_Should_Keep_Previous_State_When_Dependency_Table_Breaks_Cross_Table_Reference()
|
||||
{
|
||||
CreateConfigFile(
|
||||
"item/potion.yaml",
|
||||
"""
|
||||
id: potion
|
||||
name: Potion
|
||||
""");
|
||||
CreateConfigFile(
|
||||
"monster/slime.yaml",
|
||||
"""
|
||||
id: 1
|
||||
name: Slime
|
||||
dropItemId: potion
|
||||
""");
|
||||
CreateSchemaFile(
|
||||
"schemas/item.schema.json",
|
||||
"""
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["id", "name"],
|
||||
"properties": {
|
||||
"id": { "type": "string" },
|
||||
"name": { "type": "string" }
|
||||
}
|
||||
}
|
||||
""");
|
||||
CreateSchemaFile(
|
||||
"schemas/monster.schema.json",
|
||||
"""
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["id", "name", "dropItemId"],
|
||||
"properties": {
|
||||
"id": { "type": "integer" },
|
||||
"name": { "type": "string" },
|
||||
"dropItemId": {
|
||||
"type": "string",
|
||||
"x-gframework-ref-table": "item"
|
||||
}
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
var loader = new YamlConfigLoader(_rootPath)
|
||||
.RegisterTable<string, ItemConfigStub>("item", "item", "schemas/item.schema.json",
|
||||
static config => config.Id)
|
||||
.RegisterTable<int, MonsterDropConfigStub>("monster", "monster", "schemas/monster.schema.json",
|
||||
static config => config.Id);
|
||||
var registry = new ConfigRegistry();
|
||||
await loader.LoadAsync(registry);
|
||||
|
||||
var reloadFailureTaskSource =
|
||||
new TaskCompletionSource<(string TableName, Exception Exception)>(TaskCreationOptions
|
||||
.RunContinuationsAsynchronously);
|
||||
var hotReload = loader.EnableHotReload(
|
||||
registry,
|
||||
onTableReloadFailed: (tableName, exception) =>
|
||||
reloadFailureTaskSource.TrySetResult((tableName, exception)),
|
||||
debounceDelay: TimeSpan.FromMilliseconds(150));
|
||||
var (loader, registry) = await CreateLoadedCrossTableReferenceHotReloadScenarioAsync().ConfigureAwait(false);
|
||||
var (reloadFailureTaskSource, hotReload) = EnableHotReloadWithFailureCapture(loader, registry);
|
||||
|
||||
try
|
||||
{
|
||||
CreateConfigFile(
|
||||
"item/potion.yaml",
|
||||
"""
|
||||
id: elixir
|
||||
name: Elixir
|
||||
""");
|
||||
CreateConfigFile("item/potion.yaml", UpdatedItemConfigContent);
|
||||
|
||||
var failure = await WaitForTaskWithinAsync(reloadFailureTaskSource.Task, TimeSpan.FromSeconds(5));
|
||||
var failure = await WaitForTaskWithinAsync(reloadFailureTaskSource.Task, TimeSpan.FromSeconds(5))
|
||||
.ConfigureAwait(false);
|
||||
var diagnosticException = failure.Exception as ConfigLoadException;
|
||||
|
||||
Assert.Multiple(() =>
|
||||
@ -3223,6 +3003,243 @@ public class YamlConfigLoaderTests
|
||||
}
|
||||
}
|
||||
|
||||
private const string ItemSchemaContent =
|
||||
"""
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["id", "name"],
|
||||
"properties": {
|
||||
"id": { "type": "string" },
|
||||
"name": { "type": "string" }
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
private const string InitialMonsterConfigContent =
|
||||
"""
|
||||
id: 1
|
||||
name: Slime
|
||||
hp: 10
|
||||
""";
|
||||
|
||||
private const string UpdatedMonsterConfigContent =
|
||||
"""
|
||||
id: 1
|
||||
name: Slime
|
||||
hp: 25
|
||||
""";
|
||||
|
||||
private const string MonsterSchemaContent =
|
||||
"""
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["id", "name"],
|
||||
"properties": {
|
||||
"id": { "type": "integer" },
|
||||
"name": { "type": "string" },
|
||||
"hp": { "type": "integer" }
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
private const string MonsterSchemaWithRarityContent =
|
||||
"""
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["id", "name", "rarity"],
|
||||
"properties": {
|
||||
"id": { "type": "integer" },
|
||||
"name": { "type": "string" },
|
||||
"hp": { "type": "integer" },
|
||||
"rarity": { "type": "string" }
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
private const string UpdatedItemConfigContent =
|
||||
"""
|
||||
id: elixir
|
||||
name: Elixir
|
||||
""";
|
||||
|
||||
private const string MonsterDropArrayConfigContent =
|
||||
"""
|
||||
id: 1
|
||||
name: Slime
|
||||
dropItemIds:
|
||||
- potion
|
||||
""";
|
||||
|
||||
private const string MonsterDropArraySchemaContent =
|
||||
"""
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["id", "name", "dropItemIds"],
|
||||
"properties": {
|
||||
"id": { "type": "integer" },
|
||||
"name": { "type": "string" },
|
||||
"dropItemIds": {
|
||||
"type": "array",
|
||||
"minContains": 1,
|
||||
"contains": {
|
||||
"type": "string",
|
||||
"x-gframework-ref-table": "item"
|
||||
},
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
private const string MonsterDropConfigContent =
|
||||
"""
|
||||
id: 1
|
||||
name: Slime
|
||||
dropItemId: potion
|
||||
""";
|
||||
|
||||
private const string MonsterDropSchemaContent =
|
||||
"""
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["id", "name", "dropItemId"],
|
||||
"properties": {
|
||||
"id": { "type": "integer" },
|
||||
"name": { "type": "string" },
|
||||
"dropItemId": {
|
||||
"type": "string",
|
||||
"x-gframework-ref-table": "item"
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
/// <summary>
|
||||
/// 创建并加载标准 monster 热重载夹具,供重载成功与 schema 失败场景复用。
|
||||
/// </summary>
|
||||
/// <param name="useOptionsObject">是否通过选项对象注册表。</param>
|
||||
/// <returns>已完成首次加载的加载器与注册表。</returns>
|
||||
private async Task<(YamlConfigLoader Loader, ConfigRegistry Registry)> CreateLoadedMonsterHotReloadScenarioAsync(
|
||||
bool useOptionsObject = false)
|
||||
{
|
||||
CreateConfigFile("monster/slime.yaml", InitialMonsterConfigContent);
|
||||
CreateSchemaFile("schemas/monster.schema.json", MonsterSchemaContent);
|
||||
|
||||
var loader = useOptionsObject
|
||||
? new YamlConfigLoader(_rootPath).RegisterTable(
|
||||
new YamlConfigTableRegistrationOptions<int, MonsterConfigStub>(
|
||||
"monster",
|
||||
"monster",
|
||||
static config => config.Id)
|
||||
{
|
||||
SchemaRelativePath = "schemas/monster.schema.json"
|
||||
})
|
||||
: new YamlConfigLoader(_rootPath)
|
||||
.RegisterTable<int, MonsterConfigStub>("monster", "monster", "schemas/monster.schema.json",
|
||||
static config => config.Id);
|
||||
var registry = new ConfigRegistry();
|
||||
await loader.LoadAsync(registry).ConfigureAwait(false);
|
||||
return (loader, registry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建并加载 contains 子 schema 引用场景,供热重载依赖回滚测试复用。
|
||||
/// </summary>
|
||||
/// <returns>已完成首次加载的加载器与注册表。</returns>
|
||||
private async Task<(YamlConfigLoader Loader, ConfigRegistry Registry)>
|
||||
CreateLoadedContainsReferenceHotReloadScenarioAsync()
|
||||
{
|
||||
var loader = CreateItemBackedMonsterLoader<MonsterDropArrayConfigStub>(
|
||||
MonsterDropArrayConfigContent,
|
||||
MonsterDropArraySchemaContent,
|
||||
static config => config.Id,
|
||||
("item/potion.yaml", "potion", "Potion"));
|
||||
var registry = new ConfigRegistry();
|
||||
await loader.LoadAsync(registry).ConfigureAwait(false);
|
||||
return (loader, registry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建并加载跨表单值引用场景,供热重载依赖回滚测试复用。
|
||||
/// </summary>
|
||||
/// <returns>已完成首次加载的加载器与注册表。</returns>
|
||||
private async Task<(YamlConfigLoader Loader, ConfigRegistry Registry)>
|
||||
CreateLoadedCrossTableReferenceHotReloadScenarioAsync()
|
||||
{
|
||||
var loader = CreateItemBackedMonsterLoader<MonsterDropConfigStub>(
|
||||
MonsterDropConfigContent,
|
||||
MonsterDropSchemaContent,
|
||||
static config => config.Id,
|
||||
("item/potion.yaml", "potion", "Potion"));
|
||||
var registry = new ConfigRegistry();
|
||||
await loader.LoadAsync(registry).ConfigureAwait(false);
|
||||
return (loader, registry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 以统一的失败回调配置启用热重载,避免每个测试重复接线相同的通知逻辑。
|
||||
/// </summary>
|
||||
/// <param name="loader">已完成首次加载的加载器。</param>
|
||||
/// <param name="registry">要复用的配置注册表。</param>
|
||||
/// <returns>失败通知任务源与取消注册句柄。</returns>
|
||||
private static (TaskCompletionSource<(string TableName, Exception Exception)> TaskSource, IUnRegister Registration)
|
||||
EnableHotReloadWithFailureCapture(YamlConfigLoader loader, ConfigRegistry registry)
|
||||
{
|
||||
var reloadFailureTaskSource =
|
||||
new TaskCompletionSource<(string TableName, Exception Exception)>(TaskCreationOptions
|
||||
.RunContinuationsAsynchronously);
|
||||
var hotReload = loader.EnableHotReload(
|
||||
registry,
|
||||
onTableReloadFailed: (tableName, exception) =>
|
||||
reloadFailureTaskSource.TrySetResult((tableName, exception)),
|
||||
debounceDelay: TimeSpan.FromMilliseconds(150));
|
||||
return (reloadFailureTaskSource, hotReload);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 以统一的成功回调配置启用热重载,避免相同的防抖与回调装配在测试中重复出现。
|
||||
/// </summary>
|
||||
/// <param name="loader">已完成首次加载的加载器。</param>
|
||||
/// <param name="registry">要复用的配置注册表。</param>
|
||||
/// <param name="useOptionsObject">是否通过选项对象启用热重载。</param>
|
||||
/// <returns>成功通知任务源与取消注册句柄。</returns>
|
||||
private static (TaskCompletionSource<string> TaskSource, IUnRegister Registration) EnableHotReloadWithReloadCapture(
|
||||
YamlConfigLoader loader,
|
||||
ConfigRegistry registry,
|
||||
bool useOptionsObject = false)
|
||||
{
|
||||
var reloadTaskSource = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
var hotReload = useOptionsObject
|
||||
? loader.EnableHotReload(
|
||||
registry,
|
||||
new YamlConfigHotReloadOptions
|
||||
{
|
||||
OnTableReloaded = tableName => reloadTaskSource.TrySetResult(tableName),
|
||||
DebounceDelay = TimeSpan.FromMilliseconds(150)
|
||||
})
|
||||
: loader.EnableHotReload(
|
||||
registry,
|
||||
onTableReloaded: tableName => reloadTaskSource.TrySetResult(tableName),
|
||||
debounceDelay: TimeSpan.FromMilliseconds(150));
|
||||
return (reloadTaskSource, hotReload);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 断言标准 monster 热重载成功后,通知表名与刷新后的生命值都符合预期。
|
||||
/// </summary>
|
||||
/// <param name="tableName">热重载回调返回的表名。</param>
|
||||
/// <param name="registry">承载刷新结果的注册表。</param>
|
||||
private static void AssertMonsterHotReloadUpdated(string tableName, ConfigRegistry registry)
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(tableName, Is.EqualTo("monster"));
|
||||
Assert.That(registry.GetTable<int, MonsterConfigStub>("monster").Get(1).Hp, Is.EqualTo(25));
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为对象数组 <c>contains</c> 子集匹配场景创建加载器,避免测试方法体被大段固定 schema 稀释。
|
||||
/// </summary>
|
||||
|
||||
@ -54,16 +54,15 @@ public sealed class YamlConfigTextValidatorTests
|
||||
}
|
||||
""");
|
||||
|
||||
Assert.DoesNotThrow(() =>
|
||||
YamlConfigTextValidator.Validate(
|
||||
"monster",
|
||||
schemaPath,
|
||||
"monster/generated.yaml",
|
||||
"""
|
||||
id: 1
|
||||
name: Slime
|
||||
hp: 10
|
||||
"""));
|
||||
YamlConfigTextValidator.Validate(
|
||||
"monster",
|
||||
schemaPath,
|
||||
"monster/generated.yaml",
|
||||
"""
|
||||
id: 1
|
||||
name: Slime
|
||||
hp: 10
|
||||
""");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -169,8 +168,7 @@ public sealed class YamlConfigTextValidatorTests
|
||||
hp: 10
|
||||
""";
|
||||
|
||||
Assert.DoesNotThrow(() =>
|
||||
YamlConfigTextValidator.Validate("monster", schemaPath, "monster/generated.yaml", yaml));
|
||||
YamlConfigTextValidator.Validate("monster", schemaPath, "monster/generated.yaml", yaml);
|
||||
|
||||
File.WriteAllText(
|
||||
schemaPath,
|
||||
|
||||
@ -23,6 +23,19 @@ namespace GFramework.Game.Internal;
|
||||
/// </remarks>
|
||||
internal static class VersionedMigrationRunner
|
||||
{
|
||||
/// <summary>
|
||||
/// 复用一次迁移链执行期间不会变化的上下文,避免多个 helper 重复传递同一组委托和消息元数据。
|
||||
/// </summary>
|
||||
/// <typeparam name="TData">迁移数据类型。</typeparam>
|
||||
/// <typeparam name="TMigration">迁移描述类型。</typeparam>
|
||||
private readonly record struct MigrationExecutionContext<TData, TMigration>(
|
||||
Func<TData, int> GetVersion,
|
||||
Func<TMigration, int> GetToVersion,
|
||||
Func<TMigration, TData, TData> ApplyMigration,
|
||||
string SubjectName,
|
||||
string MigrationKind)
|
||||
where TData : class;
|
||||
|
||||
/// <summary>
|
||||
/// 校验迁移注册是否表示一次有效的前向升级。
|
||||
/// </summary>
|
||||
@ -92,58 +105,154 @@ internal static class VersionedMigrationRunner
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(migrationKind);
|
||||
|
||||
var currentVersion = getVersion(data);
|
||||
if (currentVersion > targetVersion)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"{subjectName} is version {currentVersion}, which is newer than the current runtime version {targetVersion}.");
|
||||
}
|
||||
EnsureRuntimeVersionIsSupported(currentVersion, targetVersion, subjectName);
|
||||
|
||||
if (currentVersion == targetVersion)
|
||||
{
|
||||
return data;
|
||||
}
|
||||
|
||||
var context = new MigrationExecutionContext<TData, TMigration>(
|
||||
getVersion,
|
||||
getToVersion,
|
||||
applyMigration,
|
||||
subjectName,
|
||||
migrationKind);
|
||||
|
||||
var current = data;
|
||||
|
||||
while (currentVersion < targetVersion)
|
||||
{
|
||||
var migration = resolveMigration(currentVersion);
|
||||
if (migration is null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"No {migrationKind} is registered for {subjectName} from version {currentVersion}.");
|
||||
}
|
||||
|
||||
current = applyMigration(migration, current)
|
||||
?? throw new InvalidOperationException(
|
||||
$"{migrationKind} for {subjectName} from version {currentVersion} returned null.");
|
||||
|
||||
var migratedVersion = getVersion(current);
|
||||
var declaredTargetVersion = getToVersion(migration);
|
||||
|
||||
if (declaredTargetVersion != migratedVersion)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"{migrationKind} for {subjectName} declared target version {declaredTargetVersion} " +
|
||||
$"but returned version {migratedVersion}.");
|
||||
}
|
||||
|
||||
if (migratedVersion <= currentVersion)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"{migrationKind} for {subjectName} must advance beyond version {currentVersion}.");
|
||||
}
|
||||
|
||||
if (migratedVersion > targetVersion)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"{migrationKind} for {subjectName} produced version {migratedVersion}, " +
|
||||
$"which exceeds the current runtime version {targetVersion}.");
|
||||
}
|
||||
|
||||
currentVersion = migratedVersion;
|
||||
var migration = GetRequiredMigration(resolveMigration, currentVersion, in context);
|
||||
var result = ApplyMigrationStep(
|
||||
migration,
|
||||
current,
|
||||
currentVersion,
|
||||
targetVersion,
|
||||
in context);
|
||||
current = result.Data;
|
||||
currentVersion = result.Version;
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 拒绝比当前运行时更高的数据版本,避免迁移器在未知版本上继续执行。
|
||||
/// </summary>
|
||||
/// <param name="currentVersion">数据当前版本。</param>
|
||||
/// <param name="targetVersion">运行时支持的目标版本。</param>
|
||||
/// <param name="subjectName">迁移主体名称。</param>
|
||||
/// <exception cref="InvalidOperationException">数据版本高于运行时版本时抛出。</exception>
|
||||
private static void EnsureRuntimeVersionIsSupported(int currentVersion, int targetVersion, string subjectName)
|
||||
{
|
||||
if (currentVersion > targetVersion)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"{subjectName} is version {currentVersion}, which is newer than the current runtime version {targetVersion}.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析当前版本必须存在的下一步迁移器,避免在调用循环中重复拼接相同错误。
|
||||
/// </summary>
|
||||
/// <typeparam name="TData">迁移数据类型。</typeparam>
|
||||
/// <typeparam name="TMigration">迁移描述类型。</typeparam>
|
||||
/// <param name="resolveMigration">迁移解析委托。</param>
|
||||
/// <param name="currentVersion">当前版本。</param>
|
||||
/// <param name="context">本轮迁移链共享的执行上下文。</param>
|
||||
/// <returns>已解析的迁移器。</returns>
|
||||
/// <exception cref="InvalidOperationException">缺少迁移器时抛出。</exception>
|
||||
private static TMigration GetRequiredMigration<TData, TMigration>(
|
||||
Func<int, TMigration?> resolveMigration,
|
||||
int currentVersion,
|
||||
in MigrationExecutionContext<TData, TMigration> context)
|
||||
where TData : class
|
||||
{
|
||||
var migration = resolveMigration(currentVersion);
|
||||
if (migration is null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"No {context.MigrationKind} is registered for {context.SubjectName} from version {currentVersion}.");
|
||||
}
|
||||
|
||||
return migration;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行单步迁移并验证声明目标版本、结果版本与运行时上限之间的一致性。
|
||||
/// </summary>
|
||||
/// <typeparam name="TData">迁移数据类型。</typeparam>
|
||||
/// <typeparam name="TMigration">迁移描述类型。</typeparam>
|
||||
/// <param name="migration">当前步骤的迁移器。</param>
|
||||
/// <param name="current">迁移前数据。</param>
|
||||
/// <param name="currentVersion">迁移前版本。</param>
|
||||
/// <param name="targetVersion">运行时目标版本。</param>
|
||||
/// <param name="context">本轮迁移链共享的执行上下文。</param>
|
||||
/// <returns>迁移后的数据与新版本号。</returns>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// 迁移结果为空、声明目标版本不匹配、版本未前进或超出运行时版本时抛出。
|
||||
/// </exception>
|
||||
private static (TData Data, int Version) ApplyMigrationStep<TData, TMigration>(
|
||||
TMigration migration,
|
||||
TData current,
|
||||
int currentVersion,
|
||||
int targetVersion,
|
||||
in MigrationExecutionContext<TData, TMigration> context)
|
||||
where TData : class
|
||||
{
|
||||
var migratedData = context.ApplyMigration(migration, current)
|
||||
?? throw new InvalidOperationException(
|
||||
$"{context.MigrationKind} for {context.SubjectName} from version {currentVersion} returned null.");
|
||||
var migratedVersion = context.GetVersion(migratedData);
|
||||
ValidateMigrationResult(
|
||||
currentVersion,
|
||||
targetVersion,
|
||||
migratedVersion,
|
||||
context.GetToVersion(migration),
|
||||
in context);
|
||||
return (migratedData, migratedVersion);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 校验单步迁移结果与声明目标版本一致,并确保版本严格单调递增且不越过运行时版本。
|
||||
/// </summary>
|
||||
/// <typeparam name="TData">迁移数据类型。</typeparam>
|
||||
/// <typeparam name="TMigration">迁移描述类型。</typeparam>
|
||||
/// <param name="currentVersion">迁移前版本。</param>
|
||||
/// <param name="targetVersion">运行时目标版本。</param>
|
||||
/// <param name="migratedVersion">迁移后实际版本。</param>
|
||||
/// <param name="declaredTargetVersion">迁移器声明的目标版本。</param>
|
||||
/// <param name="context">本轮迁移链共享的执行上下文。</param>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// 声明目标版本不匹配、版本未前进或超出运行时版本时抛出。
|
||||
/// </exception>
|
||||
private static void ValidateMigrationResult<TData, TMigration>(
|
||||
int currentVersion,
|
||||
int targetVersion,
|
||||
int migratedVersion,
|
||||
int declaredTargetVersion,
|
||||
in MigrationExecutionContext<TData, TMigration> context)
|
||||
where TData : class
|
||||
{
|
||||
if (declaredTargetVersion != migratedVersion)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"{context.MigrationKind} for {context.SubjectName} declared target version {declaredTargetVersion} " +
|
||||
$"but returned version {migratedVersion}.");
|
||||
}
|
||||
|
||||
if (migratedVersion <= currentVersion)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"{context.MigrationKind} for {context.SubjectName} must advance beyond version {currentVersion}.");
|
||||
}
|
||||
|
||||
if (migratedVersion > targetVersion)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"{context.MigrationKind} for {context.SubjectName} produced version {migratedVersion}, " +
|
||||
$"which exceeds the current runtime version {targetVersion}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
51
GFramework.Godot.SourceGenerators.Abstractions/README.md
Normal file
51
GFramework.Godot.SourceGenerators.Abstractions/README.md
Normal file
@ -0,0 +1,51 @@
|
||||
# GFramework.Godot.SourceGenerators.Abstractions
|
||||
|
||||
`GFramework.Godot.SourceGenerators.Abstractions` 存放 Godot 侧源码生成器公开使用的 attribute 和最小辅助类型。
|
||||
它不是单独推广的消费包,而是 `GFramework.Godot.SourceGenerators` 的支撑层。
|
||||
|
||||
## 目录定位
|
||||
|
||||
这里当前主要提供:
|
||||
|
||||
- 节点与信号相关特性
|
||||
- `GetNodeAttribute`
|
||||
- `BindNodeSignalAttribute`
|
||||
- 场景 / UI / 集合注册相关特性
|
||||
- `AutoSceneAttribute`
|
||||
- `AutoUiPageAttribute`
|
||||
- `AutoRegisterExportedCollectionsAttribute`
|
||||
- `RegisterExportedCollectionAttribute`
|
||||
- Godot 项目级辅助类型
|
||||
- `AutoLoadAttribute`
|
||||
- `GodotModuleMarker`
|
||||
- `NodeLookupMode`
|
||||
|
||||
这些类型负责定义“消费端项目可以声明哪些特性与参数”,而具体的生成逻辑、diagnostics 和生命周期约束仍在
|
||||
`GFramework.Godot.SourceGenerators` 中。
|
||||
|
||||
## 与相邻模块的关系
|
||||
|
||||
- `GFramework.Godot.SourceGenerators`
|
||||
- 实际消费这里定义的 attribute,并生成节点注入、信号绑定、Scene / UI 包装与项目元数据辅助代码。
|
||||
- `GFramework.SourceGenerators.Common`
|
||||
- 为 Godot 侧生成器提供共享基础设施和通用约束。
|
||||
- `GFramework.Godot`
|
||||
- 提供运行时宿主与集成层;生成器只负责编译期辅助。
|
||||
|
||||
当前目录本身 `IsPackable=false`。对 NuGet 使用者来说,更实际的入口仍然是
|
||||
`GeWuYou.GFramework.Godot.SourceGenerators`;这个 abstractions DLL 会跟随对应 analyzer 包一起交付。
|
||||
|
||||
## 什么时候需要读这里
|
||||
|
||||
- 你在确认 `[GetNode]`、`[BindNodeSignal]`、`[AutoScene]` 等特性的公开参数语义
|
||||
- 你在排查生成器文档是否和 attribute 契约一致
|
||||
- 你在扩展 Godot 侧生成器,想先看清契约层边界
|
||||
|
||||
如果你的目标只是把生成器接进项目,优先回到 `Godot.SourceGenerators` README 和专题页。
|
||||
|
||||
## 继续阅读
|
||||
|
||||
- [Godot 源码生成器说明](../GFramework.Godot.SourceGenerators/README.md)
|
||||
- [源码生成器总览](../docs/zh-CN/source-generators/index.md)
|
||||
- [GetNode 生成器](../docs/zh-CN/source-generators/get-node-generator.md)
|
||||
- [BindNodeSignal 生成器](../docs/zh-CN/source-generators/bind-node-signal-generator.md)
|
||||
44
GFramework.SourceGenerators.Common/README.md
Normal file
44
GFramework.SourceGenerators.Common/README.md
Normal file
@ -0,0 +1,44 @@
|
||||
# GFramework.SourceGenerators.Common
|
||||
|
||||
`GFramework.SourceGenerators.Common` 是 `GFramework` 多个源码生成器共享的公共支撑目录。它不是独立推广的运行时或
|
||||
生成器采用入口,而是跟随各个 `*.SourceGenerators` 模块一起演化的内部基础设施。
|
||||
|
||||
## 目录定位
|
||||
|
||||
这个目录当前主要承载三类公共能力:
|
||||
|
||||
- 生成器基类
|
||||
- 例如 `AttributeClassGeneratorBase`、`AttributeEnumGeneratorBase`、`MetadataAttributeClassGeneratorBase`
|
||||
- 共享诊断
|
||||
- 例如 `Diagnostics/CommonDiagnostics.cs`
|
||||
- 共享符号与冲突处理
|
||||
- 例如 `Extensions/GeneratedMethodConflictExtensions.cs`
|
||||
|
||||
如果你在 `Core`、`CQRS`、`Game`、`Godot` 侧生成器里看到相似的诊断或生成冲突语义,通常就是这里在统一约束。
|
||||
|
||||
## 与相邻模块的关系
|
||||
|
||||
- `GFramework.Core.SourceGenerators`
|
||||
- 复用这里的生成器基类和部分通用 diagnostics。
|
||||
- `GFramework.Cqrs.SourceGenerators`
|
||||
- 以这里作为编译期公共依赖,并把公共 DLL 一起打进 analyzer 包。
|
||||
- `GFramework.Godot.SourceGenerators`
|
||||
- 同样复用这里的公共实现和共享约束。
|
||||
|
||||
这个目录当前 `IsPackable=false`,不作为独立安装包推广。对 NuGet 使用者来说,更实际的入口仍然是具体的
|
||||
`GeWuYou.GFramework.*.SourceGenerators` 包。
|
||||
|
||||
## 什么时候需要读这里
|
||||
|
||||
- 你在排查多个生成器共有的 diagnostics
|
||||
- 你想确认“为什么不同生成器对命名冲突采用同一套规则”
|
||||
- 你在扩展或维护生成器,而不是只消费它们
|
||||
|
||||
如果你的目标只是“选包并开始使用”,优先回到对应生成器模块 README 和文档专题页。
|
||||
|
||||
## 继续阅读
|
||||
|
||||
- [源码生成器总览](../docs/zh-CN/source-generators/index.md)
|
||||
- [Core 源码生成器说明](../GFramework.Core.SourceGenerators/README.md)
|
||||
- [CQRS 源码生成器说明](../GFramework.Cqrs.SourceGenerators/README.md)
|
||||
- [Godot 源码生成器说明](../GFramework.Godot.SourceGenerators/README.md)
|
||||
@ -4,7 +4,7 @@
|
||||
<PackageId>GeWuYou.GFramework</PackageId>
|
||||
<Authors>gewuyou</Authors>
|
||||
<Product>GeWuYou.GFramework</Product>
|
||||
<Description>404 not found</Description>
|
||||
<Description>Meta-package that aggregates GFramework.Core and GFramework.Game for a minimal runtime adoption path.</Description>
|
||||
<Copyright>Copyright © 2025</Copyright>
|
||||
<RepositoryUrl>https://github.com/GeWuYou/GFramework</RepositoryUrl>
|
||||
<PackageProjectUrl>https://github.com/GeWuYou/GFramework</PackageProjectUrl>
|
||||
|
||||
10
README.md
10
README.md
@ -13,6 +13,8 @@
|
||||
|
||||
- 第一次接触框架:[入门指南](docs/zh-CN/getting-started/index.md)
|
||||
- 想先跑一个最小例子:[快速开始](docs/zh-CN/getting-started/quick-start.md)
|
||||
- 想先确认该装哪些包:[安装配置](docs/zh-CN/getting-started/installation.md)
|
||||
- 想接入 AI-First 配置工作流:[配置系统](docs/zh-CN/game/config-system.md) / [VS Code 配置工具](docs/zh-CN/game/config-tool.md)
|
||||
- 已经知道要用哪个模块:直接进入对应模块的说明页
|
||||
|
||||
## 模块地图
|
||||
@ -39,9 +41,9 @@
|
||||
|
||||
| 目录 | 定位 | 跟随入口 |
|
||||
| --- | --- | --- |
|
||||
| `GFramework.Core.SourceGenerators.Abstractions` | `Core.SourceGenerators` 的内部契约层 | [Core.SourceGenerators 模块说明](GFramework.Core.SourceGenerators/README.md) |
|
||||
| `GFramework.Godot.SourceGenerators.Abstractions` | `Godot.SourceGenerators` 的内部契约层 | [Godot.SourceGenerators 模块说明](GFramework.Godot.SourceGenerators/README.md) |
|
||||
| `GFramework.SourceGenerators.Common` | 生成器家族共享的公共支撑代码 | [源码生成器总览](docs/zh-CN/source-generators/index.md) |
|
||||
| `GFramework.Core.SourceGenerators.Abstractions` | `Core.SourceGenerators` 的内部契约层 | [目录说明](GFramework.Core.SourceGenerators.Abstractions/README.md) |
|
||||
| `GFramework.Godot.SourceGenerators.Abstractions` | `Godot.SourceGenerators` 的内部契约层 | [目录说明](GFramework.Godot.SourceGenerators.Abstractions/README.md) |
|
||||
| `GFramework.SourceGenerators.Common` | 生成器家族共享的公共支撑代码 | [目录说明](GFramework.SourceGenerators.Common/README.md) |
|
||||
|
||||
## 文档导航
|
||||
|
||||
@ -64,7 +66,7 @@
|
||||
## 包选择
|
||||
|
||||
- `GeWuYou.GFramework`
|
||||
当前是聚合元包,只聚合 `GFramework.Core` 与 `GFramework.Game` 这两层运行时,适合快速试用。
|
||||
当前是聚合元包,只聚合 `GFramework.Core` 与 `GFramework.Game` 这两层运行时;不会自动带上 `Cqrs`、`Godot` 或任何 Source Generator,适合快速试用或先起一个最小运行时骨架。
|
||||
- `GeWuYou.GFramework.Core`
|
||||
推荐的最小起步包。先从核心运行时开始,再按需叠加 `Cqrs`、`Game`、`Godot` 和 Source Generators。
|
||||
- `GeWuYou.GFramework.*.Abstractions`
|
||||
|
||||
@ -43,6 +43,10 @@ help the current worktree land on the right recovery documents without scanning
|
||||
- Purpose: continue the data repository persistence hardening plus the settings / serialization follow-up backlog.
|
||||
- Tracking: `ai-plan/public/data-repository-persistence/todos/data-repository-persistence-tracking.md`
|
||||
- Trace: `ai-plan/public/data-repository-persistence/traces/data-repository-persistence-trace.md`
|
||||
- `semantic-release-versioning`
|
||||
- Purpose: migrate release version calculation from fixed patch bumps to semantic-release while keeping the existing tag-driven NuGet publish flow.
|
||||
- Tracking: `ai-plan/public/semantic-release-versioning/todos/semantic-release-versioning-tracking.md`
|
||||
- Trace: `ai-plan/public/semantic-release-versioning/traces/semantic-release-versioning-trace.md`
|
||||
|
||||
## Worktree To Active Topic Map
|
||||
|
||||
@ -63,6 +67,9 @@ help the current worktree land on the right recovery documents without scanning
|
||||
- Branch: `feat/data-repository-persistence`
|
||||
- Worktree hint: `GFramework-data-repository-persistence`
|
||||
- Priority 1: `data-repository-persistence`
|
||||
- Branch: `feat/semantic-release-versioning`
|
||||
- Worktree hint: `GFramework`
|
||||
- Priority 1: `semantic-release-versioning`
|
||||
- Branch: `docs/sdk-update-documentation`
|
||||
- Worktree hint: `GFramework-update-documentation`
|
||||
- Priority 1: `documentation-full-coverage-governance`
|
||||
|
||||
@ -0,0 +1,321 @@
|
||||
# Analyzer Warning Reduction 追踪历史(RP-062 至 RP-071)
|
||||
|
||||
## 范围说明
|
||||
|
||||
本归档承接 `RP-062` 至 `RP-071` 的 active trace 明细,保留 active 入口在 `RP-072` 压缩前的执行背景、验证里程碑与 PR review 跟进记录。
|
||||
|
||||
## superseded by
|
||||
|
||||
- [analyzer-warning-reduction-trace.md](../../traces/analyzer-warning-reduction-trace.md)
|
||||
|
||||
---
|
||||
|
||||
# Analyzer Warning Reduction 追踪
|
||||
|
||||
## 2026-04-25 — RP-071
|
||||
|
||||
### 阶段:同步 PR #291 latest-head 对 active todo 的唯一剩余线程
|
||||
|
||||
- 触发背景:
|
||||
- 用户再次显式要求执行 `$gframework-pr-review`,当前分支仍对应 PR `#291`
|
||||
- 最新抓取结果显示 latest-head open review thread 只剩 `1` 条,且不再指向源码 warning,而是指向 `ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md` 中已过时的 `.codex` 风险描述
|
||||
- `.gitignore` 已在当前分支的 `chore(git)` 提交中加入 `.codex`,因此 active todo 若继续保留“当前 worktree 仍存在未跟踪的 `.codex` 目录”会与当前 head 真值冲突
|
||||
- 主线程实施:
|
||||
- 重新运行 PR review 抓取脚本,确认 PR `#291` 仍为 `OPEN`,且 latest-head 唯一 open thread 是 CodeRabbit 针对 active todo 的文档同步建议
|
||||
- 更新 active todo 恢复点为 `RP-071`,将 `.gitignore` 纳入“已提交的低风险批次文件”,并移除已过时的 `.codex` 活跃风险描述
|
||||
- 在 active todo 中明确保留当前仍未采纳的两条 non-blocking nitpick:`VersionedMigrationRunner.cs` 的上下文一致性建议,以及 active trace 归档 RP-062 ~ RP-064 的建议
|
||||
- 验证里程碑:
|
||||
- `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --json-output <current-pr-review-json>`
|
||||
- 结果:成功;确认 PR `#291` latest-head open review threads 为 `1`,唯一路径为 `ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md:54`
|
||||
- `dotnet build`
|
||||
- 结果:成功;`0 Warning(s)`、`0 Error(s)`;该次为增量 Debug 构建,只作为本轮文档同步的完成校验,warning 权威基线仍保持 `RP-070` 记录的 `639 Warning(s)`
|
||||
- 当前结论:
|
||||
- 当前 PR `#291` 唯一仍成立的 latest-head thread 已被本地文档同步吸收,后续只需在新 head 推送后复核 GitHub 状态
|
||||
- 剩余 CodeRabbit nitpick 仍是可选整理项,本轮保持 analyzer-warning-reduction 的小写集策略,不把它们混入当前提交
|
||||
- 下一轮默认先推送并重新抓取 PR review;若 open thread 清零,则继续回到 warning 热点选择
|
||||
|
||||
## 2026-04-25 — RP-070
|
||||
|
||||
### 阶段:按 PR #291 latest-head review 收口仍有效的小批次,并刷新新的仓库根基线
|
||||
|
||||
- 触发背景:
|
||||
- 用户显式要求执行 `$gframework-pr-review`,当前分支对应 PR `#291`
|
||||
- 抓取结果显示 latest-head 只有 1 条未解决 review thread 指向 `AGENTS.md` 英文标点不一致;同时最新 CodeRabbit review body 还包含 `VersionedMigrationRunner.cs` 参数过多与 `MediatorAdvancedFeaturesTests.cs` 未使用测试基础设施这两条本地仍成立的建议
|
||||
- MegaLinter 仅报出 `dotnet-format` restore 失败,test report 为 `2156 passed / 0 failed`,因此本轮重点改为“只吸收仍有效且低风险的 review 建议”
|
||||
- 主线程实施:
|
||||
- 将 `AGENTS.md` 中英文规则段的 `dotnet clean` / `dotnet build` / `dotnet test` 列表标点改为英文逗号,直接消化 latest-head open thread
|
||||
- 删除 `GFramework.Cqrs.Tests/Mediator/MediatorAdvancedFeaturesTests.cs` 中未被任何测试引用的 `TestLoggingBehavior` 静态类型,移除无收益的可变测试基础设施
|
||||
- 在 `GFramework.Game/Internal/VersionedMigrationRunner.cs` 内引入私有 `MigrationExecutionContext<TData, TMigration>`,把多处 helper 共享的不变迁移上下文收口为参数对象,并同步补齐新增泛型 helper 的 XML 文档
|
||||
- 明确拒绝把 `TestLogger` 重复实现与 `YamlConfigLoaderTests.cs` 常量位置这类“可选整理”混入本轮 warning 收敛批次
|
||||
- 验证里程碑:
|
||||
- `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --json-output <current-pr-review-json>`
|
||||
- 结果:成功;确认 PR `#291` latest-head open review threads 为 `1`,MegaLinter 仅有 `dotnet-format` restore 失败,tests 为 `2156 passed`
|
||||
- `dotnet build GFramework.Game/GFramework.Game.csproj -c Release`
|
||||
- 结果:成功;`326 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet build GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release`
|
||||
- 结果:成功;`149 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet clean`
|
||||
- 结果:成功
|
||||
- `dotnet build`
|
||||
- 结果:成功;`639 Warning(s)`、`0 Error(s)`,相较 `RP-069` 的 `640` 再下降 `1`
|
||||
- 当前结论:
|
||||
- PR #291 当前最有价值的 review follow-up 已被主线程吸收,且没有把“可选整理”误当成必须修复项
|
||||
- 当前仓库根 warning 基线继续下降到 `639`,说明这轮 review 驱动的小批次仍符合 analyzer warning reduction 主题
|
||||
- 下一轮可继续围绕 `GFramework.Game` 或 `GFramework.Cqrs.Tests` 选择新的单文件低风险热点,或在新 head 推送后重新抓取 PR review 判断是否还有剩余有效线程
|
||||
|
||||
## 2026-04-25 — RP-069
|
||||
|
||||
### 阶段:继续收口 Cqrs.Tests 双文件集合抽象 warning,并刷新新的仓库根基线
|
||||
|
||||
- 触发背景:
|
||||
- `RP-068` 收尾后,当前分支的仓库根基线已降到 `645 Warning(s)`,branch diff 仍远低于 `$gframework-batch-boot 50`
|
||||
- 为保持批次小而连续,主线程继续留在 `GFramework.Cqrs.Tests` 项目内,选取两个不涉及跨文件重构的 `MA0016` 切片
|
||||
- 接受的委派范围:
|
||||
- worker `Chandrasekhar`
|
||||
- 文件:`GFramework.Cqrs.Tests/Mediator/MediatorAdvancedFeaturesTests.cs`
|
||||
- 目标:在同一文件内收敛 `TestLoggingBehavior.LoggedMessages`、`SagaData`、`TestDatabaseRequest` 的集合抽象暴露问题
|
||||
- 结果:未自行提交;主线程接受其工作树改动并纳入本轮批次
|
||||
- 主线程实施:
|
||||
- 本地修改 `GFramework.Cqrs.Tests/Logging/TestLogger.cs`
|
||||
- 将 `Logs` 从 `List<LogEntry>` 收口为 `IReadOnlyList<LogEntry>`,保留私有 `_logs` 作为内部存储
|
||||
- 与 worker 的 `MediatorAdvancedFeaturesTests.cs` 改动合并后,重新执行 `GFramework.Cqrs.Tests` 与仓库根验证,确认双文件批次的净效果
|
||||
- 验证里程碑:
|
||||
- `dotnet build GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release`
|
||||
- 结果:成功;`0 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet clean`
|
||||
- 结果:成功
|
||||
- `dotnet build`
|
||||
- 结果:成功;`640 Warning(s)`、`0 Error(s)`,相较 `RP-068` 的 `645` 再下降 `5`
|
||||
- 当前结论:
|
||||
- `Cqrs.Tests` 双文件批次已确认有效,并继续压低仓库根 warning 基线
|
||||
- 当前分支距离 `$gframework-batch-boot 50` 的停止阈值仍有很大空间,可以继续按“主线程小切片 + subagent 并行单文件”推进
|
||||
- 下一轮可优先回到 `GFramework.Core.Tests` 或继续选择新的 `GFramework.Cqrs.Tests` 单文件热点
|
||||
|
||||
## 2026-04-25 — RP-068
|
||||
|
||||
### 阶段:吸收并行 subagent 小批次,并继续压低仓库根 warning 基线
|
||||
|
||||
- 触发背景:
|
||||
- `RP-067` 收尾后,当前分支的仓库根基线已降到 `649 Warning(s)`,branch diff 仅 `9 files`
|
||||
- 用户明确允许主线程与 subagent 在不冲突的写集上并行推进,因此本轮继续按 `$gframework-batch-boot 50` 规则拆成 3 个单文件切片
|
||||
- 接受的委派范围:
|
||||
- worker `Averroes`
|
||||
- 文件:`GFramework.Core.Tests/Logging/LogContextTests.cs`
|
||||
- 目标:收敛 `Push_InAsyncContext_ShouldIsolateAcrossThreads()` 内的 `MA0004`
|
||||
- 结果:提交 `a7fa70e` `fix(core-tests): 清理 LogContextTests 异步等待 warning`
|
||||
- worker `Laplace`
|
||||
- 文件:`GFramework.Core.Tests/Logging/LoggerTests.cs`
|
||||
- 目标:把 `TestLogger.Logs` 从 `List<LogEntry>` 收口为集合抽象以修复 `MA0016`
|
||||
- 结果:提交 `9f6204d` `fix(core-tests): 收口 LoggerTests 日志集合抽象`
|
||||
- 主线程实施:
|
||||
- 本地重构 `GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs`
|
||||
- 将 `RegisterHandlers_Should_Cache_Assembly_Metadata_Across_Containers()` 中的 mock 装配、handler 类型读取、预期集合断言与 metadata lookup verify 拆分为具名 helper
|
||||
- 在吸收两个 worker 提交后,主线程重新执行直接受影响模块与仓库根验证,确保并行切片合并后的真值一致
|
||||
- 验证里程碑:
|
||||
- `dotnet build GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release`
|
||||
- 结果:成功;`155 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --no-build --filter "FullyQualifiedName~CqrsHandlerRegistrarTests.RegisterHandlers_Should_Cache_Assembly_Metadata_Across_Containers"`
|
||||
- 结果:成功;`Passed 1/1`
|
||||
- `dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release`
|
||||
- 结果:成功;`0 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet clean`
|
||||
- 结果:成功
|
||||
- `dotnet build`
|
||||
- 结果:成功;`645 Warning(s)`、`0 Error(s)`,相较 `RP-067` 的 `649` 再下降 `4`
|
||||
- 当前结论:
|
||||
- 并行 3 文件批次已确认有效,且主线程已把 subagent 的负责范围和验证结果收口进 active trace
|
||||
- 当前分支距离 `$gframework-batch-boot 50` 的停止阈值仍有充足空间,可以继续用“主线程验证 + subagent 并行单文件切片”的节奏推进
|
||||
- 下一轮可优先回到 `GFramework.Cqrs.Tests` 或 `GFramework.Game` 的单文件 `MA0051` / `MA0016` 热点
|
||||
|
||||
## 2026-04-25 — RP-067
|
||||
|
||||
### 阶段:收口 Game runtime 单文件长方法切片,并继续压低根构建 warning 基线
|
||||
|
||||
- 触发背景:
|
||||
- `RP-066` 收尾后,当前分支已通过 `be26640` 把 `YamlConfigLoaderTests.cs` 的 4 个 `MA0051` 落地,仓库根基线降到 `652 Warning(s)`
|
||||
- 主线程随后切到 `GFramework.Game/Internal/VersionedMigrationRunner.cs`,继续挑选单文件、低风险、可独立验证的 runtime warning 切片
|
||||
- 主线程实施:
|
||||
- 将 `MigrateToTargetVersion` 中的运行时版本校验、迁移解析、单步应用与结果一致性校验拆分为具名 helper
|
||||
- 为新增 helper 补齐 XML 注释,保持该共享迁移执行器的职责边界可读,并避免仅靠机械拆分留下语义不清的私有方法
|
||||
- 保持外部行为不变,只收敛长方法 warning,不扩展到存储或日志相关调用方
|
||||
- 验证里程碑:
|
||||
- `dotnet clean`
|
||||
- 结果:成功
|
||||
- `dotnet build`
|
||||
- 结果:成功;`649 Warning(s)`、`0 Error(s)`,相较 `RP-066` 的 `652` 再下降 `3`
|
||||
- `dotnet build GFramework.Game/GFramework.Game.csproj -c Release`
|
||||
- 结果:成功;`0 Warning(s)`、`0 Error(s)`
|
||||
- 当前结论:
|
||||
- `VersionedMigrationRunner.cs` 这个 runtime 单文件批次已被主线程收口,并继续压低仓库根 warning 基线
|
||||
- 本轮只新增 1 个源码唯一文件,branch diff 仍显著低于 `$gframework-batch-boot 50` 的主停止阈值
|
||||
- 下一轮可以继续挑选 `GFramework.Cqrs.Tests` 或 `GFramework.Game` 的单文件轻量切片,并保持主线程验证、subagent 并行探索的节奏
|
||||
|
||||
## 2026-04-25 — RP-066
|
||||
|
||||
### 阶段:主线程回收停滞的单文件批次,并继续压低根构建 warning 基线
|
||||
|
||||
- 触发背景:
|
||||
- `RP-065` 收尾后,`fix/analyzer-warning-reduction-batch` 已通过 `6a704f3` 把 AGENTS / active ai-plan 真值修正和 4 文件测试噪音批次提交到分支
|
||||
- 原先负责 `YamlConfigLoaderTests.cs` 的 worker 长时间无结果,主线程收回该单文件批次以避免继续阻塞
|
||||
- 主线程实施:
|
||||
- 关闭停滞 worker,直接重构 `GFramework.Game.Tests/Config/YamlConfigLoaderTests.cs`
|
||||
- 通过提取固定夹具内容、热重载接线 helper 与共享断言,收敛以下 4 个长方法 warning:
|
||||
- `EnableHotReload_Should_Keep_Previous_State_When_Contains_Reference_Dependency_Breaks`
|
||||
- `EnableHotReload_Should_Support_Options_Object`
|
||||
- `EnableHotReload_Should_Keep_Previous_Table_When_Schema_Change_Makes_Reload_Fail`
|
||||
- `EnableHotReload_Should_Keep_Previous_State_When_Dependency_Table_Breaks_Cross_Table_Reference`
|
||||
- 在第一次仓库根重建中命中了两个 `CS0411` 泛型推断错误,主线程随即补上显式类型参数并重新建立 clean/build 基线
|
||||
- 验证里程碑:
|
||||
- `dotnet clean`
|
||||
- 结果:成功
|
||||
- `dotnet build`
|
||||
- 结果:成功;`652 Warning(s)`、`0 Error(s)`,相较 `RP-065` 的 `656` 再下降 `4`
|
||||
- `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release`
|
||||
- 结果:成功;`0 Warning(s)`、`0 Error(s)`
|
||||
- 当前结论:
|
||||
- `YamlConfigLoaderTests.cs` 这 4 个根构建直接确认的 `MA0051` 已被消化
|
||||
- 当前分支在 `6a704f3` 之后的下一提交只会新增 1 个唯一文件,因此 branch diff 仍明显低于 `$gframework-batch-boot 50` 阈值
|
||||
- 下一轮可继续选择新的单文件或小写集热点,而不必暂停当前 batch loop
|
||||
|
||||
## 2026-04-25 — RP-065
|
||||
|
||||
### 阶段:确认 .NET 验证噪音来自沙箱,并把无沙箱直跑写成仓库规则
|
||||
|
||||
- 触发背景:
|
||||
- 用户明确指出“之前很多清理、构建、测试报错像是环境问题,需要申请权限在沙箱外执行”,并要求把该解决方案写入 `AGENTS.md`
|
||||
- 主线程随后在同一 worktree 中对比了沙箱内与提权后直接 shell 的 `dotnet clean` / `dotnet build`
|
||||
- 主线程实施:
|
||||
- 在沙箱内直接运行 `dotnet clean` 时再次复现“Build FAILED but 0 errors”的无诊断噪音
|
||||
- 申请提权后重新执行同一条 `dotnet clean`,确认命令可正常完成,说明先前 clean 失败并非仓库真值
|
||||
- 在同一提权上下文直接执行 `dotnet build`,拿到当前仓库根权威基线:`656 Warning(s)`、`0 Error(s)`
|
||||
- 关闭正在运行的 warning-reduction worker,把工作重心切到仓库治理与 active recovery 文档净化
|
||||
- 更新 `AGENTS.md`,新增规则:当沙箱内 `dotnet clean` / `dotnet build` / `dotnet test` 产生缺少诊断、权限错误或其他环境噪音时,必须申请沙箱外重跑同一条直接命令,并以该结果为准
|
||||
- 刷新 active todo/trace,把“环境阻塞”从默认恢复入口中降级为历史噪音,不再作为当前真值
|
||||
- 并行工作:
|
||||
- worker 收敛了 4 个低风险测试噪音文件:
|
||||
- `GFramework.Game.Tests/Config/GameConfigBootstrapTests.cs`
|
||||
- `GFramework.Game.Tests/Config/GeneratedConfigConsumerIntegrationTests.cs`
|
||||
- `GFramework.Game.Tests/Config/YamlConfigTextValidatorTests.cs`
|
||||
- `GFramework.Ecs.Arch.Tests/Ecs/EcsAdvancedTests.cs`
|
||||
- worker 验证:
|
||||
- `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release`
|
||||
- worker 初次结果:成功;随后主线程在同一提权环境复核后确认当前为 `0 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet build GFramework.Ecs.Arch.Tests/GFramework.Ecs.Arch.Tests.csproj -c Release`
|
||||
- 主线程复核:成功;`0 Warning(s)`、`0 Error(s)`
|
||||
- 当前结论:
|
||||
- 本仓库当前在 agent 沙箱内执行 `.NET` 验证时,确实可能出现假失败或缺失诊断
|
||||
- 当前应把“提权后的直接 `dotnet` 命令输出”视为仓库真值,而不是继续围绕沙箱噪音扩展 workaround 命令形态
|
||||
- `fix/analyzer-warning-reduction-batch` 当前 `HEAD` 已与 `origin/main` 对齐;新的 `$gframework-batch-boot 50` 轮次从 `0 files / 0 lines` committed diff 开始
|
||||
- 下一轮低风险热点仍是 `GFramework.Game.Tests/Config/YamlConfigLoaderTests.cs` 的 `4` 个 `MA0051`
|
||||
|
||||
## 2026-04-25 — RP-064
|
||||
|
||||
### 阶段:按标准 WSL build 路径复核 PR #288 建议并完成本轮收口
|
||||
|
||||
- 触发背景:
|
||||
- 用户指出“在 WSL 里直接执行 `dotnet build` 可以成功”,要求主线程按普通路径重新验证,而不是继续使用带 `MSBuildEnableWorkloadResolver=false`、`--no-restore`、手工 `TargetFramework` 的 workaround 命令
|
||||
- 当前任务仍属于 PR #288 review follow-up,因此本轮重点改为“区分哪些 AI 建议值得采纳”以及“用真实 WSL build 结果验证”
|
||||
- 主线程实施:
|
||||
- 重新抓取 PR #288 review,确认 latest-head open threads 为 `CodeRabbit 6 + Greptile 2`
|
||||
- 复核 `outside diff + nitpick` 的 19 条建议,只采纳本地仍成立的建议;拒绝把“评论总数”机械等同于“必须全改”
|
||||
- 完成以下高信号修复:
|
||||
- `ContextAware*` / `AsyncExtensions` / `NumericExtensions` / `StringExtensions` / `StoreBuilder`:回退为 `ArgumentNullException.ThrowIfNull(...)`
|
||||
- `ArchitectureServicesTests` / `GameContextTests`:同步 XML `<exception>` 到 `NotSupportedException`
|
||||
- `RegistryInitializationHookBaseTests`:修复 override 可空签名实现,避免再次引入编译错误
|
||||
- `RollingFileAppenderTests` / `TaskCoroutineExtensionsTests` / `WaitForTaskTests` / `ScopedStorage`:移除无收益噪音代码
|
||||
- `FileStorage`:通过 `leaveOpen: true` 修正 `FileStream` 的双重释放语义
|
||||
- `SceneRouterBase`:统一显式 `ConfigureAwait(true)` 并补齐引擎线程亲和说明
|
||||
- `StoreSelection`:保留 `net9.0+` 的 `System.Threading.Lock`,同时修正条件编译旁的注释写法,避免 `CS1587`
|
||||
- 验证里程碑:
|
||||
- `dotnet restore GFramework.sln -p:RestoreFallbackFolders="" -v minimal`
|
||||
- 结果:成功;证明先前 `MSB4018` 来自 stale restore 元数据,而不是当前 WSL 默认 build 路径本身不可用
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release`
|
||||
- 结果:成功;`28 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet build GFramework.Game/GFramework.Game.csproj -c Release`
|
||||
- 结果:成功;`329 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release`
|
||||
- 结果:成功;`137 Warning(s)`、`0 Error(s)`
|
||||
- 当前结论:
|
||||
- 用户关于“WSL 里直接 `dotnet build` 可行”的判断正确
|
||||
- 前一轮失败的核心原因不是仓库不可构建,而是主线程附加的 workaround 参数改变了 MSBuild 行为
|
||||
- 本轮已完成 PR #288 中一组仍成立的建议修复,并重新拿到标准 WSL 路径下的 Release build 验证
|
||||
- 剩余 review 线程需要在新 head 上重新抓取后再决定是否逐条 resolve
|
||||
|
||||
## 2026-04-25 — RP-063
|
||||
|
||||
### 阶段:先收口 PR #288 latest-head 编译错误,再暂停在环境阻塞点并准备提交
|
||||
|
||||
- 触发背景:
|
||||
- 用户显式要求先执行 `$gframework-pr-review`,并指出 `AsyncExtensionsTests.cs(126,23)` 当前存在 `CS0029` / `CS1662` 构建错误
|
||||
- 当前 worktree 仍是 `fix/analyzer-warning-reduction-batch`,因此本 turn 继续沿用 `analyzer-warning-reduction` 的 active recovery 文档
|
||||
- 主线程实施:
|
||||
- 运行 PR review 抓取脚本,确认当前分支对应 PR `#288`
|
||||
- 核对 latest-head unresolved review threads 后,优先修复 `AsyncExtensionsTests.cs` 中 `ct => Task.Delay(...).ConfigureAwait(false)` 错误返回 `ConfiguredTaskAwaitable` 的问题
|
||||
- 顺手收敛多处已被 latest review 点名且本地仍成立的低风险残留:
|
||||
- 测试中的 `async` 无 `await`
|
||||
- `ValueTask` 断言包装
|
||||
- `RegistryInitializationHookBaseTests.cs` 的可空返回签名
|
||||
- `NumericExtensions.cs`、`StringExtensions.cs`、`StoreBuilder.cs` 的 Allman 花括号残留
|
||||
- `StoreSelection.cs` 在 `net9.0+` 下切到 `System.Threading.Lock`,同时保留 `net8.0` 兼容分支
|
||||
- 验证里程碑:
|
||||
- `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --json-output <current-pr-review-json>`
|
||||
- 结果:成功;确认 PR `#288` 的 latest-head unresolved AI review threads 共 `9` 个,其中 `AsyncExtensionsTests.cs:126` 为 critical 编译错误
|
||||
- `DOTNET_CLI_HOME=<dotnet-cli-home> MSBuildEnableWorkloadResolver=false dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -v minimal`
|
||||
- 结果:失败;`MSB4018`,`ResolvePackageAssets` 仍读取失效 Windows fallback package folder `<stale-windows-fallback-package-folder>`
|
||||
- `DOTNET_CLI_HOME=<dotnet-cli-home> MSBuildEnableWorkloadResolver=false dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net9.0 -p:RestoreFallbackFolders="" -v minimal`
|
||||
- 结果:失败;原因同上
|
||||
- `DOTNET_CLI_HOME=<dotnet-cli-home> MSBuildEnableWorkloadResolver=false dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-restore -p:TargetFramework=net10.0 -p:RestoreFallbackFolders="" -v minimal`
|
||||
- 结果:失败;原因同上
|
||||
- 当前结论:
|
||||
- 用户点名的 `AsyncExtensionsTests.cs` 编译错误已在源码层修复
|
||||
- 本 turn 未能拿到新的可通过 Release build,阻塞点已从先前记录的 `MSB4276` 收敛为当前 `obj/*.csproj.nuget.g.props` 中 stale Windows fallback package folder 导致的 `MSB4018`
|
||||
- 用户随后要求“先不管这个了,先提交吧”,因此本 turn 在记录环境阻塞后先执行提交收口
|
||||
|
||||
## 2026-04-25 — RP-062
|
||||
|
||||
### 阶段:触达 `$gframework-batch-boot 75` 停止阈值并收口到 `75 files / 2098 lines`
|
||||
|
||||
- 触发背景:
|
||||
- `RP-061` 收尾时分支相对 `origin/main` 仍只有 `48` 个已提交文件,距离本轮 `75 files` 停止条件还有明显空间
|
||||
- 用户明确允许继续委派 subagent,因此主线程继续把低风险机械型写集拆成互不重叠的 test / runtime 小批次
|
||||
- 本轮主目标不是继续深挖单个高上下文热点,而是用新的低风险文件精确把 branch diff 推到阈值后停止
|
||||
- 主线程实施:
|
||||
- 先接受并提交 7 文件 `Core.Tests` 收尾批次为 `03c73a8` `test(core-tests): 收敛测试桩与辅助类型 warning`
|
||||
- 随后主线程与多个 worker 并行收口以下新增文件:
|
||||
- `ArchitectureAdditionalCqrsHandlersTests.cs`
|
||||
- `RegistryInitializationHookBaseTests.cs`
|
||||
- `CommandCoroutineExtensionsTests.cs`
|
||||
- `TaskCoroutineExtensionsTests.cs`
|
||||
- `WaitForTaskTTests.cs`
|
||||
- `AsyncExtensionsTests.cs`
|
||||
- `LogContextTests.cs`
|
||||
- `PauseStackManagerTests.cs`
|
||||
- `AsyncExtensions.cs`
|
||||
- `CollectionExtensions.cs`
|
||||
- `ContextAwareCommandExtensions.cs`
|
||||
- `ContextAwareEnvironmentExtensions.cs`
|
||||
- `ContextAwareEventExtensions.cs`
|
||||
- `ContextAwareQueryExtensions.cs`
|
||||
- `ContextAwareServiceExtensions.cs`
|
||||
- `GuardExtensions.cs`
|
||||
- `NumericExtensions.cs`
|
||||
- `StoreEventBusExtensions.cs`
|
||||
- `StringExtensions.cs`
|
||||
- `StoreBuilder.cs`
|
||||
- `StoreSelection.cs`
|
||||
- 将上述 22 文件批次收口为 `9ce1fa6` `refactor(core): 收敛 Core 扩展与测试的机械 warning`
|
||||
- 验证里程碑:
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -v minimal`
|
||||
- 结果:成功;`0 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-incremental --no-restore -p:RestoreFallbackFolders= -v:diag`
|
||||
- 结果:失败;`MSB4276`,默认 SDK resolver 缺少 `Microsoft.NET.SDK.WorkloadAutoImportPropsLocator`
|
||||
- `dotnet restore GFramework.Core.Tests/GFramework.Core.Tests.csproj -p:TestTargetFrameworks=net8.0 -p:RestoreFallbackFolders="" -v minimal`
|
||||
- 结果:失败;`NU1201`,`GFramework.Tests.Common` 仅支持 `net10.0`,不能作为 `Core.Tests` 的 net8 旁路验证
|
||||
- `git diff --name-only origin/main...HEAD | wc -l`
|
||||
- 结果:`75`
|
||||
- `git diff --numstat origin/main...HEAD`
|
||||
- 结果:累计 `1083` added、`1015` deleted,即 `2098` changed lines
|
||||
- 当前结论:
|
||||
- 本轮 `$gframework-batch-boot 75` 已精确达到主停止条件,默认恢复点应停止在 `9ce1fa6`
|
||||
- `Core` runtime 的本轮机械型改动已有可通过的最小 Release build 验证
|
||||
- `Core.Tests` 的继续推进当前首先受 `MSB4276` 环境阻塞影响;下一轮若要继续,应先修复构建环境,再重新建立 warning 基线
|
||||
@ -6,85 +6,37 @@
|
||||
|
||||
## 当前恢复点
|
||||
|
||||
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-064`
|
||||
- 当前阶段:`Phase 64`
|
||||
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-073`
|
||||
- 当前阶段:`Phase 73`
|
||||
- 当前焦点:
|
||||
- `2026-04-25` 当前 turn 先执行 `$gframework-pr-review`,复核 PR #288 的 latest-head unresolved 线程与折叠评论
|
||||
- 已收敛一批经本地复核后仍成立的 review 建议,包括 `ThrowIfNull` 回退、测试桩 XML 注释修正、`FileStorage` 资源所有权、`SceneRouterBase` 线程亲和语义与若干测试噪音
|
||||
- 已确认用户在 WSL 下直接执行的标准 `dotnet build -c Release` 路径可用;前一轮失败主要来自主线程附加的 workaround 参数而非仓库本身不可构建
|
||||
- 基线 `origin/main` 仍为 `9964962`(`2026-04-24T23:05:53+08:00`)
|
||||
- 当前累计 branch diff 相对 `origin/main` 为 `75` 个文件、`2098` 行,已触达本轮 `75 files` 阈值
|
||||
- `RP-061` 之后已接受 2 个批次提交:`03c73a8`、`9ce1fa6`
|
||||
- 当前默认恢复入口不再继续扩写集;若要继续 analyzer reduction,优先重新抓取 PR #288 的 unresolved 线程并按最新 head 再做一轮收口
|
||||
- `2026-04-26` 主线程再次按 `$gframework-pr-review` 复核当前分支 PR `#291`,确认 latest-head 仍剩 `2` 条 open review thread,均指向 `ai-plan` 文档中的绝对路径记录
|
||||
- 当前批次同步 active todo/trace 与相关 archive trace:把 PR review 输出路径、临时 `dotnet` home 和失效 Windows fallback package folder 改写为仓库安全占位符
|
||||
- `dotnet clean` + `dotnet build` 的直接仓库根基线仍为 `639 Warning(s)`、`0 Error(s)`,因此本轮属于文档真值收口,而不是新的 warning 清理批次
|
||||
|
||||
## 当前活跃事实
|
||||
|
||||
- 当前 `origin/main` 基线提交为 `9964962`(`2026-04-24T23:05:53+08:00`)。
|
||||
- 本轮 `Core.Tests` 低风险机械型清理已落地到:
|
||||
- `ArchitectureAdditionalCqrsHandlersTests.cs`
|
||||
- `RegistryInitializationHookBaseTests.cs`
|
||||
- `CommandCoroutineExtensionsTests.cs`
|
||||
- `TaskCoroutineExtensionsTests.cs`
|
||||
- `WaitForTaskTTests.cs`
|
||||
- `AsyncExtensionsTests.cs`
|
||||
- `LogContextTests.cs`
|
||||
- `PauseStackManagerTests.cs`
|
||||
- 本 turn 结合 PR #288 latest-head review 额外收敛了以下仍然成立的问题:
|
||||
- `AsyncExtensionsTests.cs`:修复 `WithTimeoutAsync` 无返回值测试中错误返回 `ConfiguredTaskAwaitable` 导致的 `CS0029` / `CS1662`
|
||||
- `ContextAwareCommandExtensions.cs`
|
||||
- `ContextAwareQueryExtensions.cs`
|
||||
- `ContextAwareEventExtensions.cs`
|
||||
- `AsyncExtensions.cs`
|
||||
- `AsyncKeyLockManagerTests.cs`:去掉两处不会产生额外价值的 `Assert.DoesNotThrowAsync(() => Task.WhenAll(...))` 包装,并把取消断言改为直接消费 `ValueTask.AsTask()`
|
||||
- `AsyncArchitectureTests.cs`
|
||||
- `ArchitectureLifecycleBehaviorTests.cs`
|
||||
- `StateMachineSystemTests.cs`
|
||||
- `RegistryInitializationHookBaseTests.cs`
|
||||
- `NumericExtensions.cs`
|
||||
- `StringExtensions.cs`
|
||||
- `StoreBuilder.cs`
|
||||
- `StoreSelection.cs`
|
||||
- `ArchitectureServicesTests.cs`
|
||||
- `GameContextTests.cs`
|
||||
- `RollingFileAppenderTests.cs`
|
||||
- `TaskCoroutineExtensionsTests.cs`
|
||||
- `WaitForTaskTests.cs`
|
||||
- `ScopedStorage.cs`
|
||||
- `FileStorage.cs`
|
||||
- `SceneRouterBase.cs`
|
||||
- 当前 PR review 观察:
|
||||
- PR:`#288`
|
||||
- latest reviewed commit:`70c42b579f70c90ab5461a02e611c0fbd8d8a6f2`
|
||||
- 抓取时 `coderabbitai[bot]` 有 `6` 个 open threads,`greptile-apps[bot]` 有 `2` 个 open threads
|
||||
- `Actionable comments posted: 7` 与 `outside diff + nitpick = 19` 并不等于必须全收;本 turn 仅接受经本地复核后仍成立且不与仓库约束冲突的建议
|
||||
- 本轮 `Core` runtime 低风险机械型清理已落地到:
|
||||
- `AsyncExtensions.cs`
|
||||
- `CollectionExtensions.cs`
|
||||
- `ContextAwareCommandExtensions.cs`
|
||||
- `ContextAwareEnvironmentExtensions.cs`
|
||||
- `ContextAwareEventExtensions.cs`
|
||||
- `ContextAwareQueryExtensions.cs`
|
||||
- `ContextAwareServiceExtensions.cs`
|
||||
- `GuardExtensions.cs`
|
||||
- `NumericExtensions.cs`
|
||||
- `StoreEventBusExtensions.cs`
|
||||
- `StringExtensions.cs`
|
||||
- `StoreBuilder.cs`
|
||||
- `StoreSelection.cs`
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -v minimal` 当前结果为 `0 Warning(s)`、`0 Error(s)`,可作为本轮 runtime 变更的最终最小 Release build 验证。
|
||||
- `GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-incremental` 在 `03c73a8` 提交前的最近一次可信主线程结果为 `198 Warning(s)`、`0 Error(s)`;该观测值覆盖了 `ArchitectureContextTests`、`ArchitectureServicesTests`、`GameContextTests`、`ResultTests`、`AsyncTestModel`、`AsyncTestSystem` 与 `ContextAwareEnvironmentExtensionsTests` 的 7 文件批次。
|
||||
- 当前累计 branch diff 相对 `origin/main` 为 `75` 个文件、`2098` 行;本轮主停止条件已经达到。
|
||||
- 当前 `origin/main` 基线提交为 `4ad880c`(`2026-04-25T14:35:38+08:00`)。
|
||||
- 提权后的直接仓库根验证当前确认为:
|
||||
- `dotnet clean`
|
||||
- 结果:成功;此前沙箱内 “Build FAILED but 0 errors” 的 clean 结果不是仓库真值
|
||||
- `dotnet build`
|
||||
- 最新结果:成功;`639 Warning(s)`、`0 Error(s)`
|
||||
- 当前分支低风险批次文件:
|
||||
- `ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md`
|
||||
- `ai-plan/public/analyzer-warning-reduction/traces/analyzer-warning-reduction-trace.md`
|
||||
- `ai-plan/public/analyzer-warning-reduction/archive/traces/analyzer-warning-reduction-history-rp062-rp071.md`
|
||||
- 当前批次验证结果:
|
||||
- `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --json-output <current-pr-review-json>`
|
||||
- 最新主线程结果:成功;确认 PR `#291` latest-head open review thread 为 `2`,两者都指向 `ai-plan` 文档中的绝对路径记录
|
||||
- `dotnet build`
|
||||
- 最新主线程结果:成功;`639 Warning(s)`、`0 Error(s)`;与当前权威仓库根基线一致
|
||||
|
||||
## 当前风险
|
||||
|
||||
- `dotnet clean GFramework.sln -c Release` 与 `dotnet clean GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` 仍无法稳定提供新的 clean 基线。
|
||||
- 缓解措施:后续若继续整仓 warning reduction,需要单独定位 clean 失败原因,或明确继续沿用 direct build 观测值作为临时真值。
|
||||
- 当前 worktree 仍存在未跟踪的 `.codex` 目录。
|
||||
- 缓解措施:提交当前批次时只暂存 analyzer-warning-reduction 相关源码与 `ai-plan` 文件,避免把工作目录辅助文件混入提交。
|
||||
- 将分支继续推过 `75 files` 会明显降低本轮 reviewability。
|
||||
- 缓解措施:当前恢复点默认停止;如需继续,建议在新 turn 明确新的文件阈值或先 rebase / refresh baseline。
|
||||
- `GFramework.Core`、`GFramework.Game`、`GFramework.Core.Tests` 当前都仍存在模块级历史 warning 基线。
|
||||
- 缓解措施:本 turn 已确保本次 touched files 不再引入新的编译错误,并消化了当前 PR review 中仍成立的高信号问题;若要继续 warning reduction,应开新批次按模块系统化收敛。
|
||||
- `GFramework.Core`、`GFramework.Game`、`GFramework.Core.Tests`、`GFramework.Cqrs.Tests` 仍有较大 warning 基线。
|
||||
- 缓解措施:后续批次继续优先挑低风险、少文件、可独立验证的测试与局部逻辑切片。
|
||||
- 当前 review 相关真值要等新 head 推送后才能在 GitHub UI 中自动收口。
|
||||
- 缓解措施:本轮提交后立即重新执行 `$gframework-pr-review`,确认 PR `#291` 的 latest-head thread 与 nitpick 是否消失。
|
||||
|
||||
## 活跃文档
|
||||
|
||||
@ -94,41 +46,18 @@
|
||||
- [analyzer-warning-reduction-history-rp001.md](../archive/todos/analyzer-warning-reduction-history-rp001.md)
|
||||
- [analyzer-warning-reduction-history-rp002-rp041.md](../archive/todos/analyzer-warning-reduction-history-rp002-rp041.md)
|
||||
- 历史 trace 归档:
|
||||
- [analyzer-warning-reduction-history-rp062-rp071.md](../archive/traces/analyzer-warning-reduction-history-rp062-rp071.md)
|
||||
- [analyzer-warning-reduction-history-rp001.md](../archive/traces/analyzer-warning-reduction-history-rp001.md)
|
||||
- [analyzer-warning-reduction-history-rp002-rp041.md](../archive/traces/analyzer-warning-reduction-history-rp002-rp041.md)
|
||||
- [analyzer-warning-reduction-history-rp042-rp048.md](../archive/traces/analyzer-warning-reduction-history-rp042-rp048.md)
|
||||
|
||||
## 验证说明
|
||||
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -v minimal`
|
||||
- 历史结果:成功;`0 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-incremental --no-restore -p:RestoreFallbackFolders= -v:diag`
|
||||
- 历史结果:失败;`MSB4276`,默认 SDK resolver 无法解析 `Microsoft.NET.SDK.WorkloadAutoImportPropsLocator`,属于当前 WSL / dotnet 10 环境阻塞
|
||||
- `DOTNET_CLI_HOME=/tmp/dotnet-home MSBuildEnableWorkloadResolver=false dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -v minimal`
|
||||
- 结果:失败;`MSB4018`,`ResolvePackageAssets` 命中失效 Windows fallback package folder `D:\Tool\Development Tools\Microsoft Visual Studio\Shared\NuGetPackages`
|
||||
- `DOTNET_CLI_HOME=/tmp/dotnet-home MSBuildEnableWorkloadResolver=false dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net9.0 -p:RestoreFallbackFolders="" -v minimal`
|
||||
- 结果:失败;`MSB4018`,原因同上
|
||||
- `DOTNET_CLI_HOME=/tmp/dotnet-home MSBuildEnableWorkloadResolver=false dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-restore -p:TargetFramework=net10.0 -p:RestoreFallbackFolders="" -v minimal`
|
||||
- 结果:失败;`MSB4018`,原因同上
|
||||
- `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --json-output /tmp/current-pr-review.json`
|
||||
- 结果:成功;定位到 PR `#288`,提取 latest-head unresolved AI review threads、MegaLinter 与 Docstring Coverage 信号
|
||||
- `dotnet restore GFramework.sln -p:RestoreFallbackFolders="" -v minimal`
|
||||
- 结果:成功;已刷新 WSL 原生 restore 元数据,清除先前的 stale fallback package folder 阻塞
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release`
|
||||
- 结果:成功;`28 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet build GFramework.Game/GFramework.Game.csproj -c Release`
|
||||
- 结果:成功;`329 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release`
|
||||
- 结果:成功;`137 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet restore GFramework.Core.Tests/GFramework.Core.Tests.csproj -p:TestTargetFrameworks=net8.0 -p:RestoreFallbackFolders="" -v minimal`
|
||||
- 结果:失败;`NU1201`,`GFramework.Tests.Common` 仅支持 `net10.0`,因此不能用 `net8.0` 旁路验证 `Core.Tests`
|
||||
- `git diff --name-only origin/main...HEAD | wc -l`
|
||||
- 当前结果:`75`
|
||||
- `git diff --numstat origin/main...HEAD`
|
||||
- 当前结果:累计 `1083` added、`1015` deleted,即 `2098` changed lines
|
||||
- 权威验证结果统一维护在“当前活跃事实”和“当前批次验证结果”。
|
||||
- 后续若刷新构建或 PR review 真值,只更新上述权威区块,不在本节重复抄录。
|
||||
|
||||
## 下一步建议
|
||||
|
||||
1. 当前 turn 已按标准 WSL `dotnet build` 路径完成 `Core` / `Game` / `Core.Tests` Release build 验证;后续若继续 PR #288 收尾,优先重新抓取 unresolved threads,确认哪些线程已可直接 resolve。
|
||||
2. 若后续要继续 `Core` / `Core.Tests` / `Game` warning reduction,应以当前标准 build 输出为新真值,而不是继续沿用上一轮带 workaround 参数的失败命令。
|
||||
3. 若要开启下一轮批处理,优先选择新的 stop-condition(例如新的 file 阈值、warning 目标或限定到单模块)后再继续。
|
||||
1. 推送包含本轮 absolute-path 脱敏的提交后,重新执行 `$gframework-pr-review`,确认 PR `#291` 的 latest-head open thread 是否已自动收口。
|
||||
2. 若 PR `#291` 已清零,继续以当前 `639 Warning(s)` 根基线为恢复点,按 `$gframework-batch-boot 50` 规则挑选下一个 1-3 文件的低风险热点。
|
||||
3. 若 GitHub 仍保留 review 信号,先确认它们是否仍指向新 head,再决定是否需要继续清理同主题下的其它历史 `ai-plan` 记录。
|
||||
|
||||
@ -1,119 +1,31 @@
|
||||
# Analyzer Warning Reduction 追踪
|
||||
|
||||
## 2026-04-25 — RP-064
|
||||
## 2026-04-26 — RP-073
|
||||
|
||||
### 阶段:按标准 WSL build 路径复核 PR #288 建议并完成本轮收口
|
||||
### 阶段:脱敏 analyzer-warning-reduction 文档中的绝对路径记录
|
||||
|
||||
- 触发背景:
|
||||
- 用户指出“在 WSL 里直接执行 `dotnet build` 可以成功”,要求主线程按普通路径重新验证,而不是继续使用带 `MSBuildEnableWorkloadResolver=false`、`--no-restore`、手工 `TargetFramework` 的 workaround 命令
|
||||
- 当前任务仍属于 PR #288 review follow-up,因此本轮重点改为“区分哪些 AI 建议值得采纳”以及“用真实 WSL build 结果验证”
|
||||
- 用户再次显式要求执行 `$gframework-pr-review`,当前分支仍对应 PR `#291`
|
||||
- 最新抓取结果确认 latest-head 还剩 `2` 条 open review thread,分别指向 active todo 与 archive trace 中记录的绝对路径
|
||||
- active trace 当前也保留了同类 `/tmp` 路径记录;虽然这次 review 没直接点名,但继续保留会留下同一类治理缺口
|
||||
- 主线程实施:
|
||||
- 重新抓取 PR #288 review,确认 latest-head open threads 为 `CodeRabbit 6 + Greptile 2`
|
||||
- 复核 `outside diff + nitpick` 的 19 条建议,只采纳本地仍成立的建议;拒绝把“评论总数”机械等同于“必须全改”
|
||||
- 完成以下高信号修复:
|
||||
- `ContextAware*` / `AsyncExtensions` / `NumericExtensions` / `StringExtensions` / `StoreBuilder`:回退为 `ArgumentNullException.ThrowIfNull(...)`
|
||||
- `ArchitectureServicesTests` / `GameContextTests`:同步 XML `<exception>` 到 `NotSupportedException`
|
||||
- `RegistryInitializationHookBaseTests`:修复 override 可空签名实现,避免再次引入编译错误
|
||||
- `RollingFileAppenderTests` / `TaskCoroutineExtensionsTests` / `WaitForTaskTests` / `ScopedStorage`:移除无收益噪音代码
|
||||
- `FileStorage`:通过 `leaveOpen: true` 修正 `FileStream` 的双重释放语义
|
||||
- `SceneRouterBase`:统一显式 `ConfigureAwait(true)` 并补齐引擎线程亲和说明
|
||||
- `StoreSelection`:保留 `net9.0+` 的 `System.Threading.Lock`,同时修正条件编译旁的注释写法,避免 `CS1587`
|
||||
- 将 active todo 与 active trace 中的 PR review 输出路径改写为 `--json-output <current-pr-review-json>`
|
||||
- 将 [analyzer-warning-reduction-history-rp062-rp071.md](../archive/traces/analyzer-warning-reduction-history-rp062-rp071.md) 里的临时 `dotnet` home、PR review 输出路径和失效 Windows fallback package folder 改写为仓库安全占位符
|
||||
- 同步刷新 active todo 中的 review 真值,把当前恢复点更新到 `RP-073`
|
||||
- 验证里程碑:
|
||||
- `dotnet restore GFramework.sln -p:RestoreFallbackFolders="" -v minimal`
|
||||
- 结果:成功;证明先前 `MSB4018` 来自 stale restore 元数据,而不是当前 WSL 默认 build 路径本身不可用
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release`
|
||||
- 结果:成功;`28 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet build GFramework.Game/GFramework.Game.csproj -c Release`
|
||||
- 结果:成功;`329 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release`
|
||||
- 结果:成功;`137 Warning(s)`、`0 Error(s)`
|
||||
- `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --json-output <current-pr-review-json>`
|
||||
- 结果:成功;确认 PR `#291` latest-head open review thread 为 `2`,两者都指向 `ai-plan` 文档中的绝对路径记录
|
||||
- `dotnet build`
|
||||
- 结果:成功;`639 Warning(s)`、`0 Error(s)`;与当前权威仓库根基线一致
|
||||
- 当前结论:
|
||||
- 用户关于“WSL 里直接 `dotnet build` 可行”的判断正确
|
||||
- 前一轮失败的核心原因不是仓库不可构建,而是主线程附加的 workaround 参数改变了 MSBuild 行为
|
||||
- 本轮已完成 PR #288 中一组仍成立的建议修复,并重新拿到标准 WSL 路径下的 Release build 验证
|
||||
- 剩余 review 线程需要在新 head 上重新抓取后再决定是否逐条 resolve
|
||||
|
||||
## 2026-04-25 — RP-063
|
||||
|
||||
### 阶段:先收口 PR #288 latest-head 编译错误,再暂停在环境阻塞点并准备提交
|
||||
|
||||
- 触发背景:
|
||||
- 用户显式要求先执行 `$gframework-pr-review`,并指出 `AsyncExtensionsTests.cs(126,23)` 当前存在 `CS0029` / `CS1662` 构建错误
|
||||
- 当前 worktree 仍是 `fix/analyzer-warning-reduction-batch`,因此本 turn 继续沿用 `analyzer-warning-reduction` 的 active recovery 文档
|
||||
- 主线程实施:
|
||||
- 运行 PR review 抓取脚本,确认当前分支对应 PR `#288`
|
||||
- 核对 latest-head unresolved review threads 后,优先修复 `AsyncExtensionsTests.cs` 中 `ct => Task.Delay(...).ConfigureAwait(false)` 错误返回 `ConfiguredTaskAwaitable` 的问题
|
||||
- 顺手收敛多处已被 latest review 点名且本地仍成立的低风险残留:
|
||||
- 测试中的 `async` 无 `await`
|
||||
- `ValueTask` 断言包装
|
||||
- `RegistryInitializationHookBaseTests.cs` 的可空返回签名
|
||||
- `NumericExtensions.cs`、`StringExtensions.cs`、`StoreBuilder.cs` 的 Allman 花括号残留
|
||||
- `StoreSelection.cs` 在 `net9.0+` 下切到 `System.Threading.Lock`,同时保留 `net8.0` 兼容分支
|
||||
- 验证里程碑:
|
||||
- `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --json-output /tmp/current-pr-review.json`
|
||||
- 结果:成功;确认 PR `#288` 的 latest-head unresolved AI review threads 共 `9` 个,其中 `AsyncExtensionsTests.cs:126` 为 critical 编译错误
|
||||
- `DOTNET_CLI_HOME=/tmp/dotnet-home MSBuildEnableWorkloadResolver=false dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -v minimal`
|
||||
- 结果:失败;`MSB4018`,`ResolvePackageAssets` 仍读取失效 Windows fallback package folder `D:\Tool\Development Tools\Microsoft Visual Studio\Shared\NuGetPackages`
|
||||
- `DOTNET_CLI_HOME=/tmp/dotnet-home MSBuildEnableWorkloadResolver=false dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net9.0 -p:RestoreFallbackFolders="" -v minimal`
|
||||
- 结果:失败;原因同上
|
||||
- `DOTNET_CLI_HOME=/tmp/dotnet-home MSBuildEnableWorkloadResolver=false dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-restore -p:TargetFramework=net10.0 -p:RestoreFallbackFolders="" -v minimal`
|
||||
- 结果:失败;原因同上
|
||||
- 当前结论:
|
||||
- 用户点名的 `AsyncExtensionsTests.cs` 编译错误已在源码层修复
|
||||
- 本 turn 未能拿到新的可通过 Release build,阻塞点已从先前记录的 `MSB4276` 收敛为当前 `obj/*.csproj.nuget.g.props` 中 stale Windows fallback package folder 导致的 `MSB4018`
|
||||
- 用户随后要求“先不管这个了,先提交吧”,因此本 turn 在记录环境阻塞后先执行提交收口
|
||||
|
||||
## 2026-04-25 — RP-062
|
||||
|
||||
### 阶段:触达 `$gframework-batch-boot 75` 停止阈值并收口到 `75 files / 2098 lines`
|
||||
|
||||
- 触发背景:
|
||||
- `RP-061` 收尾时分支相对 `origin/main` 仍只有 `48` 个已提交文件,距离本轮 `75 files` 停止条件还有明显空间
|
||||
- 用户明确允许继续委派 subagent,因此主线程继续把低风险机械型写集拆成互不重叠的 test / runtime 小批次
|
||||
- 本轮主目标不是继续深挖单个高上下文热点,而是用新的低风险文件精确把 branch diff 推到阈值后停止
|
||||
- 主线程实施:
|
||||
- 先接受并提交 7 文件 `Core.Tests` 收尾批次为 `03c73a8` `test(core-tests): 收敛测试桩与辅助类型 warning`
|
||||
- 随后主线程与多个 worker 并行收口以下新增文件:
|
||||
- `ArchitectureAdditionalCqrsHandlersTests.cs`
|
||||
- `RegistryInitializationHookBaseTests.cs`
|
||||
- `CommandCoroutineExtensionsTests.cs`
|
||||
- `TaskCoroutineExtensionsTests.cs`
|
||||
- `WaitForTaskTTests.cs`
|
||||
- `AsyncExtensionsTests.cs`
|
||||
- `LogContextTests.cs`
|
||||
- `PauseStackManagerTests.cs`
|
||||
- `AsyncExtensions.cs`
|
||||
- `CollectionExtensions.cs`
|
||||
- `ContextAwareCommandExtensions.cs`
|
||||
- `ContextAwareEnvironmentExtensions.cs`
|
||||
- `ContextAwareEventExtensions.cs`
|
||||
- `ContextAwareQueryExtensions.cs`
|
||||
- `ContextAwareServiceExtensions.cs`
|
||||
- `GuardExtensions.cs`
|
||||
- `NumericExtensions.cs`
|
||||
- `StoreEventBusExtensions.cs`
|
||||
- `StringExtensions.cs`
|
||||
- `StoreBuilder.cs`
|
||||
- `StoreSelection.cs`
|
||||
- 将上述 22 文件批次收口为 `9ce1fa6` `refactor(core): 收敛 Core 扩展与测试的机械 warning`
|
||||
- 验证里程碑:
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -v minimal`
|
||||
- 结果:成功;`0 Warning(s)`、`0 Error(s)`
|
||||
- `dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-incremental --no-restore -p:RestoreFallbackFolders= -v:diag`
|
||||
- 结果:失败;`MSB4276`,默认 SDK resolver 缺少 `Microsoft.NET.SDK.WorkloadAutoImportPropsLocator`
|
||||
- `dotnet restore GFramework.Core.Tests/GFramework.Core.Tests.csproj -p:TestTargetFrameworks=net8.0 -p:RestoreFallbackFolders="" -v minimal`
|
||||
- 结果:失败;`NU1201`,`GFramework.Tests.Common` 仅支持 `net10.0`,不能作为 `Core.Tests` 的 net8 旁路验证
|
||||
- `git diff --name-only origin/main...HEAD | wc -l`
|
||||
- 结果:`75`
|
||||
- `git diff --numstat origin/main...HEAD`
|
||||
- 结果:累计 `1083` added、`1015` deleted,即 `2098` changed lines
|
||||
- 当前结论:
|
||||
- 本轮 `$gframework-batch-boot 75` 已精确达到主停止条件,默认恢复点应停止在 `9ce1fa6`
|
||||
- `Core` runtime 的本轮机械型改动已有可通过的最小 Release build 验证
|
||||
- `Core.Tests` 的继续推进当前首先受 `MSB4276` 环境阻塞影响;下一轮若要继续,应先修复构建环境,再重新建立 warning 基线
|
||||
- 本轮只吸收当前仍成立的 PR review 文档项,不扩展到新的 warning 清理切片
|
||||
- 当前仓库根 warning 权威基线仍保持 `639 Warning(s)`、`0 Error(s)`;本轮目标是让 analyzer-warning-reduction 主题下当前入口不再记录绝对路径
|
||||
- 下一轮默认先推送本轮同步并重新执行 `$gframework-pr-review`,确认 PR `#291` 的 open thread 是否已自动收口
|
||||
|
||||
## 历史归档指针
|
||||
|
||||
- 最新 trace 归档:
|
||||
- [analyzer-warning-reduction-history-rp062-rp071.md](../archive/traces/analyzer-warning-reduction-history-rp062-rp071.md)
|
||||
- 早期 trace 归档:
|
||||
- [analyzer-warning-reduction-history-rp001.md](../archive/traces/analyzer-warning-reduction-history-rp001.md)
|
||||
- [analyzer-warning-reduction-history-rp002-rp041.md](../archive/traces/analyzer-warning-reduction-history-rp002-rp041.md)
|
||||
|
||||
@ -12,14 +12,14 @@
|
||||
|
||||
## 当前恢复点
|
||||
|
||||
- 恢复点编号:`DOCUMENTATION-FULL-COVERAGE-GOV-RP-036`
|
||||
- 恢复点编号:`DOCUMENTATION-FULL-COVERAGE-GOV-RP-040`
|
||||
- 当前阶段:`Phase 5 - Governance Maintenance`
|
||||
- 当前焦点:
|
||||
- 继续以最新 `origin/main`(`4ad880c`,`2026-04-25 14:35:38 +08:00`)作为 baseline,当前只收口 PR `#290` latest-head review 仍然成立的 reader-facing 文档问题
|
||||
- 已用 `$gframework-pr-review` 重新抓取 PR `#290`(`docs/sdk-update-documentation`),确认 `coderabbitai[bot]` 与 `greptile-apps[bot]` 仍各有 `1` 条 open thread,测试汇总保持 `2156 passed`
|
||||
- 本轮未提交变更聚焦 3 个点:移除 `API 参考` 侧栏里的跨栏目重复入口、统一 3 个 source generator 侧栏标签与目标页标题、删除 `Core` / `Ecs.Arch` / `Game` README 中残留的 XML 覆盖基线表达
|
||||
- 当前批次仍属于低风险 reader-facing 文档治理,但已经从批量“普查”切换为按 PR review 精确收口
|
||||
- 若本轮收口并提交后仍存在 review 线程,应重新抓取最新 PR 状态,再决定是否继续扩展到其他未触达 README 或 docs 巡检点
|
||||
- 继续以最新 `origin/main`(`79934f7`,`2026-04-25 16:15:55 +08:00`)作为 baseline,当前批处理 stop condition 仍是 branch diff vs baseline 接近 `50` changed files
|
||||
- 当前批次已从“单点 review 收口”切到“覆盖整个项目功能的 reader-facing 文档补齐”,重点处理 4 组低风险切片:meta-package / 安装入口、config tool adoption、source-generators 真实契约修正、内部支撑模块 README
|
||||
- 当前未提交工作树已触达 `18` 个文件,其中 `14` 个更新、`4` 个新增;当前 committed branch diff vs `origin/main` 仍为 `3` files,提交本批次后仍明显低于 `50` 文件阈值
|
||||
- 已接受 subagent 结论:`Cqrs` 当前不是栏目缺失,而是 `docs/zh-CN/core/cqrs.md` 需要补 `Request` / stream 变体与协程入口;source-generators 侧当前优先修正文档失真与共享支撑层说明,而不是扩新导航
|
||||
- `2026-04-26` 重新抓取 PR `#292` 后确认 latest reviewed commit 已推进到 `d3d62cf4541063c46458f88eea0f5acd1b4503f9`;当前 open thread 仍集中在 `tools/gframework-config-tool/README.md`,其中“缺少中文文档入口链接”已在本地工作树验证为过期评论,仍需收口的是补最小接入路径以及统一 `current schema subset` 术语
|
||||
|
||||
## 当前状态摘要
|
||||
|
||||
@ -27,7 +27,15 @@
|
||||
- `2026-04-25` 已重新抓取 PR `#290` 并确认:latest reviewed commit 为 `54b8e5770af9ab3c8a86a396ffa4794fe4bb5181`,open thread 聚焦在 `docs/.vitepress/config.mts` 的侧栏重复 / 标签不一致,以及 `GFramework.Core`、`GFramework.Ecs.Arch`、`GFramework.Game` README 的 reader-facing 表格残留治理字段。
|
||||
- `2026-04-25` `docs/.vitepress/config.mts` 已保留 `source-generators` 栏目自有子页导航,但不再让 `api-reference` 侧栏重复跳回 `core`、`game`、`godot`、`ecs` 等独立栏目入口。
|
||||
- `2026-04-25` `GFramework.Core/README.md`、`GFramework.Ecs.Arch/README.md`、`GFramework.Game/README.md` 当前把 XML 阅读表统一收敛为“代表类型 + 阅读重点”,不再暴露日期、覆盖计数或 `已覆盖` 这类治理式字段。
|
||||
- `2026-04-25` `docs/zh-CN/tutorials`、`best-practices`、`troubleshooting`、`contributing`、`godot/resource` 的前一轮 reader-facing 与代码块标记治理已保持有效;本轮不再扩批这些同模板切片。
|
||||
- `2026-04-25` `docs/zh-CN/contributing.md` 中最后一个嵌套 fenced 示例已改写为转义围栏文本,现有 `validate-code-blocks.sh` 不再报告第 `631` 行警告。
|
||||
- `2026-04-25` 全量 `docs/zh-CN` 验证已无剩余代码块语言警告;前一轮触达的 `tutorials`、`best-practices`、`troubleshooting`、`godot/resource` 等栏目结果保持有效。
|
||||
- `2026-04-25` `docs/zh-CN/source-generators/index.md` 已按 PR `#292` review 调整“共享支撑模块”段落句式,避免“对读者更重要的判断是”这类拗口表达。
|
||||
- `2026-04-25` `tools/gframework-config-tool/README.md` 已新增 `Documentation` 章节,直接链接到 `docs/zh-CN/game/config-tool.md` 与 `config-system.md`,让工具 README 能回到完整中文接入文档。
|
||||
- `2026-04-26` `tools/gframework-config-tool/README.md` 已补 `Quick Start`,把安装扩展、配置 `configPath` / `schemasPath`、打开 Explorer、先跑校验、再进入表单 / 批量编辑的最小接入路径串起来,并把 `Validation Coverage` 的 `stable config-schema subset` 统一为 `current schema subset`。
|
||||
- `2026-04-25` 当前批次已补齐 meta-package / 安装面:`GFramework.csproj` 不再保留占位描述,`README.md`、`docs/zh-CN/index.md`、`docs/zh-CN/getting-started/installation.md` 当前明确说明聚合元包只聚合 `Core` + `Game`,并把安装入口更新到当前 `net8.0/net9.0/net10.0` 与 Godot `4.6.2` 基线。
|
||||
- `2026-04-25` `docs/zh-CN/game/config-tool.md` 已新增为 reader-facing 工具页,`docs/zh-CN/game/index.md`、`config-system.md`、`docs/.vitepress/config.mts` 与 `tools/gframework-config-tool/README.md` 当前把 VS Code 配置工具纳入 `Game` 配置工作流入口。
|
||||
- `2026-04-25` source-generators 栏目已修正 4 处真实契约问题:`GetNode` 显式路径 / `Lookup` 语义、枚举生成器实际开关、`Context Get` 集合注入边界,以及 `GFramework.SourceGenerators.Common` / `*.SourceGenerators.Abstractions` 的共享支撑层说明。
|
||||
- `2026-04-25` `GFramework.SourceGenerators.Common/README.md`、`GFramework.Core.SourceGenerators.Abstractions/README.md`、`GFramework.Godot.SourceGenerators.Abstractions/README.md` 已补齐本地目录说明,根 README 的“内部支撑模块”表可以直接跳到对应目录说明。
|
||||
- `Game` persistence docs surface 当前以 `docs/zh-CN/game/data.md`、`storage.md`、`serialization.md`、`setting.md`
|
||||
作为最小巡检集合;若后续 README、runtime public API 或 `PersistenceTests` 变动,应优先复核这一组页面。
|
||||
- `Godot` runtime 与 generator 入口当前以 `GFramework.Godot/README.md`、
|
||||
@ -42,8 +50,8 @@
|
||||
- `GFramework.Cqrs` 在当前 WSL / dotnet 环境下仍会读取失效的 fallback package folder,并在标准 build 中触发
|
||||
`MSB4276` / `MSB4018`;这是已知环境阻塞,不属于本轮文档回归。
|
||||
- 当前 WSL 会话里 `git.exe` 可解析但不能执行,应继续使用显式 `--git-dir` / `--work-tree` 绑定作为默认 Git 策略。
|
||||
- `docs/zh-CN/contributing.md:631` 仍有 1 条既有代码块语言警告;该位置属于嵌套 fenced 示例结构,不适合继续沿用前一轮“只补 opening fence”规则自动改写。
|
||||
- PR `#290` 当前 review 线程来自 bot,对 reader-facing 导航和文案一致性的期望比较细;本轮提交后仍需重新抓取 latest-head review,确认是否还有新的 open thread 或旧线程未自动关闭。
|
||||
- `dotnet build GFramework.csproj -c Release` 当前仍会输出仓库既有 analyzer warnings(如 `MA0158`、`MA0051`、`MA0004`);本轮仅修改文档与 package metadata,不扩展到 warning 清理。
|
||||
- PR `#292` 当前 review 线程仍主要来自 CodeRabbit,对 reader-facing 文案和文档入口连通性要求较细;本轮提交后仍需重新抓取 latest-head review,确认 open thread 是否已自动关闭。
|
||||
|
||||
## 归档指针
|
||||
|
||||
@ -62,6 +70,8 @@
|
||||
|
||||
- `2026-04-25` `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --json-output /tmp/gframework-current-pr-review.json`
|
||||
- 结果:通过;PR `#290` 处于 `OPEN`,latest head commit `54b8e5770af9ab3c8a86a396ffa4794fe4bb5181` 有 `2` 条 open thread(CodeRabbit `1`、Greptile `1`),测试汇总为 `2156 passed`,无 failed checks。
|
||||
- `2026-04-25` `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --json-output /tmp/gframework-current-pr-review.json`
|
||||
- 结果:通过;PR `#292` 处于 `OPEN`,latest head commit `b96565ffa367bade30f44c2d4e8955143fbff85e` 有 `2` 条 CodeRabbit open thread,测试汇总为 `2156 passed`,无 failed tests;另有 `Title check` inconclusive,属于 PR 标题元数据问题,不是仓库文件阻塞。
|
||||
- `2026-04-25` `bash .agents/skills/gframework-doc-refresh/scripts/validate-links.sh GFramework.Core/README.md GFramework.Ecs.Arch/README.md GFramework.Game/README.md`
|
||||
- 结果:通过;本轮 3 个模块 README 调整后链接目标仍然有效。
|
||||
- `2026-04-25` `bun run build`(工作目录:`docs/`)
|
||||
@ -75,7 +85,25 @@
|
||||
- `2026-04-25` `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/troubleshooting.md`
|
||||
- 结果:通过;错误输出与完整错误信息块补齐为 `text` 后页面验证通过。
|
||||
- `2026-04-25` `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/contributing.md`
|
||||
- 结果:通过,但保留 `docs/zh-CN/contributing.md:631` 的既有嵌套 fenced 示例警告;不属于本轮自动补标规则可安全收口的范围。
|
||||
- 结果:通过;嵌套 fenced 示例已改写为转义围栏文本,`docs/zh-CN/contributing.md` 不再保留代码块语言警告。
|
||||
- `2026-04-25` `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN`
|
||||
- 结果:通过;当前 `docs/zh-CN` 全量 frontmatter、链接与代码块校验均通过,不再保留既有代码块语言警告。
|
||||
- `2026-04-25` `bun run build`(工作目录:`docs/`)
|
||||
- 结果:通过;`contributing.md` 的 Mermaid 示例改写后站点仍可正常构建,仅保留既有大 chunk warning。
|
||||
- `2026-04-25` `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/source-generators`
|
||||
- 结果:通过;`source-generators` 栏目触达页 frontmatter、链接与代码块校验均通过。
|
||||
- `2026-04-25` `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/game`
|
||||
- 结果:通过;新增 `config-tool.md` 与 `Game` 栏目触达页 frontmatter、链接与代码块校验均通过。
|
||||
- `2026-04-25` `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/core/cqrs.md`
|
||||
- 结果:通过;`CQRS` 页补充 `Request` / stream 变体与协程入口后链接和代码块校验通过。
|
||||
- `2026-04-25` `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/index.md`
|
||||
- 结果:通过;首页 hero actions 与 feature 文案更新后 frontmatter、代码块校验通过。
|
||||
- `2026-04-25` `bash .agents/skills/gframework-doc-refresh/scripts/validate-links.sh README.md tools/gframework-config-tool/README.md GFramework.SourceGenerators.Common/README.md GFramework.Core.SourceGenerators.Abstractions/README.md GFramework.Godot.SourceGenerators.Abstractions/README.md`
|
||||
- 结果:通过;根 README、config tool README 与新增 3 个 support README 的链接目标有效。
|
||||
- `2026-04-25` `dotnet build GFramework.csproj -c Release`
|
||||
- 结果:通过;元包工程与聚合依赖可编译,输出 `357` 条既有 analyzer warnings,无新增错误。
|
||||
- `2026-04-25` `bun run build`(工作目录:`docs/`)
|
||||
- 结果:通过;meta-package / config tool / source-generators / CQRS 多批次文档更新后站点仍可构建,仅保留既有大 chunk warning。
|
||||
- `2026-04-25` `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/godot/resource.md`
|
||||
- 结果:通过;`Godot` 资源页剩余 bare opening fence 已补齐语言标记。
|
||||
- `2026-04-25` `bun run build`(工作目录:`docs/`)
|
||||
@ -125,15 +153,18 @@
|
||||
`1` 条 Greptile open thread,无 failed checks,测试汇总为 `2156 passed`。
|
||||
- `2026-04-25` `bun run build`(工作目录:`docs/`)
|
||||
- 结果:通过;`docs/zh-CN/api-reference/index.md` 的站内入口标签统一为语义化写法后站点仍可正常构建,仅保留既有大 chunk warning。
|
||||
- `2026-04-25` `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN`
|
||||
- 结果:通过;本轮触达页面的 frontmatter、链接与代码块校验均通过,脚本仅继续报告仓库中既有页面的“代码块缺少语言标记”警告。
|
||||
- `2026-04-26` `bun run test`(工作目录:`tools/gframework-config-tool/`)
|
||||
- 结果:通过;`122` 个测试全部通过,说明 README 收口没有影响该工具现有测试面。
|
||||
- `2026-04-26` `bun run package:vsix`(工作目录:`tools/gframework-config-tool/`)
|
||||
- 结果:通过;成功生成 `gframework-config-tool-0.0.3.vsix`,满足本轮工具模块的最小 build validation。
|
||||
|
||||
## 下一步
|
||||
|
||||
1. 完成本轮修改后先跑 `bun run build`(工作目录 `docs/`)与受影响 README 链接校验,再提交当前 PR review 收口批次。
|
||||
2. 提交完成后重新抓取 `$gframework-pr-review` 确认 PR `#290` 的 latest-head review 是否已清空 open thread,再决定是否继续巡检其他 reader-facing 文档。
|
||||
3. 若后续继续处理公开文档,优先人工评估 `docs/zh-CN/contributing.md:631` 的嵌套 fenced 示例是否值得做结构化改写,而不是继续沿用“只补 opening fence”的自动批处理规则。
|
||||
4. 若后续分支继续调整 `Game` persistence runtime、README 或公共 API,优先复核 `docs/zh-CN/game/data.md`、
|
||||
1. 运行 `bun run test`(工作目录:`tools/gframework-config-tool/`),完成本轮 README 收口改动的最小模块验证。
|
||||
2. 验证通过后重新抓取 `$gframework-pr-review`,确认 PR `#292` 的 latest-head review 是否只剩过期线程或已自动清空。
|
||||
3. 若本轮提交后 branch diff vs `origin/main` 仍明显低于 `50` 文件阈值,再决定是否继续追加低风险 reader-facing 文档切片。
|
||||
4. 若后续继续扩批,优先在已识别但尚未扩写的低风险 reader-facing 方向里选择下一组:config tool 更深的 adoption 示例、首页 / 安装页的进一步选包引导,或其它 repo-visible support surface 的本地说明补齐。
|
||||
5. 若后续分支继续调整 `Game` persistence runtime、README 或公共 API,优先复核 `docs/zh-CN/game/data.md`、
|
||||
`storage.md`、`serialization.md`、`setting.md` 与 landing page 是否仍保持同一套职责边界。
|
||||
5. 若后续分支继续调整 `Godot` generator 接法,优先复核 `GFramework.Godot.SourceGenerators/README.md`、
|
||||
6. 若后续分支继续调整 `Godot` generator 接法,优先复核 `GFramework.Godot.SourceGenerators/README.md`、
|
||||
`docs/zh-CN/tutorials/godot-integration.md` 与相关专题页是否仍保持一致。
|
||||
|
||||
@ -1,7 +1,124 @@
|
||||
# Documentation Full Coverage Governance Trace
|
||||
|
||||
## 2026-04-26
|
||||
|
||||
### 当前恢复点:RP-040
|
||||
|
||||
- 本轮继续从 `$gframework-pr-review` 恢复,沿用显式 `--git-dir` / `--work-tree` 绑定确认当前分支仍为 `docs/sdk-update-documentation`,并重新抓取 PR `#292` 的 latest-head review。
|
||||
- 使用 `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --json-output /tmp/gframework-current-pr-review.json` 抓取后确认:PR `#292` 最新 reviewed commit 为 `d3d62cf4541063c46458f88eea0f5acd1b4503f9`,failed checks 为 `0`,测试汇总仍为 `2156 passed`;剩余 `2` 条 CodeRabbit open thread 都落在 `tools/gframework-config-tool/README.md`。
|
||||
- 本地逐项复核后确认:缺少 `docs/zh-CN` 链接的评论已经过期,因为 README 当前已有 `Documentation` 章节;仍成立的是补最小接入路径,以及统一 `stable config-schema subset` / `current schema subset` 术语。
|
||||
|
||||
### 当前决策(RP-040)
|
||||
|
||||
- 接受当前 latest-head review 中仍然成立的两项 README 收口:新增 `Quick Start` 最小接入路径,并统一校验支持范围术语。
|
||||
- 不对已经过期的“缺少中文文档入口链接”线程做额外扩展,只在本地结果里保留“已验证为 stale”的结论,等待后续 PR review 刷新反映最新状态。
|
||||
- 继续遵守 active topic 的恢复要求,在同一轮里同步更新 tracking / trace,并对直接受影响的工具模块执行最小验证。
|
||||
|
||||
### 当前验证(RP-040)
|
||||
|
||||
- PR review 抓取:
|
||||
- `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --json-output /tmp/gframework-current-pr-review.json`
|
||||
- 结果:通过;PR `#292` 处于 `OPEN`,latest head review 还有 `2` 条 CodeRabbit open thread,failed checks 为 `0`,测试汇总为 `2156 passed`。
|
||||
- 工具 README 收口:
|
||||
- `tools/gframework-config-tool/README.md`
|
||||
- 结果:已新增 `Quick Start` 段落,并把 `Validation Coverage` 术语统一为 `current schema subset`。
|
||||
- 工具测试:
|
||||
- `bun run test`(工作目录:`tools/gframework-config-tool/`)
|
||||
- 结果:通过;`122` 个测试全部通过。
|
||||
- 工具打包:
|
||||
- `bun run package:vsix`(工作目录:`tools/gframework-config-tool/`)
|
||||
- 结果:通过;成功生成 `gframework-config-tool-0.0.3.vsix`,确认工具模块可完成最小打包验证。
|
||||
|
||||
## 2026-04-25
|
||||
|
||||
### 当前恢复点:RP-039
|
||||
|
||||
- 本轮从 `$gframework-pr-review` 重新进入,先按仓库规则读取 `AGENTS.md`、`.ai/environment/tools.ai.yaml`、`ai-plan/public/README.md` 与 active topic tracking / trace,并继续使用显式 `--git-dir` / `--work-tree` 绑定确认当前分支为 `docs/sdk-update-documentation`。
|
||||
- 使用 `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --json-output /tmp/gframework-current-pr-review.json` 抓取后确认:PR `#292` 最新 reviewed commit 为 `b96565ffa367bade30f44c2d4e8955143fbff85e`,latest head review 仅剩 `2` 条 CodeRabbit open thread,无 failed tests;唯一 failed check 为 `Title check` inconclusive,属于 PR 标题文案元数据提示。
|
||||
- 本地逐项复核后,两条 review 仍成立且都属于低风险 reader-facing 修正:
|
||||
- `docs/zh-CN/source-generators/index.md` 的“共享支撑模块”段落中,句式“对读者更重要的判断是”略拗口。
|
||||
- `tools/gframework-config-tool/README.md` 缺少通往 `docs/zh-CN/game/config-tool.md` 的中文接入文档入口。
|
||||
|
||||
### 当前决策(RP-039)
|
||||
|
||||
- 接受这两条 latest-head review,并限定本轮只做文案可读性与 README 入口补链,不扩展到未被当前 review 指向的其它页面。
|
||||
- `Title check` 不通过仓库文件修复;保持在本轮结果中显式记录,等待后续通过 GitHub PR 标题更新处理。
|
||||
- 继续沿用 active topic 的治理要求,在同一变更里同步更新 tracking / trace,保证后续从 PR review 恢复时能直接看到最新 commit 与剩余风险。
|
||||
|
||||
### 当前验证(RP-039)
|
||||
|
||||
- PR review 抓取:
|
||||
- `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --json-output /tmp/gframework-current-pr-review.json`
|
||||
- 结果:通过;PR `#292` 处于 `OPEN`,latest head review 还有 `2` 条 CodeRabbit open thread,测试汇总为 `2156 passed`,无 failed tests,另有 `Title check` inconclusive。
|
||||
|
||||
### 当前恢复点:RP-038
|
||||
|
||||
- 用户明确要求从“低效的单次批次”切到“循环跑到接近阈值”,并允许通过 subagent 避免主线程上下文过长;因此本轮把批处理目标从 PR `#290` 的单点收口扩展为“覆盖整个项目功能的 reader-facing 文档补齐”。
|
||||
- 先在主线程确认 critical path 仍是“选定低风险文档切片并控制 branch-size stop condition”,再委派 3 个 explorer 做只读巡检:
|
||||
- source-generator support modules / 文档失真点
|
||||
- CQRS 文档覆盖缺口
|
||||
- repo-root / tooling / meta-package surface
|
||||
- 接受的 explorer 结论:
|
||||
- `CQRS` 当前不需要扩独立栏目;最小有用修复是补 `docs/zh-CN/core/cqrs.md` 对 `RequestBase`、stream command/query 与协程入口的说明。
|
||||
- source-generators 当前最有价值的是修正文档失真,并补清楚 `GFramework.SourceGenerators.Common` 与 `*.SourceGenerators.Abstractions` 的共享支撑层语义。
|
||||
- repo-root / tooling 当前最缺的是 meta-package / install surface、VS Code config tool adoption path,以及 repo-visible support module README。
|
||||
- 由此收敛出 5 组连续低风险批次:
|
||||
- meta-package / 安装入口
|
||||
- config tool adoption
|
||||
- source-generators 真实契约修正
|
||||
- CQRS `Request` / stream 覆盖补齐
|
||||
- generator support module README
|
||||
|
||||
### 当前决策(RP-038)
|
||||
|
||||
- 不把 `CQRS` 从 `Core` 导航中抽成新栏目;本轮优先修正 reader-facing 覆盖缺口,而不是引入新的站点结构。
|
||||
- 对 repo-visible support modules,不扩成新的 docs 栏目,而是在各目录本地补 `README.md` 说明“为什么存在、跟谁一起走、什么时候需要读这里”。
|
||||
- 对 config tool,不新建顶级 `tooling/` 栏目,而是挂到 `Game` 下,保持它与 `config-system` 的采用路径一致。
|
||||
- stop condition 仍按 `origin/main` 与 `50` changed files 追踪;本轮提交前工作树已触达 `18` 个文件,仍明显低于阈值。
|
||||
|
||||
### 当前验证(RP-038)
|
||||
|
||||
- 文档栏目校验:
|
||||
- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/source-generators`
|
||||
- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/game`
|
||||
- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/core/cqrs.md`
|
||||
- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/index.md`
|
||||
- 结果:通过;触达页 frontmatter、链接与代码块校验通过。
|
||||
- README / 链接校验:
|
||||
- `bash .agents/skills/gframework-doc-refresh/scripts/validate-links.sh README.md tools/gframework-config-tool/README.md GFramework.SourceGenerators.Common/README.md GFramework.Core.SourceGenerators.Abstractions/README.md GFramework.Godot.SourceGenerators.Abstractions/README.md`
|
||||
- 结果:通过;根 README、config tool README 与新增 support README 的链接目标有效。
|
||||
- 站点构建:
|
||||
- `bun run build`(工作目录:`docs/`)
|
||||
- 结果:通过;站点可构建,仅保留既有大 chunk warning。
|
||||
- 元包编译:
|
||||
- `dotnet build GFramework.csproj -c Release`
|
||||
- 结果:通过;输出 `357` 条既有 analyzer warnings,无新增错误。
|
||||
|
||||
### 当前恢复点:RP-037
|
||||
|
||||
- 通过 `$gframework-batch-boot 50` 重新进入后,先按技能要求读取 `AGENTS.md`、`.ai/environment/tools.ai.yaml`、`ai-plan/public/README.md`、active topic tracking / trace,并确认当前 worktree 仍映射到 `documentation-full-coverage-governance`。
|
||||
- 使用显式 `git --git-dir=<repo>/.git/worktrees/GFramework-update-documentation --work-tree=<worktree-root>` 绑定确认 baseline 采用 `origin/main` `79934f7`(`2026-04-25 16:15:55 +08:00`);branch diff vs baseline 当前为 `0` files,工作树仅有本批次改动。
|
||||
- 全量运行 `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN` 后确认 reader-facing 文档仅剩 `docs/zh-CN/contributing.md:631` 这一条既有代码块语言警告,适合作为单文件低风险批次收口。
|
||||
- 将 `docs/zh-CN/contributing.md` 的 Mermaid 示例从“真实嵌套 triple-backtick”改写为“外层 fenced block + 内层转义围栏文本”,避免当前 `validate-code-blocks.sh` 的简单 `^```` 状态机把内层 closing fence 误判成缺语言标记的新 opening fence。
|
||||
|
||||
### 当前决策(RP-037)
|
||||
|
||||
- 当前批处理目标收敛为“消除 `contributing.md` 中最后一个剩余代码块语言 warning”,不再继续扩展到别的栏目页。
|
||||
- 继续沿用 `origin/main` 作为 branch-size stop condition 基线,主指标仍是 `50` changed files;本批次只新增 1 个工作树文件,远未逼近阈值。
|
||||
- 对这类“文档中展示 Markdown 代码块”的示例,优先选择仓库现有校验脚本可稳定识别的转义文本写法,而不是依赖嵌套 fenced block 的解析细节。
|
||||
|
||||
### 当前验证(RP-037)
|
||||
|
||||
- 文档单文件校验:
|
||||
- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/contributing.md`
|
||||
- 结果:通过;`docs/zh-CN/contributing.md` 不再报告第 `631` 行代码块语言警告。
|
||||
- 文档全量校验:
|
||||
- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN`
|
||||
- 结果:通过;当前 `docs/zh-CN` 的 frontmatter、链接与代码块校验全部通过。
|
||||
- 站点构建:
|
||||
- `bun run build`(工作目录:`docs/`)
|
||||
- 结果:通过;站点仍可构建,仅保留既有大 chunk warning。
|
||||
|
||||
### 当前恢复点:RP-036
|
||||
|
||||
- 本轮从 `$gframework-pr-review` 重新进入,目标不再是扩批,而是核对 PR `#290` latest-head review 仍未关闭的 reader-facing 文档问题。
|
||||
|
||||
@ -0,0 +1,77 @@
|
||||
# Semantic Release 版本迁移归档(2026-04-26)
|
||||
|
||||
## 归档范围
|
||||
|
||||
- Phase 1 初始迁移结论
|
||||
- `SEMREL-RP-001` 到 `SEMREL-RP-003` 期间已稳定的 PR review 修复
|
||||
- active tracking 中已不需要继续占据默认恢复入口的历史验证明细
|
||||
|
||||
## 历史完成项
|
||||
|
||||
- 已确认当前版本入口为 `.github/workflows/auto-tag.yml`,现状始终执行 `PATCH + 1`
|
||||
- 已确认当前 `.github/workflows/publish.yml` 由 tag 触发,并负责 `.nupkg` 打包、发布和 GitHub Release
|
||||
- 已确认最新 tag 为 `v0.0.222`
|
||||
- 已确认 `v0.0.222..HEAD` 之间存在 `feat(...)` 提交,按目标规则首次 dry-run 预期版本应为 `v0.1.0`
|
||||
- 已新增 `.releaserc.json`,仅保留 `@semantic-release/commit-analyzer` 与
|
||||
`@semantic-release/release-notes-generator`,避免 `semantic-release` 直接创建 GitHub Release
|
||||
- 已将 `.releaserc.json` 的 `commit-analyzer` / `release-notes-generator` 同步切换到 `conventionalcommits`
|
||||
preset,并显式声明:
|
||||
- `breaking -> major`
|
||||
- `revert -> patch`
|
||||
- `feat -> minor`
|
||||
- `fix/perf/refactor -> patch`
|
||||
- `docs/test/chore/build/ci/style -> no release`
|
||||
- 已将 `.github/workflows/auto-tag.yml` 重写为:
|
||||
- `workflow_dispatch` 启动后总是先跑 `preview`
|
||||
- `preview` 只执行 dry-run,输出 `last_tag`、`next_version` 与 `next_tag`
|
||||
- `release` job 依赖 `preview` 输出,并通过 `release-approval` environment 暂停等待人工确认
|
||||
- 人工批准后,`release` 在同一 SHA 上执行真实打 tag,并把 preview / release 结果都写入 job summary
|
||||
- 已按上一轮 PR review 修复 `auto-tag.yml`:
|
||||
- 删除 preview job 中与 job 级 `if` 重复的运行时分支校验
|
||||
- 为 release job 增加 `needs.preview.result == 'success'` 守卫
|
||||
- 为 preview / release 的 semantic-release action 显式安装 `conventional-changelog-conventionalcommits@9.1.0`
|
||||
- 在 release 前通过 GitHub API 校验 `PAT_TOKEN` 是否真实可访问当前仓库
|
||||
- 在 preview / release summary 中补充 snapshot 语义与生成的 release notes
|
||||
- 已修复 preview 链路的第一轮鉴权前提:
|
||||
- 在 preview 开始前通过 GitHub API 校验 `PAT_TOKEN`
|
||||
- 将 preview 的 `semantic-release` 令牌从 `${{ github.token }}` 切换为 `${{ secrets.PAT_TOKEN }}`
|
||||
- 在 preview summary 中明确说明 dry-run 仍会执行远端 push 权限探测,避免将 403 误判为版本计算失败
|
||||
- 已明确真实打 tag 仍使用 `PAT_TOKEN`,因为 `GITHUB_TOKEN` 推送的 tag 不会继续触发 `publish.yml`
|
||||
- 已更新 `AGENTS.md` 的 Conventional Commit 规则,显式补充:
|
||||
- `fix/perf/refactor -> patch`
|
||||
- `docs/test/chore/build/ci/style -> no release`
|
||||
- `BREAKING CHANGE` 或 `!` header -> major
|
||||
- 已移除基于 `workflow_run` 和 `[release ci]` 的自动发版门闸,后续版本预览与真实发版都由维护者手动触发
|
||||
- 已将 release 流程从“两次独立 workflow_dispatch”收敛为“同一次 run 里 preview + 审批 + release”的链路
|
||||
- 已精简 active trace,移除已废弃的 `release_mode=preview|release` 中间方案,保留当前有效恢复点
|
||||
|
||||
## 历史验证
|
||||
|
||||
- `git describe --tags --abbrev=0`
|
||||
- 结果:通过
|
||||
- 备注:当前最新 tag 为 `v0.0.222`
|
||||
- `git log --pretty=format:%h%x09%s v0.0.222..HEAD`
|
||||
- 结果:通过
|
||||
- 备注:最近版本窗口内存在多条 `feat(...)`,后续 dry-run 预期应提升 `minor`
|
||||
- `dotnet build GFramework.Core.Abstractions/GFramework.Core.Abstractions.csproj -c Release -p:RestoreFallbackFolders=`
|
||||
- 结果:通过
|
||||
- 备注:`GFramework.Core.Abstractions` 与 `GFramework.Cqrs.Abstractions` Release 构建通过,`0 warning / 0 error`
|
||||
- `npx --yes semantic-release --dry-run --no-ci`
|
||||
- 结果:受阻
|
||||
- 备注:当前工作树的本地 tag 历史在 `git fetch --tags` 阶段出现 `would clobber existing tag` 冲突,不能直接作为 dry-run 环境
|
||||
- `git clone --branch main --single-branch git@github.com:GeWuYou/GFramework.git /tmp/gframework-semrel-dryrun`
|
||||
- 结果:通过
|
||||
- 备注:已建立干净临时克隆用于 dry-run 验证
|
||||
- `npx --yes semantic-release --dry-run --no-ci`(在 `/tmp/gframework-semrel-dryrun`)
|
||||
- 结果:通过
|
||||
- 备注:dry-run 成功识别 `v0.0.222` 为最新 release,并分析 `269` 个提交;按当前规则会提升到下一次 `minor` 发布,预期 tag 为 `v0.1.0`
|
||||
- `npx --yes -p semantic-release -p conventional-changelog-conventionalcommits@9.1.0 semantic-release --dry-run --no-ci`(在 `/tmp/gframework-semrel-dryrun`)
|
||||
- 结果:通过
|
||||
- 备注:成功加载 `@semantic-release/commit-analyzer` 与 `@semantic-release/release-notes-generator`,证明
|
||||
`conventionalcommits` preset 包可被解析;本次 dry-run 未继续出版本,是因为干净克隆的 `main` 已落后远端
|
||||
- `dotnet build GFramework.Core.Abstractions/GFramework.Core.Abstractions.csproj -c Release -p:RestoreFallbackFolders=`(手动发版入口调整后复验)
|
||||
- 结果:通过
|
||||
- 备注:`0 warning / 0 error`
|
||||
- `dotnet build GFramework.sln -c Release`
|
||||
- 结果:通过
|
||||
- 备注:Release 构建通过,`639 warning / 0 error`;warning 为仓库既有基线;preview 鉴权修复后复验结果与基线一致
|
||||
@ -0,0 +1,55 @@
|
||||
# Semantic Release 版本迁移跟踪
|
||||
|
||||
## 目标
|
||||
|
||||
将版本管理从固定 `patch + 1` 的自动打 tag 迁移到 `semantic-release`,同时保留现有 `.github/workflows/publish.yml`
|
||||
的 tag 触发打包、NuGet 发布、GitHub Packages 发布和 GitHub Release 流程。
|
||||
|
||||
- 用 `cycjimmy/semantic-release-action` 替换 `auto-tag.yml` 的版本判断和打 tag 逻辑
|
||||
- 保留 `publish.yml` 的现有发布实现,不重写 NuGet 流程
|
||||
- 避免 `semantic-release` 与 `publish.yml` 重复创建 GitHub Release
|
||||
- 将版本规则固定为 `feat -> minor`、`fix/perf/refactor -> patch`、`BREAKING CHANGE` 或 `! -> major`
|
||||
- 为手动 `workflow_dispatch` 保留 dry-run 验证入口,先验证最近提交会算出什么版本
|
||||
|
||||
## 当前恢复点
|
||||
|
||||
- 恢复点编号:`SEMREL-RP-004`
|
||||
- 当前阶段:`Phase 2`
|
||||
- 当前焦点:
|
||||
- 将 preview / release 的 PAT 校验收敛为同一个复用入口,避免后续修复在两段脚本间漂移
|
||||
- 在 PAT 校验阶段提前识别“仅有 read、没有 push”的 token,真正覆盖 `git push --dry-run` 的权限前提
|
||||
- 将 active tracking 中已稳定的历史完成项归档,恢复默认入口的可读性
|
||||
|
||||
### 已知风险
|
||||
|
||||
- `GITHUB_TOKEN` 推送 tag 不会再触发另一个 workflow,真实发布仍需要 `PAT_TOKEN`
|
||||
- `semantic-release` preview 虽然不会真实推送 tag,但仍会执行远端 `git push --dry-run` 权限探测;如果 PAT 仅具备
|
||||
read 权限、没有 `contents:write`,仍然会先于版本分析阶段失败
|
||||
- `semantic-release` 的版本判断完全依赖 Conventional Commits;不规范提交会直接影响版本计算
|
||||
- `cycjimmy/semantic-release-action@v6` 需要在 preview / release 两端都安装 `conventional-changelog-conventionalcommits`
|
||||
以保证 `conventionalcommits` preset 在 GitHub Actions 中可解析
|
||||
- 当前仓库本地 `dotnet clean/build` 会带出既有 analyzer warnings;本轮仅修正发版配置与文档,不额外处理这些历史 warning
|
||||
|
||||
## 已完成
|
||||
|
||||
- 历史迁移结论与 `SEMREL-RP-001` 到 `SEMREL-RP-003` 的稳定完成项已归档到
|
||||
`ai-plan/public/semantic-release-versioning/archive/todos/semantic-release-versioning-2026-04-26.md`
|
||||
- 已将 preview / release 两段重复的 PAT 校验提取到 `.github/actions/validate-pat/action.yml`
|
||||
- 已在 PAT 校验中补充 `permissions.push` 断言,避免 read-only token 通过 API 探活却在
|
||||
`semantic-release` 的 `git push --dry-run` 阶段才失败
|
||||
- 已为 PAT 校验的 `mktemp` 文件补充 `trap` 清理,避免异常退出时遗留临时文件路径干扰日志
|
||||
- 已同步更新 active trace 到 `SEMREL-RP-004`,记录本轮 PR review 收敛结果
|
||||
|
||||
## 验证
|
||||
|
||||
- `dotnet build GFramework.sln -c Release`
|
||||
- 结果:通过
|
||||
- 备注:Release 构建通过,`639 warning / 0 error`;warning 为仓库既有基线,preview 鉴权修复后与本轮 PAT 校验收敛后复验结果一致
|
||||
- 更早阶段的 dry-run / tag /抽象项目验证已归档到
|
||||
`ai-plan/public/semantic-release-versioning/archive/todos/semantic-release-versioning-2026-04-26.md`
|
||||
|
||||
## 下一步
|
||||
|
||||
1. 手动重跑 `Semantic Release Version and Tag` 的 preview job,确认 read-only PAT 会在校验步骤提前失败、可写 PAT 不再进入 `git push --dry-run ... 403`
|
||||
2. 推送本轮修复后重新抓取 PR review,确认 CodeRabbit / Greptile 的 open threads 已转为过时或可关闭
|
||||
3. 如 CI 仍报告权限边界问题,再决定是否将 PAT 校验升级为更贴近真实链路的远端 git 探测
|
||||
@ -0,0 +1,53 @@
|
||||
# Semantic Release 版本迁移追踪
|
||||
|
||||
## 2026-04-26
|
||||
|
||||
### 当前恢复点(SEMREL-RP-004)
|
||||
|
||||
- 当前链路:
|
||||
- `workflow_dispatch` 手动启动
|
||||
- `preview` 对 dispatch SHA 执行 dry-run
|
||||
- `release-approval` environment 审批
|
||||
- `release` 在同一次 run、同一 SHA 上执行真实打 tag
|
||||
- 当前规则:
|
||||
- `conventionalcommits` preset 负责解析 `feat!:` / `feat(scope)!:` 与 `BREAKING CHANGE`
|
||||
- `feat -> minor`
|
||||
- `fix/perf/refactor -> patch`
|
||||
- `docs/test/chore/build/ci/style -> no release`
|
||||
- `breaking -> major`
|
||||
- 当前 workflow 加固:
|
||||
- `release` 额外要求 `needs.preview.result == 'success'`
|
||||
- `PAT_TOKEN` 通过复用的 composite action 统一校验
|
||||
- GitHub API 校验额外断言 `.permissions.push == true`,避免 read-only PAT 混过 preview
|
||||
- preview / release summary 会展示 snapshot 语义与生成的 release notes
|
||||
- `preview` 改为先校验并使用 `PAT_TOKEN`,避免 `github-actions[bot]` 在 dry-run 的远端 push 权限探测中触发 403
|
||||
|
||||
### 本轮关键决策
|
||||
|
||||
- 保留 `@semantic-release/release-notes-generator`,但不再让它白跑:
|
||||
- 继续生成 notes
|
||||
- 将 notes 写入 GitHub Actions summary
|
||||
- preview 与 release 共用 `PAT_TOKEN`:
|
||||
- `semantic-release` dry-run 仍会执行 `git push --dry-run`
|
||||
- preview 如果继续使用 `${{ github.token }}`,会先被 `github-actions[bot]` 的仓库写权限拦住,日志不再具有可读性
|
||||
- API 探活必须覆盖 push 权限:
|
||||
- 单纯 `GET /repos/{owner}/{repo}` 的 `200` 只能证明 read access
|
||||
- 本轮直接读取响应体里的 `permissions.push`,让 preview 在更接近真实失败原因的位置终止
|
||||
- 不保留已废弃的 `release_mode=preview|release` 中间方案:
|
||||
- active trace 只保留当前有效链路
|
||||
- 历史演进以 tracking 归档文件为准,active tracking 仅保留当前恢复入口
|
||||
|
||||
### 验证结论
|
||||
|
||||
1. `npx --yes -p semantic-release -p conventional-changelog-conventionalcommits@9.1.0 semantic-release --dry-run --no-ci`
|
||||
- 已确认新 preset 包可加载,`commit-analyzer` 与 `release-notes-generator` 正常初始化
|
||||
- 本次 dry-run 未继续出版本,因为干净克隆的 `main` 已落后远端
|
||||
2. `dotnet build GFramework.sln -c Release`
|
||||
- 通过,`639 warning / 0 error`
|
||||
- warning 为仓库既有基线,本轮 workflow / ai-plan 调整未新增关联 warning
|
||||
|
||||
### 下一步
|
||||
|
||||
1. 重跑 `auto-tag.yml` 的 preview,确认 read-only PAT 会在校验步骤提前失败、可写 PAT 不再落到 `EGITNOPERMISSION`
|
||||
2. 复查当前 PR 的 open review threads 是否已与本地修复对齐
|
||||
3. 创建提交并推送当前分支
|
||||
@ -211,6 +211,7 @@ export default defineConfig({
|
||||
items: [
|
||||
{ text: '概览', link: '/zh-CN/game/' },
|
||||
{ text: '内容配置系统', link: '/zh-CN/game/config-system' },
|
||||
{ text: 'VS Code 配置工具', link: '/zh-CN/game/config-tool' },
|
||||
{ text: '数据管理', link: '/zh-CN/game/data' },
|
||||
{ text: '场景系统', link: '/zh-CN/game/scene' },
|
||||
{ text: 'UI 系统', link: '/zh-CN/game/ui' },
|
||||
|
||||
@ -70,6 +70,16 @@ description: GFramework 的 API 阅读入口,按模块映射 README、专题
|
||||
这些目录当前不是独立消费模块,因此不单独维护站内 API 参考入口。它们的公开说明跟随所属模块 README 和
|
||||
`source-generators` 栏目维护。
|
||||
|
||||
更准确地说:
|
||||
|
||||
- `*.SourceGenerators.Abstractions`
|
||||
- 主要定义公开 attribute 和最小契约,供对应生成器与消费端项目共享。
|
||||
- `GFramework.SourceGenerators.Common`
|
||||
- 主要提供共享生成器基类、通用 diagnostics,以及生成方法冲突等跨模块约束。
|
||||
|
||||
如果你在 `Core`、`CQRS`、`Game`、`Godot` 的生成器页面里遇到相似的 diagnostics 或冲突语义,优先把它理解为共享
|
||||
支撑层在复用同一套规则,而不是把这些目录当成新的安装入口。
|
||||
|
||||
## 使用方式
|
||||
|
||||
把本页当成“API 阅读导航”而不是“签名快照”:
|
||||
|
||||
@ -622,15 +622,15 @@ await architecture.SendCommandAsync(new AttackCommand
|
||||
|
||||
使用 Mermaid 或 ASCII 图表说明复杂概念:
|
||||
|
||||
```markdown
|
||||
```mermaid
|
||||
````markdown
|
||||
\`\`\`mermaid
|
||||
graph TD
|
||||
A[Controller] --> B[Command]
|
||||
B --> C[System]
|
||||
C --> D[Model]
|
||||
```
|
||||
\`\`\`
|
||||
|
||||
```
|
||||
````
|
||||
|
||||
## PR 流程
|
||||
|
||||
|
||||
@ -93,6 +93,9 @@ var playerId = await architecture.Context.SendRequestAsync(
|
||||
- `PublishAsync(...)`
|
||||
- `CreateStream(...)`
|
||||
|
||||
如果你在协程驱动的调用链里工作,`GFramework.Core` 还提供了 `CqrsCoroutineExtensions.SendCommandCoroutine(...)`
|
||||
这类桥接入口,用来把 CQRS 调度接回协程系统。
|
||||
|
||||
## 统一请求模型
|
||||
|
||||
这套 runtime 不只处理 command,也统一处理:
|
||||
@ -106,6 +109,30 @@ var playerId = await architecture.Context.SendRequestAsync(
|
||||
|
||||
新代码通常不需要再分别设计“命令总线”“查询总线”和另一套通知分发语义。
|
||||
|
||||
## Request 与流式变体
|
||||
|
||||
除了最常见的 `Command` / `Query` / `Notification`,当前公开面还覆盖两类容易被忽略的入口:
|
||||
|
||||
### 普通 Request
|
||||
|
||||
如果你的请求不想再被读者预设成“命令”或“查询”,可以直接使用:
|
||||
|
||||
- `RequestBase<TInput, TResponse>`
|
||||
- `AbstractRequestHandler<TRequest, TResponse>`
|
||||
|
||||
它们仍然走统一的 `SendRequestAsync(...)` 调度入口,只是把语义保持在更中性的 `Request` 层。
|
||||
|
||||
### 流式 Command / Query
|
||||
|
||||
如果你需要返回 `IAsyncEnumerable<T>`,除了通用的 `IStreamRequest<TResponse>`,当前也提供更明确的专用契约:
|
||||
|
||||
- `IStreamCommand<TResponse>`
|
||||
- `IStreamQuery<TResponse>`
|
||||
- `AbstractStreamCommandHandler<TCommand, TResponse>`
|
||||
- `AbstractStreamQueryHandler<TQuery, TResponse>`
|
||||
|
||||
这几类处理器最终仍然通过 `CreateStream(...)` 进入统一的 CQRS runtime,而不是另一套独立流式总线。
|
||||
|
||||
## 处理器注册与生成器协作
|
||||
|
||||
在标准 `Architecture` 启动路径中,CQRS runtime 会自动接入基础设施。你通常只需要在 `OnInitialize()` 里追加行为或额外程序集:
|
||||
@ -176,14 +203,15 @@ RegisterCqrsPipelineBehavior<LoggingBehavior<,>>();
|
||||
| 类型族 | 代表类型 | 建议先确认什么 |
|
||||
| --- | --- | --- |
|
||||
| `GFramework.Cqrs.Abstractions/Cqrs/` | `ICqrsRuntime`、`ICqrsHandlerRegistrar`、`IPipelineBehavior<,>`、`IRequestHandler<,>`、`Unit` | 请求、处理器和 runtime seam 的最小契约 |
|
||||
| `GFramework.Cqrs/Command` `Query` `Notification` `Request` `Extensions` | `CommandBase<TInput, TResponse>`、`QueryBase<TInput, TResponse>`、`NotificationBase<TInput>`、`ContextAwareCqrsExtensions` | 业务侧常用基类和上下文发送入口 |
|
||||
| `GFramework.Cqrs/Cqrs/` | `AbstractCommandHandler<,>`、`AbstractQueryHandler<,>`、`AbstractNotificationHandler<>`、`LoggingBehavior<,>` | 默认处理器基类、上下文注入与行为管道 |
|
||||
| `GFramework.Cqrs/Command` `Query` `Notification` `Request` `Extensions` | `CommandBase<TInput, TResponse>`、`QueryBase<TInput, TResponse>`、`NotificationBase<TInput>`、`RequestBase<TInput, TResponse>`、`ContextAwareCqrsExtensions` | 业务侧常用基类和上下文发送入口 |
|
||||
| `GFramework.Cqrs/Cqrs/` | `AbstractCommandHandler<,>`、`AbstractQueryHandler<,>`、`AbstractRequestHandler<,>`、`AbstractStreamCommandHandler<,>`、`AbstractStreamQueryHandler<,>`、`LoggingBehavior<,>` | 默认处理器基类、上下文注入、流式处理与行为管道 |
|
||||
| `GFramework.Cqrs` 根入口与 `Internal/` | `CqrsRuntimeFactory`、`ICqrsHandlerRegistry`、`CqrsHandlerRegistryAttribute`、`CqrsReflectionFallbackAttribute`、`DefaultCqrsRegistrationService` | runtime 创建入口、registry 协议、fallback 语义和程序集去重规则 |
|
||||
| `GFramework.Cqrs.SourceGenerators/Cqrs/` | `CqrsHandlerRegistryGenerator`、`RuntimeTypeReferenceSpec`、`OrderedRegistrationKind` | 生成注册器、精确 type lookup 和 fallback 诊断边界 |
|
||||
|
||||
## 继续阅读
|
||||
|
||||
- 架构入口:[architecture](./architecture.md)
|
||||
- 上下文入口:[context](./context.md)
|
||||
- 架构入口:[架构与上下文](./architecture.md)
|
||||
- 上下文入口:[Context 上下文](./context.md)
|
||||
- 生成器专题:[CQRS Handler Registry 生成器](../source-generators/cqrs-handler-registry-generator.md)
|
||||
- 协程接法:[协程系统](./coroutine.md)
|
||||
- 模块说明:[CQRS 运行时说明](https://github.com/GeWuYou/GFramework/blob/main/GFramework.Cqrs/README.md)
|
||||
|
||||
@ -21,6 +21,8 @@ description: 说明 GFramework.Game 配置系统的定位、目录约定、生
|
||||
- Source Generator 生成配置类型、表包装、单表注册/访问辅助,以及项目级聚合注册目录
|
||||
- VS Code 插件提供配置浏览、raw 编辑、schema 打开、递归轻量校验和嵌套对象表单入口
|
||||
|
||||
对应工具说明见:[VS Code 配置工具](./config-tool.md)
|
||||
|
||||
## 推荐目录结构
|
||||
|
||||
```text
|
||||
@ -978,6 +980,8 @@ var hotReload = loader.EnableHotReload(
|
||||
|
||||
## VS Code 工具
|
||||
|
||||
完整采用说明见:[VS Code 配置工具](./config-tool.md)。
|
||||
|
||||
仓库中的 `tools/gframework-config-tool` 当前提供以下能力:
|
||||
|
||||
- 浏览 `config/` 目录
|
||||
|
||||
138
docs/zh-CN/game/config-tool.md
Normal file
138
docs/zh-CN/game/config-tool.md
Normal file
@ -0,0 +1,138 @@
|
||||
---
|
||||
title: VS Code 配置工具
|
||||
description: 说明 GFramework AI-First 配置工作流对应的 VS Code 工具入口、工作区约定、常用命令与使用边界。
|
||||
---
|
||||
|
||||
# VS Code 配置工具
|
||||
|
||||
`GFramework Config Tool` 是面向 AI-First 配置工作流的 VS Code 扩展。它不是新的运行时模块,而是把
|
||||
`config/`、`schemas/`、轻量校验、表单预览和批量编辑收敛到编辑器侧的一条辅助工作流。
|
||||
|
||||
如果你正在维护 `GFramework.Game` 的 YAML + JSON Schema 配置,这个工具通常比纯手写 YAML 更适合做日常浏览、
|
||||
校验和批量修改;如果你要做复杂嵌套结构或超出当前支持子集的 schema 设计,仍然应该回到原始 YAML 和 schema 文件。
|
||||
|
||||
## 适合什么时候用
|
||||
|
||||
- 你已经采用 `config/**/*.yaml` + `schemas/**/*.schema.json`
|
||||
- 你希望在 VS Code 里快速浏览配置域和对应 schema
|
||||
- 你需要批量修改同一配置域的顶层标量或标量数组字段
|
||||
- 你想先走表单预览,再决定是否回到 raw YAML
|
||||
|
||||
不适合:
|
||||
|
||||
- 项目不使用 `GFramework.Game` 的配置工作流
|
||||
- 需要完整 JSON Schema 编辑器,而不是当前仓库落地的稳定子集
|
||||
- 需要在编辑器里处理更深层对象数组嵌套,且不接受回退到 raw YAML
|
||||
|
||||
## 工作区约定
|
||||
|
||||
默认目录约定是:
|
||||
|
||||
```text
|
||||
GameProject/
|
||||
├─ config/
|
||||
│ ├─ monster/
|
||||
│ │ ├─ slime.yaml
|
||||
│ │ └─ goblin.yaml
|
||||
│ └─ item/
|
||||
│ └─ potion.yaml
|
||||
└─ schemas/
|
||||
├─ monster.schema.json
|
||||
└─ item.schema.json
|
||||
```
|
||||
|
||||
扩展默认会把:
|
||||
|
||||
- `config/` 视为配置根目录
|
||||
- `schemas/` 视为 schema 根目录
|
||||
|
||||
如果你的项目用了不同目录,可以通过工作区设置覆盖。
|
||||
|
||||
## 扩展当前提供什么
|
||||
|
||||
### Explorer 视图
|
||||
|
||||
扩展会在 VS Code Explorer 中提供一个独立视图,用来浏览配置域和配置文件。
|
||||
|
||||
### 常用命令
|
||||
|
||||
当前命令面向这几类操作:
|
||||
|
||||
- 刷新配置树
|
||||
- 打开 raw YAML
|
||||
- 打开对应 schema
|
||||
- 打开轻量表单预览
|
||||
- 对单个配置域做批量编辑
|
||||
- 运行全量校验
|
||||
|
||||
如果你更关心“当前 schema 和 YAML 是否仍一致”,优先使用全量校验;如果你只是定位单个字段或注释,优先使用
|
||||
Explorer + 表单预览。
|
||||
|
||||
## 推荐工作流
|
||||
|
||||
### 1. 浏览配置与 schema
|
||||
|
||||
先从 Explorer 里进入目标配置文件,再根据需要:
|
||||
|
||||
- 打开 raw YAML
|
||||
- 跳转到对应 schema
|
||||
- 进入轻量表单预览
|
||||
|
||||
### 2. 先校验,再批量改
|
||||
|
||||
如果你准备改同一配置域下多份文件,推荐顺序是:
|
||||
|
||||
1. 先运行全量校验
|
||||
2. 再进入配置域批量编辑
|
||||
3. 批量修改完成后回到 raw YAML 或表单确认结果
|
||||
|
||||
### 3. 嵌套结构优先分层处理
|
||||
|
||||
当前工具支持:
|
||||
|
||||
- 顶层标量字段
|
||||
- 顶层标量数组
|
||||
- 嵌套对象字段
|
||||
- 对象数组
|
||||
|
||||
如果你进入更深层对象数组嵌套,当前更稳妥的做法通常是:
|
||||
|
||||
1. 用 Explorer 找到目标文件
|
||||
2. 先看表单预览确认字段结构
|
||||
3. 再回到 raw YAML 完成最终编辑
|
||||
|
||||
## 工作区设置
|
||||
|
||||
当前公开设置只有两个:
|
||||
|
||||
```json
|
||||
{
|
||||
"gframeworkConfig.configPath": "config",
|
||||
"gframeworkConfig.schemasPath": "schemas"
|
||||
}
|
||||
```
|
||||
|
||||
- `gframeworkConfig.configPath`
|
||||
- 配置根目录,默认是 `config`
|
||||
- `gframeworkConfig.schemasPath`
|
||||
- schema 根目录,默认是 `schemas`
|
||||
|
||||
## 当前边界
|
||||
|
||||
当前扩展重点覆盖的是仓库已经验证过的最小工作流:
|
||||
|
||||
- 工作区默认只取第一个 workspace folder
|
||||
- 校验聚焦仓库当前支持的 schema 子集
|
||||
- 表单预览支持对象数组,但更深的嵌套对象数组仍可能需要回退到 raw YAML
|
||||
- 批量编辑当前聚焦顶层标量和顶层标量数组字段
|
||||
|
||||
因此,最稳妥的理解方式是:
|
||||
|
||||
- 用它加速“浏览、定位、轻量校验、批量维护”
|
||||
- 不把它当成完整替代 YAML / schema 编辑的唯一入口
|
||||
|
||||
## 继续阅读
|
||||
|
||||
- [游戏内容配置系统](./config-system.md)
|
||||
- [Game 模块](./index.md)
|
||||
- [安装配置](../getting-started/installation.md)
|
||||
@ -31,6 +31,7 @@ description: GFramework.Game family 的运行时入口、采用顺序与源码
|
||||
|
||||
- 配置与内容系统
|
||||
- [配置系统](./config-system.md)
|
||||
- [VS Code 配置工具](./config-tool.md)
|
||||
- 数据、设置、序列化与存储
|
||||
- [数据系统](./data.md)
|
||||
- [设置系统](./setting.md)
|
||||
@ -99,9 +100,10 @@ IStorage storage = new FileStorage("GameData", serializer);
|
||||
|
||||
1. [Core 入口](../core/index.md)
|
||||
2. [配置系统](./config-system.md)
|
||||
3. [数据系统](./data.md)
|
||||
4. [设置系统](./setting.md)
|
||||
5. [场景系统](./scene.md)或[UI 系统](./ui.md)
|
||||
3. [VS Code 配置工具](./config-tool.md)
|
||||
4. [数据系统](./data.md)
|
||||
5. [设置系统](./setting.md)
|
||||
6. [场景系统](./scene.md)或[UI 系统](./ui.md)
|
||||
|
||||
## 源码与 API 阅读入口
|
||||
|
||||
|
||||
@ -26,6 +26,13 @@ GFramework 采用模块化设计,不同包提供不同的功能:
|
||||
|
||||
当前 NuGet 发布按模块拆分 source generator 包,不存在 `GeWuYou.GFramework.SourceGenerators` 聚合包。
|
||||
|
||||
`GeWuYou.GFramework` 当前是聚合元包,只聚合:
|
||||
|
||||
- `GFramework.Core`
|
||||
- `GFramework.Game`
|
||||
|
||||
它不会自动带上 `Cqrs`、`Godot` 或任何 `*.SourceGenerators` 包。如果你需要这些能力,请按模块单独安装。
|
||||
|
||||
## 安装方式
|
||||
|
||||
### 1. 使用 .NET CLI(推荐)
|
||||
@ -66,7 +73,7 @@ dotnet add package GeWuYou.GFramework.Cqrs.SourceGenerators
|
||||
```xml
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -110,14 +117,14 @@ dotnet add package GeWuYou.GFramework.Cqrs.SourceGenerators
|
||||
|
||||
### 运行时要求
|
||||
|
||||
- **.NET 6.0** 或更高版本
|
||||
- **Godot 4.5+**(仅 Godot 项目)
|
||||
- **.NET 8.0、9.0 或 10.0**
|
||||
- **Godot 4.6.2**(仅 Godot 项目)
|
||||
|
||||
### 开发工具
|
||||
|
||||
- Visual Studio 2022 或 VS Code
|
||||
- .NET 6.0 SDK
|
||||
- Godot 4.5+(可选,仅 Godot 项目需要)
|
||||
- .NET 8 SDK 或更高版本
|
||||
- Godot 4.6.2(可选,仅 Godot 项目需要)
|
||||
|
||||
## 项目配置
|
||||
|
||||
|
||||
@ -2,11 +2,11 @@
|
||||
# https://vitepress.dev/reference/default-theme-home-page
|
||||
layout: home
|
||||
title: GFramework
|
||||
description: 概览 GFramework 的模块能力、入门路径与主要中文文档入口。
|
||||
description: 概览 GFramework 的模块能力、安装选包路径,以及 Core、CQRS、Game、Godot 与配置工具入口。
|
||||
hero:
|
||||
name: "GFramework"
|
||||
text: 面向游戏开发的模块化 C# 架构体系
|
||||
tagline: 基于清洁架构与 CQRS 思想构建,支持可扩展设计与多引擎集成
|
||||
tagline: 基于清洁架构与 CQRS 思想构建,覆盖运行时、源码生成器、Godot 集成与 AI-First 配置工作流
|
||||
image:
|
||||
src: /logo.png
|
||||
alt: GFramework Logo
|
||||
@ -15,25 +15,31 @@ hero:
|
||||
text: 快速开始
|
||||
link: /zh-CN/getting-started/quick-start
|
||||
- theme: alt
|
||||
text: 架构概览
|
||||
link: /zh-CN/getting-started
|
||||
text: 安装与选包
|
||||
link: /zh-CN/getting-started/installation
|
||||
- theme: alt
|
||||
text: CQRS
|
||||
link: /zh-CN/core/cqrs
|
||||
- theme: alt
|
||||
text: 配置工具
|
||||
link: /zh-CN/game/config-tool
|
||||
|
||||
features:
|
||||
- title: 🏗 清洁架构分层
|
||||
details: 基于 Model–Controller–System–Utility 五层结构设计,实现职责清晰、可测试、可维护的代码组织方式。
|
||||
details: 基于 Model–Controller–System–Utility 五层结构组织运行时能力,适合先从 Core 起步,再逐层叠加 Game、CQRS 和引擎集成。
|
||||
|
||||
- title: 🔧 CQRS 命令查询分离
|
||||
details: 通过类型安全的命令与查询系统构建业务流程,支持可扩展操作链与可撤销机制。
|
||||
- title: 🔧 CQRS 请求模型
|
||||
details: 提供 request、notification、pipeline、handler registry 与 source generator 协作路径,适合把新业务统一收敛到 CQRS runtime。
|
||||
|
||||
- title: 📡 类型安全事件系统
|
||||
details: 提供高性能事件总线,实现模块间松耦合通信与可扩展的业务触发机制。
|
||||
- title: 🧭 模块化选包路径
|
||||
details: 支持按运行时、抽象层、源码生成器和引擎集成拆分安装,而不是先引入一个难以裁剪的大而全包。
|
||||
|
||||
- title: 🎮 引擎集成层
|
||||
details: 核心层与引擎层解耦设计,当前提供 Godot 集成实现,支持节点扩展、协程桥接与对象池能力。
|
||||
- title: 🎮 Godot 集成
|
||||
details: 在保持 Core / Game 运行时边界的前提下,补齐节点扩展、场景与 UI 接线、协程桥接和生成器辅助。
|
||||
|
||||
- title: 🔄 响应式属性系统
|
||||
details: 可绑定属性模型驱动 UI 更新与状态变化,构建声明式的数据响应流程。
|
||||
- title: 🧩 AI-First 配置工作流
|
||||
details: 通过 YAML + JSON Schema + Source Generator + VS Code 工具,把静态内容配置、校验、表单预览和批量编辑串成一条链路。
|
||||
|
||||
- title: ⚡ Roslyn 源码生成器
|
||||
details: 自动生成日志、枚举扩展与规则代码,减少样板代码并提升开发效率。
|
||||
details: 自动生成日志、上下文注入、配置类型、CQRS registry 和 Godot 辅助代码,并复用共享 diagnostics 约束生成行为。
|
||||
---
|
||||
|
||||
@ -34,13 +34,13 @@ Context Get 注入依赖 `[ContextAware]` 特性提供的上下文访问能力
|
||||
| 特性 | 应用目标 | 功能描述 | 类型约束 |
|
||||
|------------------|------|------------------|------------------------------------|
|
||||
| `[GetModel]` | 字段 | 注入单个 Model 实例 | 必须实现 IModel |
|
||||
| `[GetModels]` | 字段 | 注入 Model 集合 | IReadOnlyList\<IModel\> |
|
||||
| `[GetModels]` | 字段 | 注入 Model 集合 | 可赋值自 IReadOnlyList\<IModel\> 的类型 |
|
||||
| `[GetSystem]` | 字段 | 注入单个 System 实例 | 必须实现 ISystem |
|
||||
| `[GetSystems]` | 字段 | 注入 System 集合 | IReadOnlyList\<ISystem\> |
|
||||
| `[GetSystems]` | 字段 | 注入 System 集合 | 可赋值自 IReadOnlyList\<ISystem\> 的类型 |
|
||||
| `[GetUtility]` | 字段 | 注入单个 Utility 实例 | 必须实现 IUtility |
|
||||
| `[GetUtilities]` | 字段 | 注入 Utility 集合 | IReadOnlyList\<IUtility\> |
|
||||
| `[GetUtilities]` | 字段 | 注入 Utility 集合 | 可赋值自 IReadOnlyList\<IUtility\> 的类型 |
|
||||
| `[GetService]` | 字段 | 注入单个服务实例 | 必须是引用类型 |
|
||||
| `[GetServices]` | 字段 | 注入服务集合 | IReadOnlyList\<T\> where T : class |
|
||||
| `[GetServices]` | 字段 | 注入服务集合 | 可赋值自 IReadOnlyList\<T\> 且 `T : class` 的类型 |
|
||||
| `[GetAll]` | 类 | 自动推断并注入所有符合类型的字段 | 智能推断 |
|
||||
|
||||
## 基础使用
|
||||
@ -248,9 +248,9 @@ public void TestController()
|
||||
| IModel 及其子类型 | 单实例注入 | `this.GetModel<T>()` |
|
||||
| ISystem 及其子类型 | 单实例注入 | `this.GetSystem<T>()` |
|
||||
| IUtility 及其子类型 | 单实例注入 | `this.GetUtility<T>()` |
|
||||
| IReadOnlyList\<IModel\> | 集合注入 | `this.GetModels<T>()` |
|
||||
| IReadOnlyList\<ISystem\> | 集合注入 | `this.GetSystems<T>()` |
|
||||
| IReadOnlyList\<IUtility\> | 集合注入 | `this.GetUtilities<T>()` |
|
||||
| 可赋值自 IReadOnlyList\<IModel\> 的字段类型 | 集合注入 | `this.GetModels<T>()` |
|
||||
| 可赋值自 IReadOnlyList\<ISystem\> 的字段类型 | 集合注入 | `this.GetSystems<T>()` |
|
||||
| 可赋值自 IReadOnlyList\<IUtility\> 的字段类型 | 集合注入 | `this.GetUtilities<T>()` |
|
||||
|
||||
### 不自动推断的类型
|
||||
|
||||
@ -735,13 +735,19 @@ public partial class Controller
|
||||
**A**: 不会。`[GetAll]` 只注入符合类型约束的字段:
|
||||
|
||||
- 实现 IModel、ISystem、IUtility 接口的类型
|
||||
- 上述类型的 `IReadOnlyList<T>` 集合
|
||||
- 上述类型的、可赋值自 `IReadOnlyList<T>` 的集合字段
|
||||
- 排除 Godot.Node 类型
|
||||
- 排除 Service 类型
|
||||
|
||||
### Q: 集合注入可以是 List\<T\> 吗?
|
||||
|
||||
**A**: 不可以。集合注入只支持 `IReadOnlyList<T>` 类型,这是为了保证注入集合的不可变性。
|
||||
**A**: 不能直接写成普通 `List<T>`。当前要求字段类型至少要能接受 `IReadOnlyList<T>` 赋值,例如:
|
||||
|
||||
- `IReadOnlyList<T>`
|
||||
- 更宽的只读集合接口或基类,只要生成结果能直接赋给字段
|
||||
|
||||
生成器的约束重点是“返回值必须能以 `IReadOnlyList<T>` 形式安全赋给字段”,而不是要求你必须逐字写成
|
||||
`IReadOnlyList<T>`。
|
||||
|
||||
```csharp
|
||||
[ContextAware]
|
||||
@ -751,7 +757,7 @@ public partial class Controller
|
||||
[GetModels]
|
||||
private IReadOnlyList<IPlayerModel> _players = null!;
|
||||
|
||||
// ❌ 错误:不支持
|
||||
// ❌ 错误:不能接受 IReadOnlyList<T> 赋值
|
||||
[GetModels]
|
||||
private List<IPlayerModel> _players = null!;
|
||||
}
|
||||
|
||||
@ -102,31 +102,6 @@ public enum GameState
|
||||
| GenerateIsMethods | bool | true | 是否为每个枚举值生成 IsX 方法 |
|
||||
| GenerateIsInMethod | bool | true | 是否生成 IsIn 方法 |
|
||||
|
||||
#### 未实现的选项
|
||||
|
||||
以下选项在属性定义中存在,但生成器当前版本**未实现**:
|
||||
|
||||
- `GenerateHasMethod`:未实现 HasX 方法生成
|
||||
- `IncludeToString`:未实现 ToString 扩展方法
|
||||
|
||||
```csharp
|
||||
// ❌ 以下选项不会生效
|
||||
[GenerateEnumExtensions(
|
||||
GenerateIsMethods = true,
|
||||
GenerateIsInMethod = true,
|
||||
GenerateHasMethod = true, // 未实现,参数会被忽略
|
||||
IncludeToString = true // 未实现,参数会被忽略
|
||||
)]
|
||||
public enum GameState
|
||||
{
|
||||
Normal,
|
||||
Paused,
|
||||
GameOver
|
||||
}
|
||||
```
|
||||
|
||||
**注意**:这些选项计划在后续版本中实现,届时会更新文档说明。
|
||||
|
||||
### 只生成 IsX 方法
|
||||
|
||||
```csharp
|
||||
@ -175,7 +150,16 @@ public static bool IsNormal(this GameState value)
|
||||
if (state == GameState.Normal) { }
|
||||
```
|
||||
|
||||
### 3. 可读性提升
|
||||
### 3. 什么时候值得关闭某个开关
|
||||
|
||||
- 只想保留精确单值判断:
|
||||
- 保留 `GenerateIsMethods = true`
|
||||
- 关闭 `GenerateIsInMethod = false`
|
||||
- 只想保留多值判断入口:
|
||||
- 保留 `GenerateIsInMethod = true`
|
||||
- 关闭 `GenerateIsMethods = false`
|
||||
|
||||
### 4. 可读性提升
|
||||
|
||||
```csharp
|
||||
// 使用生成的方法(推荐)
|
||||
|
||||
@ -83,7 +83,26 @@ private Label _scoreLabel = null!;
|
||||
对文档来说,最关键的结论是:
|
||||
|
||||
- `Auto` 在未给路径时默认走唯一名推断
|
||||
- 显式路径会结合 `Lookup` 决定最终生成的字符串
|
||||
- 一旦显式给了 `Path`,生成结果直接使用这个字符串,`Lookup` 不再改写它
|
||||
|
||||
可以直接按下面这张表理解当前行为:
|
||||
|
||||
| `Path` | `Lookup` | 生成路径 |
|
||||
| --- | --- | --- |
|
||||
| 未设置 | `Auto` | `%FieldName` |
|
||||
| 未设置 | `UniqueName` | `%FieldName` |
|
||||
| 未设置 | `RelativePath` | `FieldName` |
|
||||
| 未设置 | `AbsolutePath` | `/FieldName` |
|
||||
| 已显式设置 | 任意值 | 原样使用显式路径 |
|
||||
|
||||
例如:
|
||||
|
||||
```csharp
|
||||
[GetNode("HUD/ScoreLabel", Lookup = NodeLookupMode.AbsolutePath)]
|
||||
private Label _scoreLabel = null!;
|
||||
```
|
||||
|
||||
当前生成结果仍然会直接使用 `"HUD/ScoreLabel"`,不会因为 `Lookup = AbsolutePath` 被改写成 `"/HUD/ScoreLabel"`。
|
||||
|
||||
### `Required`
|
||||
|
||||
@ -189,6 +208,7 @@ public override void _Ready()
|
||||
- 只有缺少 `_Ready()` 时才会自动补 override
|
||||
- `OnGetNodeReadyGenerated()` 只存在于自动补 `_Ready()` 的路径
|
||||
- `Required = false` 会真实切换到 `GetNodeOrNull<T>()`
|
||||
- `Lookup` 只影响“未显式给路径时”的推断前缀;显式 `Path` 不会被二次改写
|
||||
|
||||
## 推荐阅读
|
||||
|
||||
|
||||
@ -20,6 +20,23 @@ GFramework 当前发布的生成器包是:
|
||||
|
||||
不存在 `GeWuYou.GFramework.SourceGenerators` 或 `*.SourceGenerators.Attributes` 这类聚合包。
|
||||
|
||||
## 共享支撑模块
|
||||
|
||||
除了上面的可直接安装包,仓库里还有三类跟随这些生成器共同演化的支撑目录:
|
||||
|
||||
- `GFramework.SourceGenerators.Common`
|
||||
- 承载跨生成器共享的基类、通用 diagnostics 和生成冲突规则。
|
||||
- `GFramework.Core.SourceGenerators.Abstractions`
|
||||
- 承载 `Core` 侧生成器特性定义,例如 `[Log]`、`[ContextAware]`、`[GetModel]`、`[GenerateEnumExtensions]`。
|
||||
- `GFramework.Godot.SourceGenerators.Abstractions`
|
||||
- 承载 Godot 侧生成器特性定义,例如 `[GetNode]`、`[BindNodeSignal]`、`[AutoScene]`、`[AutoUiPage]`。
|
||||
|
||||
这些目录当前不是新的安装入口。对读者更重要的是先判断:
|
||||
|
||||
- 应该安装哪个 `*.SourceGenerators` 包
|
||||
- 当前看到的 attribute 和 diagnostics 属于哪条生成链
|
||||
- 继续阅读时应该回到哪个运行时或专题页
|
||||
|
||||
## 先按场景选包
|
||||
|
||||
- 想减少日志、上下文注入、模块自动注册等 Core 侧样板代码:
|
||||
@ -55,6 +72,12 @@ GFramework 当前发布的生成器包是:
|
||||
|
||||
其他生成器包的安装模式相同。
|
||||
|
||||
`GFramework.SourceGenerators.Common` 和两个 `*.SourceGenerators.Abstractions` 目录当前都跟随对应生成器模块维护:
|
||||
|
||||
- 不是额外安装的独立包选择题
|
||||
- 主要用于承载 attribute、共享基类和跨生成器共用 diagnostics
|
||||
- 读者只需要在排查 attribute 语义、冲突规则或生成失败原因时回到这些源码目录确认契约
|
||||
|
||||
## 这个栏目怎么读
|
||||
|
||||
### Core 侧通用生成器
|
||||
|
||||
@ -2,26 +2,66 @@
|
||||
|
||||
VS Code extension for the GFramework AI-First config workflow.
|
||||
|
||||
## Current MVP
|
||||
## Purpose
|
||||
|
||||
This extension is the editor-side companion for the `GFramework.Game` config pipeline:
|
||||
|
||||
- `config/**/*.yaml`
|
||||
- `schemas/**/*.schema.json`
|
||||
- source-generated config types and tables
|
||||
|
||||
It is intended to speed up browsing, validation, lightweight form editing, and domain-level maintenance inside VS Code.
|
||||
It is not a replacement for the runtime or generator packages, and it does not attempt to be a full JSON Schema editor.
|
||||
|
||||
## Recommended Workspace Layout
|
||||
|
||||
By default, the extension expects:
|
||||
|
||||
```text
|
||||
GameProject/
|
||||
├─ config/
|
||||
│ ├─ monster/
|
||||
│ │ ├─ slime.yaml
|
||||
│ │ └─ goblin.yaml
|
||||
│ └─ item/
|
||||
│ └─ potion.yaml
|
||||
└─ schemas/
|
||||
├─ monster.schema.json
|
||||
└─ item.schema.json
|
||||
```
|
||||
|
||||
## What It Adds
|
||||
|
||||
### Explorer View
|
||||
|
||||
- Browse config files from the workspace `config/` directory
|
||||
- Open raw YAML files
|
||||
- Group files by config domain
|
||||
- Open matching schema files from `schemas/`
|
||||
|
||||
### File-Level Actions
|
||||
|
||||
- Open raw YAML
|
||||
- Open the matching schema
|
||||
- Open a lightweight form preview
|
||||
|
||||
### Domain-Level Actions
|
||||
|
||||
- Batch edit one config domain across multiple files for top-level scalar and scalar-array fields
|
||||
- Run validation across the current workspace config surface
|
||||
|
||||
### Form / Validation Support
|
||||
|
||||
- Localize extension UI text in English and Simplified Chinese, including the form preview, prompts, and notifications
|
||||
- Run lightweight schema validation for nested required fields, unknown nested fields, scalar types, scalar arrays, and
|
||||
arrays of objects
|
||||
- Open a lightweight form preview for nested object fields, object arrays, top-level scalar fields, and scalar arrays
|
||||
- Render existing YAML comments in the form preview and edit per-field YAML comments directly from the form
|
||||
- Jump from reference fields to the referenced schema, config domain, or direct config file when a reference value is
|
||||
present
|
||||
- Initialize empty config files from schema-derived example YAML
|
||||
- Batch edit one config domain across multiple files for top-level scalar and scalar-array fields
|
||||
- Surface schema metadata such as `title`, `description`, `default`, `enum`, and `x-gframework-ref-table` in the
|
||||
lightweight editors
|
||||
|
||||
## Validation Coverage
|
||||
|
||||
The extension currently validates the repository's minimal config-schema subset:
|
||||
The extension currently validates the repository's current schema subset:
|
||||
|
||||
- required properties in nested objects
|
||||
- unknown properties in nested objects
|
||||
@ -30,6 +70,37 @@ The extension currently validates the repository's minimal config-schema subset:
|
||||
- arrays of objects whose items use the same supported subset recursively
|
||||
- scalar `enum` constraints and scalar-array item `enum` constraints
|
||||
|
||||
## Workspace Settings
|
||||
|
||||
```json
|
||||
{
|
||||
"gframeworkConfig.configPath": "config",
|
||||
"gframeworkConfig.schemasPath": "schemas"
|
||||
}
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
1. Install the extension in VS Code and open the workspace that contains your `config/` and `schemas/` directories.
|
||||
2. Keep the default workspace layout, or set `gframeworkConfig.configPath` and `gframeworkConfig.schemasPath` to your
|
||||
project-specific paths.
|
||||
3. Open the `GFramework Config` explorer view and select a config file or domain.
|
||||
4. Run validation first to confirm the current YAML files still match the supported schema subset.
|
||||
5. Open the lightweight form preview or domain batch editing actions, then fall back to raw YAML for deeper nested edits
|
||||
when needed.
|
||||
|
||||
## Documentation
|
||||
|
||||
- Chinese adoption guide: [`docs/zh-CN/game/config-tool.md`](../../docs/zh-CN/game/config-tool.md)
|
||||
- Related config runtime guide: [`docs/zh-CN/game/config-system.md`](../../docs/zh-CN/game/config-system.md)
|
||||
|
||||
## Current Constraints
|
||||
|
||||
- Multi-root workspaces use the first workspace folder
|
||||
- Validation only covers the repository's current schema subset
|
||||
- Form preview supports object-array editing, but nested object arrays inside array items still fall back to raw YAML
|
||||
- Batch editing remains limited to top-level scalar fields and top-level scalar arrays
|
||||
|
||||
## Local Testing
|
||||
|
||||
```bash
|
||||
@ -46,15 +117,3 @@ bun install
|
||||
bun run package:vsix
|
||||
VSCE_PAT=your_marketplace_pat bun run publish:marketplace
|
||||
```
|
||||
|
||||
## Current Constraints
|
||||
|
||||
- Multi-root workspaces use the first workspace folder
|
||||
- Validation only covers a minimal subset of JSON Schema
|
||||
- Form preview supports object-array editing, but nested object arrays inside array items still fall back to raw YAML
|
||||
- Batch editing remains limited to top-level scalar fields and top-level scalar arrays
|
||||
|
||||
## Workspace Settings
|
||||
|
||||
- `gframeworkConfig.configPath`
|
||||
- `gframeworkConfig.schemasPath`
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user