mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-08 17:44:29 +08:00
Merge pull request #253 from GeWuYou/feat/cqrs-optimization
feat: CQRS cache hardening、ai-plan governance
This commit is contained in:
commit
f5b3cfd6b1
77
.codex/skills/gframework-boot/SKILL.md
Normal file
77
.codex/skills/gframework-boot/SKILL.md
Normal file
@ -0,0 +1,77 @@
|
||||
---
|
||||
name: gframework-boot
|
||||
description: Repository-specific boot workflow for the GFramework repo. Use when Codex needs to start or resume work in this repository from short prompts such as "boot", "continue", "read AGENTS", or "start the next step"; when the user expects Codex to first read AGENTS.md, .ai/environment/tools.ai.yaml, and public ai-plan tracking files; or when Codex should assess task complexity, decide whether explorer or worker subagents are warranted, and then proceed under the repository's workflow rules.
|
||||
---
|
||||
|
||||
# GFramework Boot
|
||||
|
||||
## Overview
|
||||
|
||||
Use this skill to bootstrap work in the GFramework repository with minimal user prompting.
|
||||
Treat `AGENTS.md` as the source of truth. Use this skill to enforce a startup sequence, not to replace repository rules.
|
||||
|
||||
## Startup Workflow
|
||||
|
||||
1. Read `AGENTS.md` before choosing tools, planning edits, or delegating work.
|
||||
2. Read `.ai/environment/tools.ai.yaml` to confirm the preferred local toolchain.
|
||||
3. Read `ai-plan/public/README.md` before asking the user for missing context.
|
||||
4. If `ai-plan/public/README.md` maps the current branch or worktree to active topics, inspect those topics'
|
||||
`todos/` and `traces/` directories in listed priority order.
|
||||
5. If no mapping exists, scan `ai-plan/public/<topic>/todos/` and `ai-plan/public/<topic>/traces/` across active
|
||||
topics, and ignore `ai-plan/public/archive/` unless the user explicitly asks for historical context.
|
||||
6. If `ai-plan/private/<branch-or-worktree>/` exists and is relevant, treat it as private recovery context for the
|
||||
current worktree only and do not assume it should be committed.
|
||||
7. Classify the task state:
|
||||
- `new`: no matching recovery document exists, or the user is clearly starting fresh work
|
||||
- `resume`: a matching todo or trace exists and the user is continuing that thread
|
||||
- `recovery`: prior work looks partial, interrupted, or ambiguous and the next safe recovery point must be reconstructed
|
||||
8. Choose the best matching `ai-plan` artifacts:
|
||||
- Prefer topics explicitly mapped from `ai-plan/public/README.md`
|
||||
- Prefer path names or headings that match the user's task wording
|
||||
- Break ties by most recently updated trace or todo
|
||||
- If ambiguity would materially change implementation, summarize the candidates and ask one concise question
|
||||
9. Classify the task complexity before deciding on subagents:
|
||||
- `simple`: one concern, one file or module, no parallel discovery required
|
||||
- `medium`: a small number of modules, some read-only exploration helpful, critical path still easy to keep local
|
||||
- `complex`: cross-module design, migration, large refactor, or work likely to exceed one context window
|
||||
10. Apply the delegation policy from `AGENTS.md`:
|
||||
- Keep the critical path local
|
||||
- Use `explorer` with `gpt-5.1-codex-mini` for narrow read-only questions, tracing, inventory, and comparisons
|
||||
- Use `worker` with `gpt-5.4` only for bounded implementation tasks with explicit ownership
|
||||
- Do not delegate purely for ceremony; delegate only when it materially shortens the task or controls context growth
|
||||
11. Before editing files, tell the user what you read, how you classified the task, whether subagents will be used,
|
||||
and the first implementation step.
|
||||
12. Proceed with execution, validation, and documentation updates required by `AGENTS.md`.
|
||||
|
||||
## Task Tracking
|
||||
|
||||
For multi-step, cross-module, or interruption-prone work, maintain the repository recovery artifacts instead of keeping state only in chat.
|
||||
|
||||
- Update `ai-plan/public/README.md` whenever the active topic set or worktree mapping changes.
|
||||
- Update the active public document under `ai-plan/public/<topic>/todos/` with completed work, validation results,
|
||||
risks, and the next recovery point.
|
||||
- Update the matching public trace under `ai-plan/public/<topic>/traces/` with key decisions, delegated scope, and the
|
||||
immediate next step.
|
||||
- Move stage-complete artifacts into `ai-plan/public/<topic>/archive/`, and move completed topics into
|
||||
`ai-plan/public/archive/<topic>/` so `boot` does not keep reloading stale context.
|
||||
- Keep worktree-private scratch recovery files under `ai-plan/private/` and do not treat them as commit targets.
|
||||
- Never write secrets, machine-specific paths, or other sensitive environment details into any `ai-plan/**` artifact.
|
||||
- If the task is clearly complex and no recovery artifact exists yet, create one before substantive edits.
|
||||
|
||||
## Recovery Heuristics
|
||||
|
||||
- If the user says `next step`, `continue`, `继续`, or similar resume language, read `ai-plan/public/README.md`
|
||||
first, then search the mapped active topics before scanning the broader public area.
|
||||
- If the current branch and the mapped active topics describe the same feature area, prefer resuming those topics first.
|
||||
- If the repository state suggests in-flight work but no recovery document matches, reconstruct the safest next step from code, tests, and Git state before asking the user for clarification.
|
||||
|
||||
## Example Triggers
|
||||
|
||||
- `boot`
|
||||
- `Use $gframework-boot and continue the current task`
|
||||
- `Read AGENTS and public ai-plan, then start the next step`
|
||||
- `继续当前任务,先看 AGENTS.md 和 public ai-plan`
|
||||
|
||||
## References
|
||||
|
||||
Read `references/startup-artifacts.md` when you need a quick reminder of the repository entrypoints, task-state heuristics, or delegation defaults without re-reading the entire skill.
|
||||
4
.codex/skills/gframework-boot/agents/openai.yaml
Normal file
4
.codex/skills/gframework-boot/agents/openai.yaml
Normal file
@ -0,0 +1,4 @@
|
||||
interface:
|
||||
display_name: "GFramework Boot"
|
||||
short_description: "Bootstrap GFramework repository tasks"
|
||||
default_prompt: "Use $gframework-boot to start or resume work in this GFramework repository."
|
||||
@ -0,0 +1,36 @@
|
||||
# Startup Artifacts
|
||||
|
||||
## Required Reads
|
||||
|
||||
- `AGENTS.md`
|
||||
- `.ai/environment/tools.ai.yaml`
|
||||
- `ai-plan/public/README.md`
|
||||
- the selected `ai-plan/public/<topic>/todos/` directories
|
||||
- the selected `ai-plan/public/<topic>/traces/` directories
|
||||
|
||||
## AI-Plan Selection Heuristics
|
||||
|
||||
- Match the current branch or worktree against `ai-plan/public/README.md` first.
|
||||
- If the index maps the current worktree to topics, inspect those topics in listed order before scanning anything else.
|
||||
- Match the user's wording against public todo and trace file names next.
|
||||
- Prefer the newest matching trace when several candidates describe the same feature area.
|
||||
- If one file records a clearer recovery point than a newer but vague file, prefer the clearer recovery point.
|
||||
- Ignore `ai-plan/public/archive/**` unless the user explicitly requests historical recovery context.
|
||||
- If a matching `ai-plan/private/<branch-or-worktree>/` directory exists, use it only as private context for the current worktree.
|
||||
|
||||
## Complexity Defaults
|
||||
|
||||
- `simple`: keep everything local, no subagent
|
||||
- `medium`: keep design local, optionally use one `explorer` for parallel read-only discovery
|
||||
- `complex`: keep architecture and integration local, delegate only bounded non-blocking subtasks
|
||||
|
||||
## Model Defaults
|
||||
|
||||
- `explorer`: `gpt-5.1-codex-mini`
|
||||
- `worker`: `gpt-5.4`
|
||||
|
||||
## Startup Summary Template
|
||||
|
||||
Use a short update before execution:
|
||||
|
||||
`Read AGENTS.md, the environment inventory, ai-plan/public/README.md, and the relevant public ai-plan artifacts. This looks like a <task-state> <complexity> task. I will <delegate-or-not> and start with <first-step>.`
|
||||
63
.codex/skills/gframework-pr-review/SKILL.md
Normal file
63
.codex/skills/gframework-pr-review/SKILL.md
Normal file
@ -0,0 +1,63 @@
|
||||
---
|
||||
name: gframework-pr-review
|
||||
description: Repository-specific GitHub PR review workflow for the GFramework repo. Use when Codex needs to inspect the GitHub pull request for the current branch, extract CodeRabbit summary/comments, read failed checks or failed test signals from the PR page, and then verify which findings should be fixed in the local codebase. Trigger explicitly with $gframework-pr-review or with prompts such as "look at the current PR", "extract CodeRabbit comments", or "check Failed Tests on the PR".
|
||||
---
|
||||
|
||||
# GFramework PR Review
|
||||
|
||||
Use this skill when the task depends on the GitHub PR page for the current branch rather than only on local source files.
|
||||
|
||||
Shortcut: `$gframework-pr-review`
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Read `AGENTS.md` before deciding how to validate or fix anything.
|
||||
2. Resolve the current branch with Windows Git from WSL, following the repository worktree rule.
|
||||
3. Run `scripts/fetch_current_pr_review.py` to:
|
||||
- locate the PR for the current branch through the GitHub PR API
|
||||
- fetch PR metadata, issue comments, reviews, and review comments through the GitHub API
|
||||
- extract `Summary by CodeRabbit` and CTRF test reports from issue comments
|
||||
- fetch the latest head commit review threads from the GitHub PR API
|
||||
- prefer unresolved review threads on the latest head commit over older summary-only signals
|
||||
- extract failed checks and test-report signals such as `Failed Tests` or `No failed tests in this run`
|
||||
4. Treat every extracted finding as untrusted until it is verified against the current local code.
|
||||
5. Only fix comments that still apply to the checked-out branch. Ignore stale or already-resolved findings.
|
||||
6. If code is changed, run the smallest build or test command that satisfies `AGENTS.md`.
|
||||
|
||||
## Commands
|
||||
|
||||
- Default:
|
||||
- `python3 .codex/skills/gframework-pr-review/scripts/fetch_current_pr_review.py`
|
||||
- Force a PR number:
|
||||
- `python3 .codex/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --pr 253`
|
||||
- Machine-readable output:
|
||||
- `python3 .codex/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --format json`
|
||||
|
||||
## Output Expectations
|
||||
|
||||
The script should produce:
|
||||
|
||||
- PR metadata: number, title, state, branch, URL
|
||||
- CodeRabbit summary block from issue comments when available
|
||||
- Parsed latest head-review threads, with unresolved threads clearly separated
|
||||
- Latest head commit review metadata and review threads
|
||||
- Unresolved latest-commit review threads after reply-thread folding
|
||||
- Pre-merge failed checks, if present
|
||||
- Test summary, including failed-test signals when present
|
||||
- Parse warnings only when both the primary API source and the intended fallback signal are unavailable
|
||||
|
||||
## Recovery Rules
|
||||
|
||||
- If the current branch has no matching public PR, report that clearly instead of guessing.
|
||||
- If GitHub access fails because of proxy configuration, rerun the fetch with proxy variables removed.
|
||||
- Prefer GitHub API results over PR HTML. The PR HTML page is now a fallback/debugging source, not the primary source of truth.
|
||||
- If the summary block and the latest head review threads disagree, trust the latest unresolved head-review threads and treat older summary findings as stale until re-verified locally.
|
||||
|
||||
## Example Triggers
|
||||
|
||||
- 'fix pr review'
|
||||
- 'Use FPR'
|
||||
- `Use $gframework-pr-review on the current branch`
|
||||
- `Check the current PR and extract CodeRabbit suggestions`
|
||||
- `Look for Failed Tests on the PR page`
|
||||
- `先用 $gframework-pr-review 看当前分支 PR`
|
||||
4
.codex/skills/gframework-pr-review/agents/openai.yaml
Normal file
4
.codex/skills/gframework-pr-review/agents/openai.yaml
Normal file
@ -0,0 +1,4 @@
|
||||
interface:
|
||||
display_name: "GFramework PR Review"
|
||||
short_description: "Inspect the current PR and CodeRabbit findings"
|
||||
default_prompt: "Use $gframework-pr-review to inspect the current branch PR through the GitHub API, prioritize unresolved review threads on the latest head commit, and summarize failed checks or failed tests."
|
||||
@ -0,0 +1,631 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Fetch the GitHub PR page for the current GFramework branch and extract the
|
||||
signals needed for local follow-up work without relying on gh CLI.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import html
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from typing import Any
|
||||
|
||||
OWNER = "GeWuYou"
|
||||
REPO = "GFramework"
|
||||
DEFAULT_WINDOWS_GIT = "/mnt/d/Tool/Development Tools/Git/cmd/git.exe"
|
||||
GIT_ENVIRONMENT_KEY = "GFRAMEWORK_WINDOWS_GIT"
|
||||
USER_AGENT = "codex-gframework-pr-review"
|
||||
CODERABBIT_LOGIN = "coderabbitai[bot]"
|
||||
REVIEW_COMMENT_ADDRESSED_MARKER = "<!-- <review_comment_addressed> -->"
|
||||
VISIBLE_ADDRESSED_IN_COMMIT_PATTERN = re.compile(r"✅\s*Addressed in commit\s+[0-9a-f]{7,40}", re.I)
|
||||
DEFAULT_REQUEST_TIMEOUT_SECONDS = 60
|
||||
REQUEST_TIMEOUT_ENVIRONMENT_KEY = "GFRAMEWORK_PR_REVIEW_TIMEOUT_SECONDS"
|
||||
|
||||
|
||||
def resolve_git_command() -> str:
|
||||
candidates = [
|
||||
os.environ.get(GIT_ENVIRONMENT_KEY),
|
||||
DEFAULT_WINDOWS_GIT,
|
||||
"git.exe",
|
||||
"git",
|
||||
]
|
||||
|
||||
for candidate in candidates:
|
||||
if not candidate:
|
||||
continue
|
||||
|
||||
if os.path.isabs(candidate):
|
||||
if os.path.exists(candidate):
|
||||
return candidate
|
||||
continue
|
||||
|
||||
resolved_candidate = shutil.which(candidate)
|
||||
if resolved_candidate:
|
||||
return resolved_candidate
|
||||
|
||||
raise RuntimeError(f"No usable git executable found. Set {GIT_ENVIRONMENT_KEY} to override it.")
|
||||
|
||||
|
||||
def resolve_request_timeout_seconds() -> int:
|
||||
configured_timeout = os.environ.get(REQUEST_TIMEOUT_ENVIRONMENT_KEY)
|
||||
if not configured_timeout:
|
||||
return DEFAULT_REQUEST_TIMEOUT_SECONDS
|
||||
|
||||
try:
|
||||
parsed_timeout = int(configured_timeout)
|
||||
except ValueError as error:
|
||||
raise RuntimeError(
|
||||
f"{REQUEST_TIMEOUT_ENVIRONMENT_KEY} must be an integer number of seconds."
|
||||
) from error
|
||||
|
||||
if parsed_timeout <= 0:
|
||||
raise RuntimeError(f"{REQUEST_TIMEOUT_ENVIRONMENT_KEY} must be greater than zero.")
|
||||
|
||||
return parsed_timeout
|
||||
|
||||
|
||||
def run_command(args: list[str]) -> str:
|
||||
process = subprocess.run(args, capture_output=True, text=True, check=False)
|
||||
if process.returncode != 0:
|
||||
stderr = process.stderr.strip()
|
||||
raise RuntimeError(f"Command failed: {' '.join(args)}\n{stderr}")
|
||||
return process.stdout.strip()
|
||||
|
||||
|
||||
def get_current_branch() -> str:
|
||||
return run_command([resolve_git_command(), "rev-parse", "--abbrev-ref", "HEAD"])
|
||||
|
||||
|
||||
def open_url(url: str, accept: str) -> tuple[str, Any]:
|
||||
opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
|
||||
request = urllib.request.Request(url, headers={"Accept": accept, "User-Agent": USER_AGENT})
|
||||
with opener.open(request, timeout=resolve_request_timeout_seconds()) as response:
|
||||
return response.read().decode("utf-8", "replace"), response.headers
|
||||
|
||||
|
||||
def fetch_json(url: str) -> tuple[Any, Any]:
|
||||
text, headers = open_url(url, accept="application/vnd.github+json")
|
||||
return json.loads(text), headers
|
||||
|
||||
|
||||
def extract_next_link(headers: Any) -> str | None:
|
||||
link_header = headers.get("Link")
|
||||
if not link_header:
|
||||
return None
|
||||
|
||||
match = re.search(r'<([^>]+)>;\s*rel="next"', link_header)
|
||||
return match.group(1) if match else None
|
||||
|
||||
|
||||
def fetch_paged_json(url: str) -> list[dict[str, Any]]:
|
||||
items: list[dict[str, Any]] = []
|
||||
next_url: str | None = url
|
||||
while next_url:
|
||||
payload, headers = fetch_json(next_url)
|
||||
if not isinstance(payload, list):
|
||||
raise RuntimeError(f"Expected list payload from GitHub API, got {type(payload).__name__}.")
|
||||
|
||||
items.extend(payload)
|
||||
next_url = extract_next_link(headers)
|
||||
|
||||
return items
|
||||
|
||||
|
||||
def fetch_pull_request_metadata(pr_number: int) -> dict[str, Any]:
|
||||
payload, _ = fetch_json(f"https://api.github.com/repos/{OWNER}/{REPO}/pulls/{pr_number}")
|
||||
if not isinstance(payload, dict):
|
||||
raise RuntimeError("Failed to fetch GitHub PR metadata.")
|
||||
|
||||
return {
|
||||
"number": int(payload["number"]),
|
||||
"title": payload["title"],
|
||||
"state": str(payload["state"]).upper(),
|
||||
"head_branch": payload["head"]["ref"],
|
||||
"base_branch": payload["base"]["ref"],
|
||||
"url": payload["html_url"],
|
||||
}
|
||||
|
||||
|
||||
def resolve_pr_number(branch: str) -> int:
|
||||
head_query = urllib.parse.quote(f"{OWNER}:{branch}")
|
||||
payload, _ = fetch_json(f"https://api.github.com/repos/{OWNER}/{REPO}/pulls?state=all&head={head_query}")
|
||||
if not isinstance(payload, list):
|
||||
raise RuntimeError("Failed to resolve pull request from branch.")
|
||||
|
||||
matching_pull_requests = [item for item in payload if item.get("head", {}).get("ref") == branch]
|
||||
if not matching_pull_requests:
|
||||
raise RuntimeError(f"No public PR matched branch '{branch}'.")
|
||||
|
||||
latest_pull_request = max(matching_pull_requests, key=lambda item: item.get("updated_at", ""))
|
||||
return int(latest_pull_request["number"])
|
||||
|
||||
|
||||
def collapse_whitespace(text: str) -> str:
|
||||
return re.sub(r"\s+", " ", text).strip()
|
||||
|
||||
|
||||
def strip_tags(text: str) -> str:
|
||||
return collapse_whitespace(re.sub(r"<[^>]+>", " ", text))
|
||||
|
||||
|
||||
def extract_section(text: str, start_marker: str, end_markers: list[str]) -> str | None:
|
||||
start = text.find(start_marker)
|
||||
if start < 0:
|
||||
return None
|
||||
|
||||
end = len(text)
|
||||
for marker in end_markers:
|
||||
marker_index = text.find(marker, start + len(start_marker))
|
||||
if marker_index >= 0:
|
||||
end = min(end, marker_index)
|
||||
|
||||
return text[start:end].strip()
|
||||
|
||||
|
||||
def parse_failed_checks(summary_block: str) -> list[dict[str, str]]:
|
||||
failed_section = extract_section(
|
||||
summary_block,
|
||||
"### ❌ Failed checks",
|
||||
["<details>\n<summary>✅ Passed checks", "<sub>", "<!-- pre_merge_checks_walkthrough_end -->"],
|
||||
)
|
||||
if failed_section is None:
|
||||
return []
|
||||
|
||||
rows: list[dict[str, str]] = []
|
||||
for line in failed_section.splitlines():
|
||||
stripped = line.strip()
|
||||
if not stripped.startswith("|") or "Check name" in stripped or stripped.startswith("| :"):
|
||||
continue
|
||||
|
||||
parts = [part.strip() for part in stripped.strip("|").split("|")]
|
||||
if len(parts) != 4:
|
||||
continue
|
||||
|
||||
rows.append(
|
||||
{
|
||||
"name": parts[0],
|
||||
"status": parts[1],
|
||||
"explanation": parts[2],
|
||||
"resolution": parts[3],
|
||||
}
|
||||
)
|
||||
|
||||
return rows
|
||||
|
||||
|
||||
def parse_actionable_comments(actionable_block: str) -> dict[str, Any]:
|
||||
comment_count_match = re.search(r"Actionable comments posted:\s*(\d+)", actionable_block)
|
||||
count = int(comment_count_match.group(1)) if comment_count_match else 0
|
||||
|
||||
comments: list[dict[str, str]] = []
|
||||
primary_block = actionable_block.split(
|
||||
"<details>\n<summary>🤖 Prompt for all review comments with AI agents</summary>",
|
||||
1,
|
||||
)[0]
|
||||
pattern = re.compile(
|
||||
r"<summary>"
|
||||
r"((?:[^<\n]+/)*[^<\n]+\.(?:cs|md|csproj|yaml|yml|json|txt|props|targets)|AGENTS\.md|CLAUDE\.md|README\.md|\.gitignore)"
|
||||
r" \((\d+)\)</summary><blockquote>\s*(.*?)\s*(?:(?:</blockquote></details>)|(?:</blockquote>))",
|
||||
re.S,
|
||||
)
|
||||
|
||||
for path, _, body in pattern.findall(primary_block):
|
||||
finding_match = re.search(r"`([^`]+)`: \*\*(.*?)\*\*", body, re.S)
|
||||
prompt_match = re.search(r"<summary>🤖 Prompt for AI Agents</summary>\s*```(.*?)```", body, re.S)
|
||||
suggestion_match = re.search(r"<summary>✏️ 建议文案调整</summary>\s*```diff(.*?)```", body, re.S)
|
||||
|
||||
body_without_details = body.split("<details>", 1)[0]
|
||||
description = strip_tags(body_without_details)
|
||||
if finding_match is not None:
|
||||
description = description.replace(f"{finding_match.group(1)}: {finding_match.group(2)}", "").strip()
|
||||
|
||||
comments.append(
|
||||
{
|
||||
"path": path.strip(),
|
||||
"range": finding_match.group(1).strip() if finding_match else "",
|
||||
"title": collapse_whitespace(finding_match.group(2)) if finding_match else "",
|
||||
"description": description,
|
||||
"suggested_diff": suggestion_match.group(1).strip() if suggestion_match else "",
|
||||
"ai_prompt": prompt_match.group(1).strip() if prompt_match else "",
|
||||
}
|
||||
)
|
||||
|
||||
prompt_match = re.search(
|
||||
r"<summary>🤖 Prompt for all review comments with AI agents</summary>\s*```(.*?)```",
|
||||
actionable_block,
|
||||
re.S,
|
||||
)
|
||||
|
||||
return {
|
||||
"count": count,
|
||||
"comments": comments,
|
||||
"all_comments_prompt": prompt_match.group(1).strip() if prompt_match else "",
|
||||
"raw": actionable_block.strip(),
|
||||
}
|
||||
|
||||
|
||||
def parse_test_report(block: str) -> dict[str, Any]:
|
||||
report: dict[str, Any] = {
|
||||
"raw": block.strip(),
|
||||
"stats": {},
|
||||
"failed_tests": [],
|
||||
"has_failed_tests": False,
|
||||
}
|
||||
|
||||
summary_row_match = re.search(
|
||||
r"\|\s*\*?\*?(\d+)\*?\*?\s*\|\s*\*?\*?(\d+)\*?\*?\s*\|\s*\*?\*?(\d+)\*?\*?\s*\|"
|
||||
r"\s*\*?\*?(\d+)\*?\*?\s*\|\s*\*?\*?(\d+)\*?\*?\s*\|\s*\*?\*?(\d+)\*?\*?\s*\|\s*\*?\*?([^\|]+?)\*?\*?\s*\|",
|
||||
block,
|
||||
)
|
||||
if summary_row_match is not None:
|
||||
report["stats"] = {
|
||||
"tests": int(summary_row_match.group(1)),
|
||||
"passed": int(summary_row_match.group(2)),
|
||||
"failed": int(summary_row_match.group(3)),
|
||||
"skipped": int(summary_row_match.group(4)),
|
||||
"other": int(summary_row_match.group(5)),
|
||||
"flaky": int(summary_row_match.group(6)),
|
||||
"duration": summary_row_match.group(7).strip(),
|
||||
}
|
||||
|
||||
failed_tests_section = extract_section(
|
||||
block,
|
||||
"### Failed Tests",
|
||||
["### Slowest Tests", "### Insights", "<sub>", "[Github Test Reporter]"],
|
||||
)
|
||||
if failed_tests_section:
|
||||
lines = [line.strip("- ").strip() for line in failed_tests_section.splitlines()[1:] if line.strip()]
|
||||
report["failed_tests"] = lines
|
||||
report["has_failed_tests"] = True
|
||||
elif "No failed tests in this run." in block or "All tests passed!" in block:
|
||||
report["failed_tests"] = []
|
||||
report["has_failed_tests"] = False
|
||||
|
||||
return report
|
||||
|
||||
|
||||
def fetch_issue_comments(pr_number: int) -> list[dict[str, Any]]:
|
||||
return fetch_paged_json(f"https://api.github.com/repos/{OWNER}/{REPO}/issues/{pr_number}/comments?per_page=100")
|
||||
|
||||
|
||||
def select_latest_comment_body(
|
||||
comments: list[dict[str, Any]],
|
||||
predicate: Any,
|
||||
required_user: str | None = None,
|
||||
) -> str:
|
||||
matching_comments = []
|
||||
for comment in comments:
|
||||
body = html.unescape(str(comment.get("body", "")))
|
||||
if required_user is not None and comment.get("user", {}).get("login") != required_user:
|
||||
continue
|
||||
if predicate(body):
|
||||
comment_copy = dict(comment)
|
||||
comment_copy["body"] = body
|
||||
matching_comments.append(comment_copy)
|
||||
|
||||
if not matching_comments:
|
||||
return ""
|
||||
|
||||
latest_comment = max(matching_comments, key=lambda item: (item.get("updated_at", ""), item.get("created_at", "")))
|
||||
return str(latest_comment.get("body", "")).strip()
|
||||
|
||||
|
||||
def select_comment_bodies(
|
||||
comments: list[dict[str, Any]],
|
||||
predicate: Any,
|
||||
required_user: str | None = None,
|
||||
) -> list[str]:
|
||||
matching_comments = []
|
||||
for comment in comments:
|
||||
body = html.unescape(str(comment.get("body", "")))
|
||||
if required_user is not None and comment.get("user", {}).get("login") != required_user:
|
||||
continue
|
||||
if predicate(body):
|
||||
comment_copy = dict(comment)
|
||||
comment_copy["body"] = body
|
||||
matching_comments.append(comment_copy)
|
||||
|
||||
matching_comments.sort(key=lambda item: (item.get("created_at", ""), item.get("updated_at", "")))
|
||||
return [str(comment.get("body", "")).strip() for comment in matching_comments]
|
||||
|
||||
|
||||
def summarize_review_comment(comment: dict[str, Any]) -> dict[str, Any]:
|
||||
return {
|
||||
"id": comment.get("id"),
|
||||
"path": comment.get("path") or "",
|
||||
"line": comment.get("line"),
|
||||
"side": comment.get("side") or "",
|
||||
"created_at": comment.get("created_at") or "",
|
||||
"updated_at": comment.get("updated_at") or "",
|
||||
"user": comment.get("user", {}).get("login") or "",
|
||||
"commit_id": comment.get("commit_id") or "",
|
||||
"in_reply_to_id": comment.get("in_reply_to_id"),
|
||||
"body": comment.get("body") or "",
|
||||
}
|
||||
|
||||
|
||||
def classify_review_thread_status(latest_comment: dict[str, Any]) -> str:
|
||||
body = latest_comment.get("body") or ""
|
||||
author = latest_comment.get("user") or ""
|
||||
if author == CODERABBIT_LOGIN and REVIEW_COMMENT_ADDRESSED_MARKER in body:
|
||||
return "addressed"
|
||||
return "open"
|
||||
|
||||
|
||||
def contains_visible_addressed_commit_text(body: str) -> bool:
|
||||
return bool(VISIBLE_ADDRESSED_IN_COMMIT_PATTERN.search(body))
|
||||
|
||||
|
||||
def build_latest_commit_review_threads(comments: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
comment_threads: dict[int, dict[str, Any]] = {}
|
||||
|
||||
# GitHub review replies point to the root comment id. Grouping them first lets
|
||||
# the skill surface the latest thread state instead of every historical reply.
|
||||
for comment in sorted(comments, key=lambda item: (item.get("created_at") or "", item.get("id") or 0)):
|
||||
comment_id = comment.get("id")
|
||||
if comment_id is None:
|
||||
continue
|
||||
|
||||
summary = summarize_review_comment(comment)
|
||||
root_id = summary["in_reply_to_id"] or comment_id
|
||||
thread = comment_threads.setdefault(
|
||||
root_id,
|
||||
{
|
||||
"thread_id": root_id,
|
||||
"path": summary["path"],
|
||||
"line": summary["line"],
|
||||
"root_comment": None,
|
||||
"replies": [],
|
||||
},
|
||||
)
|
||||
|
||||
if summary["in_reply_to_id"] is None:
|
||||
thread["root_comment"] = summary
|
||||
thread["path"] = summary["path"]
|
||||
thread["line"] = summary["line"]
|
||||
else:
|
||||
thread["replies"].append(summary)
|
||||
|
||||
threads: list[dict[str, Any]] = []
|
||||
for thread in comment_threads.values():
|
||||
root_comment = thread.get("root_comment")
|
||||
if root_comment is None:
|
||||
continue
|
||||
|
||||
ordered_comments = [root_comment, *thread["replies"]]
|
||||
latest_comment = max(ordered_comments, key=lambda item: (item.get("updated_at") or "", item.get("created_at") or ""))
|
||||
thread["latest_comment"] = latest_comment
|
||||
thread["status"] = classify_review_thread_status(latest_comment)
|
||||
threads.append(thread)
|
||||
|
||||
return sorted(threads, key=lambda item: (item["path"], item["line"] or 0, item["thread_id"]))
|
||||
|
||||
|
||||
def fetch_latest_commit_review(pr_number: int) -> dict[str, Any]:
|
||||
api_base = f"https://api.github.com/repos/{OWNER}/{REPO}/pulls/{pr_number}"
|
||||
commits = fetch_paged_json(f"{api_base}/commits?per_page=100")
|
||||
reviews = fetch_paged_json(f"{api_base}/reviews?per_page=100")
|
||||
comments = fetch_paged_json(f"{api_base}/comments?per_page=100")
|
||||
|
||||
if not commits:
|
||||
return {
|
||||
"latest_commit": {},
|
||||
"latest_review": {},
|
||||
"threads": [],
|
||||
"open_threads": [],
|
||||
}
|
||||
|
||||
latest_commit = commits[-1]
|
||||
latest_commit_sha = latest_commit.get("sha", "")
|
||||
latest_commit_reviews = [
|
||||
review for review in reviews if review.get("commit_id") == latest_commit_sha and review.get("submitted_at")
|
||||
]
|
||||
candidate_reviews = latest_commit_reviews or [review for review in reviews if review.get("submitted_at")]
|
||||
latest_review = (
|
||||
max(candidate_reviews, key=lambda review: review.get("submitted_at", ""))
|
||||
if candidate_reviews
|
||||
else None
|
||||
)
|
||||
|
||||
latest_commit_comments = [comment for comment in comments if comment.get("commit_id") == latest_commit_sha]
|
||||
threads = build_latest_commit_review_threads(latest_commit_comments)
|
||||
open_threads = [thread for thread in threads if thread["status"] == "open"]
|
||||
|
||||
return {
|
||||
"latest_commit": {
|
||||
"sha": latest_commit_sha,
|
||||
"message": latest_commit.get("commit", {}).get("message", ""),
|
||||
},
|
||||
"latest_review": {
|
||||
"id": latest_review.get("id") if latest_review else None,
|
||||
"state": latest_review.get("state") if latest_review else "",
|
||||
"submitted_at": latest_review.get("submitted_at") if latest_review else "",
|
||||
"commit_id": latest_review.get("commit_id") if latest_review else "",
|
||||
"user": latest_review.get("user", {}).get("login") if latest_review else "",
|
||||
"body": latest_review.get("body") if latest_review else "",
|
||||
},
|
||||
"threads": threads,
|
||||
"open_threads": open_threads,
|
||||
}
|
||||
|
||||
|
||||
def build_result(pr_number: int, branch: str) -> dict[str, Any]:
|
||||
warnings: list[str] = []
|
||||
pull_request_metadata = fetch_pull_request_metadata(pr_number)
|
||||
issue_comments = fetch_issue_comments(pr_number)
|
||||
summary_block = select_latest_comment_body(
|
||||
issue_comments,
|
||||
lambda body: "auto-generated comment: summarize by coderabbit.ai" in body,
|
||||
required_user=CODERABBIT_LOGIN,
|
||||
)
|
||||
actionable_block = select_latest_comment_body(
|
||||
issue_comments,
|
||||
lambda body: "Actionable comments posted:" in body and "Prompt for all review comments with AI agents" in body,
|
||||
required_user=CODERABBIT_LOGIN,
|
||||
)
|
||||
test_blocks = select_comment_bodies(
|
||||
issue_comments,
|
||||
lambda body: "CTRF PR COMMENT TAG:" in body or "### Test Results" in body,
|
||||
)
|
||||
|
||||
if not summary_block:
|
||||
warnings.append("CodeRabbit summary block was not found in issue comments.")
|
||||
if not test_blocks:
|
||||
warnings.append("PR test-report block was not found in issue comments.")
|
||||
|
||||
latest_commit_review: dict[str, Any] = {}
|
||||
try:
|
||||
latest_commit_review = fetch_latest_commit_review(pr_number)
|
||||
except Exception as error: # noqa: BLE001
|
||||
warnings.append(f"Latest commit review comments could not be fetched: {error}")
|
||||
|
||||
if not actionable_block and not latest_commit_review.get("threads"):
|
||||
warnings.append("CodeRabbit actionable comments block was not found in issue comments.")
|
||||
|
||||
return {
|
||||
"pull_request": {
|
||||
"number": pull_request_metadata["number"],
|
||||
"title": pull_request_metadata["title"],
|
||||
"state": pull_request_metadata["state"],
|
||||
"head_branch": pull_request_metadata["head_branch"],
|
||||
"base_branch": pull_request_metadata["base_branch"],
|
||||
"url": pull_request_metadata["url"],
|
||||
"resolved_from_branch": branch,
|
||||
},
|
||||
"coderabbit_summary": {
|
||||
"failed_checks": parse_failed_checks(summary_block) if summary_block else [],
|
||||
"raw": summary_block,
|
||||
},
|
||||
"coderabbit_comments": parse_actionable_comments(actionable_block) if actionable_block else {},
|
||||
"latest_commit_review": latest_commit_review,
|
||||
"test_reports": [parse_test_report(block) for block in test_blocks],
|
||||
"parse_warnings": warnings,
|
||||
}
|
||||
|
||||
|
||||
def format_text(result: dict[str, Any]) -> str:
|
||||
lines: list[str] = []
|
||||
pr = result["pull_request"]
|
||||
lines.append(f"PR #{pr['number']}: {pr['title']}")
|
||||
lines.append(f"State: {pr['state']}")
|
||||
lines.append(f"Branch: {pr['head_branch']} -> {pr['base_branch']}")
|
||||
lines.append(f"URL: {pr['url']}")
|
||||
|
||||
failed_checks = result["coderabbit_summary"].get("failed_checks", [])
|
||||
lines.append("")
|
||||
lines.append(f"Failed checks: {len(failed_checks)}")
|
||||
for check in failed_checks:
|
||||
lines.append(f"- {check['name']}: {check['status']}")
|
||||
lines.append(f" Explanation: {check['explanation']}")
|
||||
lines.append(f" Resolution: {check['resolution']}")
|
||||
|
||||
comments = result.get("coderabbit_comments", {}).get("comments", [])
|
||||
lines.append("")
|
||||
lines.append(f"CodeRabbit actionable comments: {len(comments)}")
|
||||
for comment in comments:
|
||||
lines.append(f"- {comment['path']} {comment['range']}".rstrip())
|
||||
if comment["title"]:
|
||||
lines.append(f" Title: {comment['title']}")
|
||||
if comment["description"]:
|
||||
lines.append(f" Description: {comment['description']}")
|
||||
|
||||
latest_commit_review = result.get("latest_commit_review", {})
|
||||
latest_commit = latest_commit_review.get("latest_commit", {})
|
||||
latest_review = latest_commit_review.get("latest_review", {})
|
||||
open_threads = latest_commit_review.get("open_threads", [])
|
||||
if latest_commit:
|
||||
lines.append("")
|
||||
lines.append(f"Latest reviewed commit: {latest_commit.get('sha', '')}")
|
||||
if latest_review:
|
||||
lines.append(
|
||||
"Latest review: "
|
||||
f"{latest_review.get('state', '')} by {latest_review.get('user', '')} "
|
||||
f"at {latest_review.get('submitted_at', '')}"
|
||||
)
|
||||
|
||||
lines.append(
|
||||
"Latest commit review threads: "
|
||||
f"{len(latest_commit_review.get('threads', []))} total, {len(open_threads)} open"
|
||||
)
|
||||
for thread in open_threads:
|
||||
root_comment = thread["root_comment"]
|
||||
latest_comment = thread["latest_comment"]
|
||||
lines.append(f"- {thread['path']}:{thread['line']}")
|
||||
lines.append(f" Root by {root_comment['user']}: {collapse_whitespace(root_comment['body'])}")
|
||||
if latest_comment["id"] != root_comment["id"]:
|
||||
lines.append(f" Latest by {latest_comment['user']}: {collapse_whitespace(latest_comment['body'])}")
|
||||
if contains_visible_addressed_commit_text(root_comment["body"]) or contains_visible_addressed_commit_text(
|
||||
latest_comment["body"]
|
||||
):
|
||||
lines.append(
|
||||
" Note: thread is still open; treat the visible 'Addressed in commit ...' text as unverified until local code matches."
|
||||
)
|
||||
|
||||
lines.append("")
|
||||
lines.append(f"Test reports: {len(result['test_reports'])}")
|
||||
for index, report in enumerate(result["test_reports"], start=1):
|
||||
stats = report.get("stats", {})
|
||||
if stats:
|
||||
lines.append(
|
||||
f"- Report {index}: tests={stats.get('tests')} passed={stats.get('passed')} "
|
||||
f"failed={stats.get('failed')} skipped={stats.get('skipped')} flaky={stats.get('flaky')} "
|
||||
f"duration={stats.get('duration')}"
|
||||
)
|
||||
else:
|
||||
lines.append(f"- Report {index}: no structured test stats parsed")
|
||||
|
||||
if report["has_failed_tests"]:
|
||||
for failed_test in report["failed_tests"]:
|
||||
lines.append(f" Failed test: {failed_test}")
|
||||
else:
|
||||
lines.append(" Failed tests: none reported")
|
||||
|
||||
if result["parse_warnings"]:
|
||||
lines.append("")
|
||||
lines.append("Warnings:")
|
||||
for warning in result["parse_warnings"]:
|
||||
lines.append(f"- {warning}")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--branch", help="Override the current branch name.")
|
||||
parser.add_argument("--pr", type=int, help="Fetch a specific PR number instead of resolving from branch.")
|
||||
parser.add_argument("--format", choices=("text", "json"), default="text")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
if args.pr is not None:
|
||||
pr_number = args.pr
|
||||
branch = args.branch or ""
|
||||
else:
|
||||
branch = args.branch or get_current_branch()
|
||||
pr_number = resolve_pr_number(branch)
|
||||
|
||||
result = build_result(pr_number, branch)
|
||||
|
||||
if args.format == "json":
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
return
|
||||
|
||||
print(format_text(result))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except Exception as error: # noqa: BLE001
|
||||
print(str(error), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
10
.gitignore
vendored
10
.gitignore
vendored
@ -14,6 +14,14 @@ opencode.json
|
||||
.omc/
|
||||
docs/.omc/
|
||||
docs/.vitepress/cache/
|
||||
local-plan/
|
||||
ai-plan/*
|
||||
!ai-plan/README.md
|
||||
!ai-plan/public/
|
||||
ai-plan/public/*
|
||||
!ai-plan/public/README.md
|
||||
!ai-plan/public/**/
|
||||
!ai-plan/public/**/*.md
|
||||
ai-plan/private/
|
||||
ai-libs/
|
||||
# tool
|
||||
.venv/
|
||||
|
||||
49
AGENTS.md
49
AGENTS.md
@ -37,6 +37,16 @@ All AI agents and contributors must follow these rules when writing, reviewing,
|
||||
- The branch naming rule for a new task branch is `<type>/<topic-or-scope>`, where `<type>` should match the intended
|
||||
Conventional Commit category as closely as practical.
|
||||
|
||||
## Repository Boot Skill
|
||||
|
||||
- The repository-maintained Codex boot skill lives at `.codex/skills/gframework-boot/`.
|
||||
- Prefer invoking `$gframework-boot` when the user uses short startup prompts such as `boot`、`continue`、`next step`、
|
||||
`按 boot 开始`、`先看 AGENTS`、`继续当前任务`.
|
||||
- The boot skill is a startup convenience layer, not a replacement for this document. If the skill and `AGENTS.md`
|
||||
diverge, follow `AGENTS.md` first and update the skill in the same change.
|
||||
- The boot skill MUST read `AGENTS.md`、`.ai/environment/tools.ai.yaml`、`ai-plan/public/README.md` and the relevant
|
||||
active-topic `ai-plan/` artifacts before substantive execution.
|
||||
|
||||
## Subagent Usage Rules
|
||||
|
||||
- Use subagents only when the task is complex, the context is likely to grow too large, or the work can be split into
|
||||
@ -286,6 +296,14 @@ bash scripts/validate-csharp-naming.sh
|
||||
|
||||
- Update the relevant `README.md` or `docs/` page when behavior, setup steps, architecture guidance, or user-facing
|
||||
examples change.
|
||||
- Treat `ai-libs/` as a read-only third-party source reference area.
|
||||
- Code under `ai-libs/**` exists for comparison, tracing, design study, and behavior verification; do not modify it
|
||||
unless the user explicitly asks to sync or update that third-party snapshot.
|
||||
- When implementation plans, traces, reviews, or design notes say “reference a third-party project”, prefer the
|
||||
repository-local path under `ai-libs/` instead of an unspecified upstream repository.
|
||||
- If a task depends on observations from `ai-libs/**`, record the referenced path and conclusion in the active plan or
|
||||
trace when the work is multi-step or complex, or when an active tracking document already exists, rather than editing
|
||||
the third-party reference copy.
|
||||
- The main documentation site lives under `docs/`, with Chinese content under `docs/zh-CN/`.
|
||||
- Keep code samples, package names, and command examples aligned with the current repository state.
|
||||
- Prefer documenting behavior and design intent, not only API surface.
|
||||
@ -311,17 +329,40 @@ bash scripts/validate-csharp-naming.sh
|
||||
|
||||
### Task Tracking
|
||||
|
||||
- `ai-plan/` is split by intent:
|
||||
- `ai-plan/public/README.md`: the shared startup index that binds worktrees or branches to active topics and resume
|
||||
entry points
|
||||
- `ai-plan/public/<topic>/todos/`: repository-safe recovery documents for an active topic
|
||||
- `ai-plan/public/<topic>/traces/`: repository-safe execution traces for an active topic
|
||||
- `ai-plan/public/<topic>/archive/`: archived stage-level artifacts that still belong to an active topic
|
||||
- `ai-plan/public/archive/<topic>/`: completed-topic archives that should not be treated as default boot context
|
||||
- `ai-plan/private/`: worktree-private recovery artifacts; keep these untracked and scoped to the current worktree
|
||||
- Contributors MUST keep committed `ai-plan/public/**` content safe to publish in Git history.
|
||||
- Never write secrets, tokens, credentials, private keys, machine usernames, home-directory paths, hostnames, IP
|
||||
addresses, proprietary URLs, or other sensitive environment details into any `ai-plan/**` file.
|
||||
- Never record absolute file-system paths in `ai-plan/**`; use repository-relative paths, branch names, PR numbers, or
|
||||
stable document identifiers instead.
|
||||
- Use `ai-plan/public/**` only for durable, handoff-safe task state. Put temporary notes, local experiments, or
|
||||
worktree-specific scratch recovery data under `ai-plan/private/`.
|
||||
- `ai-plan/public/README.md` MUST list only active topics. Do not add `ai-plan/public/archive/**` content to the
|
||||
default boot index.
|
||||
- When a worktree-to-topic mapping changes, or when a topic becomes active/inactive, contributors MUST update
|
||||
`ai-plan/public/README.md` in the same change.
|
||||
- When working from a tracked implementation plan, contributors MUST update the corresponding tracking document under
|
||||
`local-plan/todos/` in the same change.
|
||||
`ai-plan/public/<topic>/todos/` in the same change.
|
||||
- Tracking updates MUST reflect completed work, newly discovered issues, validation results, and the next recommended
|
||||
recovery point.
|
||||
- Completing code changes without updating the active tracking document is considered incomplete work.
|
||||
- For any multi-step refactor, migration, or cross-module task, contributors MUST create or adopt a dedicated recovery
|
||||
document under `local-plan/todos/` before making substantive code changes.
|
||||
document under `ai-plan/public/<topic>/todos/` before making substantive code changes.
|
||||
- Recovery documents MUST record the current phase, the active recovery point identifier, known risks, and the next
|
||||
recommended resume step so another contributor or subagent can continue the work safely.
|
||||
- Contributors MUST maintain a matching execution trace under `local-plan/traces/` for complex work. The trace should
|
||||
record the current date, key decisions, validation milestones, and the immediate next step.
|
||||
- Contributors MUST maintain a matching execution trace under `ai-plan/public/<topic>/traces/` for complex work. The
|
||||
trace should record the current date, key decisions, validation milestones, and the immediate next step.
|
||||
- When a stage inside an active topic is fully complete, move the finished artifacts into that topic's `archive/`
|
||||
directory instead of leaving every completed step in the default boot path.
|
||||
- When a topic is fully complete, move the entire topic directory under `ai-plan/public/archive/<topic>/` and remove it
|
||||
from `ai-plan/public/README.md` in the same change.
|
||||
- When a task spans multiple commits or is likely to exceed a single agent context window, update both the recovery
|
||||
document and the trace at each meaningful milestone before pausing or handing work off.
|
||||
- If subagents are used on a complex task, the main agent MUST capture the delegated scope and any accepted findings in
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
/// 自动生成上下文访问能力:
|
||||
/// </para>
|
||||
/// <code>
|
||||
/// using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
/// using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
///
|
||||
/// [ContextAware]
|
||||
/// public partial class PlayerController : IController
|
||||
@ -35,4 +35,4 @@
|
||||
/// <item>可使用 this.GetModel()、this.GetSystem() 等扩展方法访问架构</item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
public interface IController;
|
||||
public interface IController;
|
||||
|
||||
@ -70,16 +70,17 @@
|
||||
<None Remove="GFramework.Cqrs.Abstractions\**"/>
|
||||
<None Remove="GFramework.Cqrs.Tests\**"/>
|
||||
<None Remove="GFramework.Tests.Common\**"/>
|
||||
<None Remove="ai-libs\**" />
|
||||
</ItemGroup>
|
||||
<!-- 聚合核心模块 -->
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="GFramework.Core\GFramework.Core.csproj"/>
|
||||
<ProjectReference Include="GFramework.Game\GFramework.Game.csproj"/>
|
||||
<ProjectReference Include="GFramework.Core\GFramework.Core.csproj" />
|
||||
<ProjectReference Include="GFramework.Game\GFramework.Game.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Remove="GFramework.Core\**"/>
|
||||
<Compile Remove="GFramework.Game\**"/>
|
||||
<Compile Remove="GFramework.Godot\**"/>
|
||||
<Compile Remove="GFramework.Core\**" />
|
||||
<Compile Remove="GFramework.Game\**" />
|
||||
<Compile Remove="GFramework.Godot\**" />
|
||||
<Compile Update="GFramework.Core.SourceGenerators\enums\EnumExtensionsGenerator.cs">
|
||||
<Link>GFramework.Core.SourceGenerators\enums\EnumExtensionsGenerator.cs</Link>
|
||||
</Compile>
|
||||
@ -89,69 +90,71 @@
|
||||
<Compile Update="GFramework.Core.SourceGenerators\logging\LoggerGenerator.cs">
|
||||
<Link>GFramework.Core.SourceGenerators\logging\LoggerGenerator.cs</Link>
|
||||
</Compile>
|
||||
<Compile Remove="GFramework.Godot.SourceGenerators\**"/>
|
||||
<Compile Remove="GFramework.SourceGenerators\**"/>
|
||||
<Compile Remove="GFramework.Core.SourceGenerators\**"/>
|
||||
<Compile Remove="GFramework.Core.SourceGenerators.Abstractions\**"/>
|
||||
<Compile Remove="GFramework.Cqrs.SourceGenerators\**"/>
|
||||
<Compile Remove="GFramework.Game.SourceGenerators\**"/>
|
||||
<Compile Remove="GFramework.SourceGenerators.Common\**"/>
|
||||
<Compile Remove="GFramework.SourceGenerators.Tests\**"/>
|
||||
<Compile Remove="GFramework.Godot.SourceGenerators.Tests\**"/>
|
||||
<Compile Remove="GFramework.Godot.SourceGenerators.Abstractions\**"/>
|
||||
<Compile Remove="GFramework.SourceGenerators.Abstractions\**"/>
|
||||
<Compile Remove="GFramework.Core.Abstractions\**"/>
|
||||
<Compile Remove="GFramework.Godot.Abstractions\**"/>
|
||||
<Compile Remove="GFramework.Game.Abstractions\**"/>
|
||||
<Compile Remove="GFramework.Core.Tests\**"/>
|
||||
<Compile Remove="GFramework.Ecs.Arch.Tests\**"/>
|
||||
<Compile Remove="GFramework.Ecs.Arch\**"/>
|
||||
<Compile Remove="GFramework.Ecs.Arch.Abstractions\**"/>
|
||||
<Compile Remove="GFramework.Generator\**"/>
|
||||
<Compile Remove="GFramework.Generator.Attributes\**"/>
|
||||
<Compile Remove="GFramework.Godot.SourceGenerators.Attributes\**"/>
|
||||
<Compile Remove="GFramework.SourceGenerators.Attributes\**"/>
|
||||
<Compile Remove="Godot\**"/>
|
||||
<Compile Remove="GFramework.Game.Tests\**"/>
|
||||
<Compile Remove="GFramework.Godot.Tests\**"/>
|
||||
<Compile Remove="GFramework.Cqrs\**"/>
|
||||
<Compile Remove="GFramework.Cqrs.Abstractions\**"/>
|
||||
<Compile Remove="GFramework.Cqrs.Tests\**"/>
|
||||
<Compile Remove="GFramework.Tests.Common\**"/>
|
||||
<Compile Remove="GFramework.Godot.SourceGenerators\**" />
|
||||
<Compile Remove="GFramework.SourceGenerators\**" />
|
||||
<Compile Remove="GFramework.Core.SourceGenerators\**" />
|
||||
<Compile Remove="GFramework.Core.SourceGenerators.Abstractions\**" />
|
||||
<Compile Remove="GFramework.Cqrs.SourceGenerators\**" />
|
||||
<Compile Remove="GFramework.Game.SourceGenerators\**" />
|
||||
<Compile Remove="GFramework.SourceGenerators.Common\**" />
|
||||
<Compile Remove="GFramework.SourceGenerators.Tests\**" />
|
||||
<Compile Remove="GFramework.Godot.SourceGenerators.Tests\**" />
|
||||
<Compile Remove="GFramework.Godot.SourceGenerators.Abstractions\**" />
|
||||
<Compile Remove="GFramework.SourceGenerators.Abstractions\**" />
|
||||
<Compile Remove="GFramework.Core.Abstractions\**" />
|
||||
<Compile Remove="GFramework.Godot.Abstractions\**" />
|
||||
<Compile Remove="GFramework.Game.Abstractions\**" />
|
||||
<Compile Remove="GFramework.Core.Tests\**" />
|
||||
<Compile Remove="GFramework.Ecs.Arch.Tests\**" />
|
||||
<Compile Remove="GFramework.Ecs.Arch\**" />
|
||||
<Compile Remove="GFramework.Ecs.Arch.Abstractions\**" />
|
||||
<Compile Remove="GFramework.Generator\**" />
|
||||
<Compile Remove="GFramework.Generator.Attributes\**" />
|
||||
<Compile Remove="GFramework.Godot.SourceGenerators.Attributes\**" />
|
||||
<Compile Remove="GFramework.SourceGenerators.Attributes\**" />
|
||||
<Compile Remove="Godot\**" />
|
||||
<Compile Remove="GFramework.Game.Tests\**" />
|
||||
<Compile Remove="GFramework.Godot.Tests\**" />
|
||||
<Compile Remove="GFramework.Cqrs\**" />
|
||||
<Compile Remove="GFramework.Cqrs.Abstractions\**" />
|
||||
<Compile Remove="GFramework.Cqrs.Tests\**" />
|
||||
<Compile Remove="GFramework.Tests.Common\**" />
|
||||
<Compile Remove="ai-libs\**" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Remove="GFramework.Core\**"/>
|
||||
<EmbeddedResource Remove="GFramework.Game\**"/>
|
||||
<EmbeddedResource Remove="GFramework.Godot\**"/>
|
||||
<EmbeddedResource Remove="GFramework.Godot.SourceGenerators\**"/>
|
||||
<EmbeddedResource Remove="GFramework.SourceGenerators\**"/>
|
||||
<EmbeddedResource Remove="GFramework.Core.SourceGenerators\**"/>
|
||||
<EmbeddedResource Remove="GFramework.Core.SourceGenerators.Abstractions\**"/>
|
||||
<EmbeddedResource Remove="GFramework.Cqrs.SourceGenerators\**"/>
|
||||
<EmbeddedResource Remove="GFramework.Game.SourceGenerators\**"/>
|
||||
<EmbeddedResource Remove="GFramework.SourceGenerators.Common\**"/>
|
||||
<EmbeddedResource Remove="GFramework.SourceGenerators.Tests\**"/>
|
||||
<EmbeddedResource Remove="GFramework.Godot.SourceGenerators.Tests\**"/>
|
||||
<EmbeddedResource Remove="GFramework.Godot.SourceGenerators.Abstractions\**"/>
|
||||
<EmbeddedResource Remove="GFramework.SourceGenerators.Abstractions\**"/>
|
||||
<EmbeddedResource Remove="GFramework.Core.Abstractions\**"/>
|
||||
<EmbeddedResource Remove="GFramework.Godot.Abstractions\**"/>
|
||||
<EmbeddedResource Remove="GFramework.Game.Abstractions\**"/>
|
||||
<EmbeddedResource Remove="GFramework.Core.Tests\**"/>
|
||||
<EmbeddedResource Remove="GFramework.Ecs.Arch.Tests\**"/>
|
||||
<EmbeddedResource Remove="GFramework.Ecs.Arch\**"/>
|
||||
<EmbeddedResource Remove="GFramework.Ecs.Arch.Abstractions\**"/>
|
||||
<EmbeddedResource Remove="GFramework.Generator\**"/>
|
||||
<EmbeddedResource Remove="GFramework.Generator.Attributes\**"/>
|
||||
<EmbeddedResource Remove="GFramework.Godot.SourceGenerators.Attributes\**"/>
|
||||
<EmbeddedResource Remove="GFramework.SourceGenerators.Attributes\**"/>
|
||||
<EmbeddedResource Remove="Godot\**"/>
|
||||
<EmbeddedResource Remove="GFramework.Game.Tests\**"/>
|
||||
<EmbeddedResource Remove="GFramework.Godot.Tests\**"/>
|
||||
<EmbeddedResource Remove="GFramework.Cqrs\**"/>
|
||||
<EmbeddedResource Remove="GFramework.Cqrs.Abstractions\**"/>
|
||||
<EmbeddedResource Remove="GFramework.Cqrs.Tests\**"/>
|
||||
<EmbeddedResource Remove="GFramework.Tests.Common\**"/>
|
||||
<EmbeddedResource Remove="GFramework.Core\**" />
|
||||
<EmbeddedResource Remove="GFramework.Game\**" />
|
||||
<EmbeddedResource Remove="GFramework.Godot\**" />
|
||||
<EmbeddedResource Remove="GFramework.Godot.SourceGenerators\**" />
|
||||
<EmbeddedResource Remove="GFramework.SourceGenerators\**" />
|
||||
<EmbeddedResource Remove="GFramework.Core.SourceGenerators\**" />
|
||||
<EmbeddedResource Remove="GFramework.Core.SourceGenerators.Abstractions\**" />
|
||||
<EmbeddedResource Remove="GFramework.Cqrs.SourceGenerators\**" />
|
||||
<EmbeddedResource Remove="GFramework.Game.SourceGenerators\**" />
|
||||
<EmbeddedResource Remove="GFramework.SourceGenerators.Common\**" />
|
||||
<EmbeddedResource Remove="GFramework.SourceGenerators.Tests\**" />
|
||||
<EmbeddedResource Remove="GFramework.Godot.SourceGenerators.Tests\**" />
|
||||
<EmbeddedResource Remove="GFramework.Godot.SourceGenerators.Abstractions\**" />
|
||||
<EmbeddedResource Remove="GFramework.SourceGenerators.Abstractions\**" />
|
||||
<EmbeddedResource Remove="GFramework.Core.Abstractions\**" />
|
||||
<EmbeddedResource Remove="GFramework.Godot.Abstractions\**" />
|
||||
<EmbeddedResource Remove="GFramework.Game.Abstractions\**" />
|
||||
<EmbeddedResource Remove="GFramework.Core.Tests\**" />
|
||||
<EmbeddedResource Remove="GFramework.Ecs.Arch.Tests\**" />
|
||||
<EmbeddedResource Remove="GFramework.Ecs.Arch\**" />
|
||||
<EmbeddedResource Remove="GFramework.Ecs.Arch.Abstractions\**" />
|
||||
<EmbeddedResource Remove="GFramework.Generator\**" />
|
||||
<EmbeddedResource Remove="GFramework.Generator.Attributes\**" />
|
||||
<EmbeddedResource Remove="GFramework.Godot.SourceGenerators.Attributes\**" />
|
||||
<EmbeddedResource Remove="GFramework.SourceGenerators.Attributes\**" />
|
||||
<EmbeddedResource Remove="Godot\**" />
|
||||
<EmbeddedResource Remove="GFramework.Game.Tests\**" />
|
||||
<EmbeddedResource Remove="GFramework.Godot.Tests\**" />
|
||||
<EmbeddedResource Remove="GFramework.Cqrs\**" />
|
||||
<EmbeddedResource Remove="GFramework.Cqrs.Abstractions\**" />
|
||||
<EmbeddedResource Remove="GFramework.Cqrs.Tests\**" />
|
||||
<EmbeddedResource Remove="GFramework.Tests.Common\**" />
|
||||
<EmbeddedResource Remove="ai-libs\**" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Remove="AnalyzerReleases.Shipped.md"/>
|
||||
|
||||
@ -4,7 +4,7 @@ using Godot;
|
||||
using GFramework.Core.Abstractions.Controller;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions;
|
||||
using GFramework.SourceGenerators.Abstractions.Logging;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
|
||||
[ContextAware]
|
||||
@ -28,4 +28,3 @@ public partial class _CLASS_ :_BASE_,IController
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ using GFramework.Game.Abstractions.UI;
|
||||
using GFramework.Godot.UI;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions;
|
||||
using GFramework.SourceGenerators.Abstractions.Logging;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
|
||||
[ContextAware]
|
||||
|
||||
@ -133,7 +133,7 @@ GFramework.sln
|
||||
|
||||
1. 先阅读对应模块目录下的 `README.md`
|
||||
2. 如果改动影响采用路径、安装方式、公共 API 或目录结构,同时更新 `docs/zh-CN/`
|
||||
3. 对跨模块或多阶段任务,维护 `local-plan/todos/` 与 `local-plan/traces/`
|
||||
3. 对跨模块或多阶段任务,维护 `ai-plan/public/README.md` 与对应主题目录下的 tracking / trace
|
||||
|
||||
## 许可证
|
||||
|
||||
|
||||
56
ai-plan/README.md
Normal file
56
ai-plan/README.md
Normal file
@ -0,0 +1,56 @@
|
||||
# AI Plan
|
||||
|
||||
`ai-plan/` stores AI task recovery artifacts for this repository, but each subdirectory serves a different sharing and
|
||||
bootstrapping purpose.
|
||||
|
||||
## Directory Semantics
|
||||
|
||||
- `public/README.md`
|
||||
- Shared startup index for `boot`.
|
||||
- Maps worktrees or branches to active topics and points at the primary tracking/trace entry paths.
|
||||
- Must list only active topics.
|
||||
- `public/<topic>/todos/`
|
||||
- Repository-safe recovery documents for one active topic.
|
||||
- Use these for durable task state that another contributor or worktree may need to resume safely.
|
||||
- `public/<topic>/traces/`
|
||||
- Repository-safe execution traces for one active topic.
|
||||
- Record decisions, validation milestones, and the immediate next step.
|
||||
- `public/<topic>/archive/`
|
||||
- Stage-level archive for completed artifacts that still belong to an active topic.
|
||||
- Use this when a topic remains active, but some prior phase no longer belongs in the default boot path.
|
||||
- `public/archive/<topic>/`
|
||||
- Completed-topic archive.
|
||||
- Move the entire topic directory here when that work direction is fully complete.
|
||||
- `private/`
|
||||
- Worktree-private recovery space.
|
||||
- Use this for temporary notes, local scratch recovery points, or state that only matters in the current worktree.
|
||||
- Keep this directory untracked.
|
||||
|
||||
## Workflow Rules
|
||||
|
||||
- `boot` must read `public/README.md` first, then the mapped active topic directories, and only then fall back to
|
||||
scanning active topics directly.
|
||||
- If no mapping exists for the current worktree or branch, scan `public/<topic>/` and ignore `public/archive/` unless
|
||||
the user explicitly asks for historical context.
|
||||
- When a worktree changes its active topic set, update `public/README.md` in the same change.
|
||||
- When a stage is complete, move the finished artifacts into `public/<topic>/archive/`.
|
||||
- When a topic is complete, move the whole topic directory into `public/archive/<topic>/` and remove it from the
|
||||
shared startup index.
|
||||
|
||||
## Content Rules
|
||||
|
||||
- Never write secrets, tokens, credentials, private keys, hostnames, IP addresses, proprietary URLs, or other
|
||||
sensitive data.
|
||||
- Never write absolute file-system paths, home-directory paths, or machine usernames.
|
||||
- Use repository-relative paths, branch names, PR numbers, recovery-point IDs, and stable document identifiers
|
||||
instead.
|
||||
- Keep committed `public/**` content concise, handoff-safe, and understandable without machine-local context.
|
||||
|
||||
## Naming
|
||||
|
||||
- Topic directories should be named by capability or work direction, for example:
|
||||
- `public/ai-plan-governance/`
|
||||
- `public/cqrs-rewrite/`
|
||||
- Worktree-private files should live under a folder named for the current branch or worktree, for example:
|
||||
- `private/feat-cqrs-optimization/`
|
||||
- `private/gframework-cqrs/`
|
||||
35
ai-plan/public/README.md
Normal file
35
ai-plan/public/README.md
Normal file
@ -0,0 +1,35 @@
|
||||
# AI-Plan Public Index
|
||||
|
||||
`ai-plan/public/README.md` is the shared startup index for `boot`. It should stay short, list only active topics, and
|
||||
help the current worktree land on the right recovery documents without scanning every public artifact.
|
||||
|
||||
## Boot Rules
|
||||
|
||||
1. Read this file before scanning `ai-plan/public/<topic>/`.
|
||||
2. If the current branch or worktree appears in the map below, read the listed topics in priority order.
|
||||
3. If there is no match, fall back to scanning active topic directories.
|
||||
4. Ignore `ai-plan/public/archive/**` by default unless the user explicitly asks for historical context.
|
||||
|
||||
## Active Topics
|
||||
|
||||
- `ai-plan-governance`
|
||||
- Purpose: govern the `ai-plan/` directory model, startup index, and archive policy.
|
||||
- Tracking: `ai-plan/public/ai-plan-governance/todos/ai-plan-governance-tracking.md`
|
||||
- Trace: `ai-plan/public/ai-plan-governance/traces/ai-plan-governance-trace.md`
|
||||
- `cqrs-rewrite`
|
||||
- Purpose: continue the CQRS migration, registry hardening, and related PR follow-up.
|
||||
- Tracking: `ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md`
|
||||
- Trace: `ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md`
|
||||
|
||||
## Worktree To Active Topic Map
|
||||
|
||||
- Branch: `feat/cqrs-optimization`
|
||||
- Worktree hint: `GFramework-cqrs`
|
||||
- Priority 1: `ai-plan-governance`
|
||||
- Priority 2: `cqrs-rewrite`
|
||||
|
||||
## Archived Topics
|
||||
|
||||
- `cqrs-cache-docs-hardening`
|
||||
- Archive root: `ai-plan/public/archive/cqrs-cache-docs-hardening/`
|
||||
- Note: archived topics stay outside the default `boot` context until a user explicitly requests historical review.
|
||||
@ -0,0 +1,65 @@
|
||||
# AI-Plan 治理跟踪
|
||||
|
||||
## 目标
|
||||
|
||||
继续收口 `ai-plan/` 的目录语义、启动入口与归档策略,避免多 worktree 并行时的公共恢复文档持续膨胀并拖慢
|
||||
`boot` 的上下文定位。
|
||||
|
||||
- 为 `ai-plan/` 建立明确的目录分层
|
||||
- 区分“可提交共享状态”与“工作树私有状态”
|
||||
- 明确禁止写入敏感数据、绝对路径和机器本地信息
|
||||
- 让 `AGENTS.md`、`ai-plan/README.md` 与 boot skill 使用同一套目录语义
|
||||
- 让 `boot` 能通过公共索引快速定位当前 worktree 的活跃主题
|
||||
- 为阶段完成和主题完成两类归档建立稳定规则
|
||||
|
||||
## 当前恢复点
|
||||
|
||||
- 恢复点编号:`AI-PLAN-GOV-RP-003`
|
||||
- 当前阶段:`Phase 2`
|
||||
- 当前焦点:
|
||||
- 已将共享恢复文档按主题迁移到 `ai-plan/public/<topic>/todos/` 与 `ai-plan/public/<topic>/traces/`
|
||||
- 已为 `boot` 新增 `ai-plan/public/README.md` 公共索引,并绑定当前 worktree 的活跃主题顺序
|
||||
- 已将完成度更高的 `cqrs-cache-docs-hardening` 移入 `ai-plan/public/archive/`
|
||||
- 已同步更新 `.gitignore`、`AGENTS.md`、`ai-plan/README.md`、根 `README.md` 与 `gframework-boot`
|
||||
- 已明确主题内归档与主题级归档的双层规则,避免活动区无限增长
|
||||
|
||||
## 已完成
|
||||
|
||||
- `.gitignore` 现允许 `ai-plan/public/**/*.md` 以主题目录与归档目录形式进入版本控制
|
||||
- `AGENTS.md` 已补充:
|
||||
- `public/README.md`、活动主题目录、主题内归档与主题级归档的职责划分
|
||||
- `boot` 默认忽略 `ai-plan/public/archive/**`
|
||||
- worktree 与活跃主题映射变化时,必须同步更新公共索引
|
||||
- `.codex/skills/gframework-boot/SKILL.md` 与其 `references/startup-artifacts.md` 已切换到:
|
||||
- 优先读取 `ai-plan/public/README.md`
|
||||
- 命中映射后优先读取对应主题目录
|
||||
- 未命中映射时再扫描活动主题目录,并排除公共归档区
|
||||
- `ai-plan/README.md` 已补充主题命名、归档触发条件与 `boot` 读取顺序
|
||||
- 根 `README.md` 已改为要求维护公共索引与对应主题目录
|
||||
- 现有共享文档已迁移为:
|
||||
- `ai-plan/public/ai-plan-governance/**`
|
||||
- `ai-plan/public/cqrs-rewrite/**`
|
||||
- `ai-plan/public/archive/cqrs-cache-docs-hardening/**`
|
||||
- 已根据 PR #253 的最新未解决 review thread 清理 `ai-plan/public/ai-plan-governance/traces/ai-plan-governance-trace.md`
|
||||
中重复的 `### 验证` / `### 下一步` 标题,并补充恢复点后缀以消除 MD024 锚点冲突
|
||||
|
||||
## 验证
|
||||
|
||||
- `find ai-plan/public -maxdepth 4 -type f | sort`
|
||||
- 结果:通过
|
||||
- 备注:活动主题、公共索引与归档主题已按新目录语义落位
|
||||
- `rg -n "ai-plan/public/README.md|ai-plan/public/<topic>|ai-plan/public/archive|ai-plan/private/" AGENTS.md .codex/skills/gframework-boot/SKILL.md .codex/skills/gframework-boot/references/startup-artifacts.md ai-plan/README.md README.md .gitignore`
|
||||
- 结果:通过
|
||||
- 备注:新目录语义、索引入口与归档规则已统一到仓库规则与 boot skill
|
||||
- `dotnet build GFramework.Core.Abstractions/GFramework.Core.Abstractions.csproj -c Release --no-restore`
|
||||
- 结果:通过
|
||||
- 备注:本轮规则与文档调整未引入构建问题
|
||||
- `rg -n "^### (验证|下一步)" ai-plan/public/ai-plan-governance/traces/ai-plan-governance-trace.md`
|
||||
- 结果:通过
|
||||
- 备注:同名标题已按恢复点后缀唯一化,不再产生重复锚点
|
||||
|
||||
## 下一步
|
||||
|
||||
1. 后续新增活动主题时,先在 `ai-plan/public/README.md` 登记 worktree 到主题映射,再创建对应主题目录
|
||||
2. 阶段完成后优先收入主题内 `archive/`;主题整体完成后,再整目录移入 `ai-plan/public/archive/`
|
||||
3. 若未来再新增 skill 或仓库规则引用 `ai-plan/`,统一按“公共索引 + 活动主题 + 归档主题 + 私有目录”扩展,不再恢复平铺结构
|
||||
@ -0,0 +1,58 @@
|
||||
# AI-Plan 治理追踪
|
||||
|
||||
## 2026-04-19
|
||||
|
||||
### 阶段:目录语义收口(RP-002)
|
||||
|
||||
- 建立 `AI-PLAN-GOV-RP-002` 恢复点
|
||||
- 用户指出当前 `ai-plan/` 存在三个治理问题:
|
||||
- 缺少更细的目录分层,容易随着 worktree 增长持续膨胀
|
||||
- 缺少“不得写入敏感数据、真实路径、机器信息”的明确约束
|
||||
- 目录语义没有区分共享恢复信息与 worktree 私有状态
|
||||
- 已据此完成以下收口:
|
||||
- 将既有共享 tracking / trace 文件迁移到扁平的 `ai-plan/public/` 共享目录
|
||||
- 新增 `ai-plan/private/` 作为工作树私有恢复空间,并通过 `.gitignore` 保持未跟踪
|
||||
- 新增 `ai-plan/README.md` 作为目录语义与内容规范的单点说明
|
||||
- 在 `AGENTS.md` 中补齐 public/private 职责边界,以及敏感信息与绝对路径禁写规则
|
||||
- 在 `gframework-boot` 中同步新的读取顺序:优先 public,按需读取当前 worktree 私有目录
|
||||
|
||||
### 验证(RP-002)
|
||||
|
||||
- `find ai-plan -maxdepth 3 -type f | sort`
|
||||
- 结果:通过
|
||||
- `rg -n "ai-plan/public/|ai-plan/private/" AGENTS.md .codex/skills/gframework-boot/SKILL.md .codex/skills/gframework-boot/references/startup-artifacts.md ai-plan/README.md .gitignore`
|
||||
- 结果:通过
|
||||
- `dotnet build GFramework.Core.Abstractions/GFramework.Core.Abstractions.csproj -c Release`
|
||||
- 结果:通过
|
||||
|
||||
### 下一步(RP-002)
|
||||
|
||||
1. 后续若出现新的 worktree 私有恢复需求,直接在 `ai-plan/private/<branch-or-worktree>/` 下创建,不再向共享目录追加本地临时状态
|
||||
2. 若将来需要进一步限制格式,可再为 `public/**` 与 `private/` 各自补一个模板文件,但本轮先把目录语义和安全边界固定下来
|
||||
|
||||
### 阶段:主题分组与启动索引(RP-003)
|
||||
|
||||
- 建立 `AI-PLAN-GOV-RP-003` 恢复点
|
||||
- 用户进一步指出:即使 public/private 已分层,只要多 worktree 并行,扁平的活动主题集合仍会让 `boot` 随着
|
||||
`ai-plan/public` 增长而退化成大范围扫描
|
||||
- 已据此完成第二轮治理:
|
||||
- 将活动共享文档迁移到 `ai-plan/public/<topic>/todos/` 与 `ai-plan/public/<topic>/traces/`
|
||||
- 新增 `ai-plan/public/README.md` 作为公共启动索引,维护 worktree 到多个活跃主题的映射与优先顺序
|
||||
- 将已完成的 `cqrs-cache-docs-hardening` 整体移入 `ai-plan/public/archive/cqrs-cache-docs-hardening/`
|
||||
- 在 `AGENTS.md`、`ai-plan/README.md`、根 `README.md` 与 `gframework-boot` 中统一“公共索引 + 活动主题 +
|
||||
主题内归档 + 主题级归档”的语义
|
||||
- 将 `.gitignore` 调整为允许 `ai-plan/public/**/*.md` 以新层级进入版本控制
|
||||
|
||||
### 验证(RP-003)
|
||||
|
||||
- `find ai-plan/public -maxdepth 4 -type f | sort`
|
||||
- 结果:通过
|
||||
- `rg -n "ai-plan/public/README.md|ai-plan/public/<topic>|ai-plan/public/archive|ai-plan/private/" AGENTS.md .codex/skills/gframework-boot/SKILL.md .codex/skills/gframework-boot/references/startup-artifacts.md ai-plan/README.md README.md .gitignore`
|
||||
- 结果:通过
|
||||
- `dotnet build GFramework.Core.Abstractions/GFramework.Core.Abstractions.csproj -c Release --no-restore`
|
||||
- 结果:通过
|
||||
|
||||
### 下一步(RP-003)
|
||||
|
||||
1. 未来每次新增或关闭主题,都同步更新 `ai-plan/public/README.md`,不要让 `boot` 回到全量扫描模式
|
||||
2. 若某个活跃主题内部继续积累阶段性完成物,优先收入该主题目录下的 `archive/`
|
||||
@ -0,0 +1,67 @@
|
||||
# CQRS Cache And Docs Hardening Tracking
|
||||
|
||||
## Goal
|
||||
|
||||
Address the current CQRS follow-up issues across runtime caches, tests, and user-facing setup/integration
|
||||
documentation so the repository state matches the published onboarding path and the runtime remains safe for
|
||||
collectible assemblies.
|
||||
|
||||
## Current Recovery Point
|
||||
|
||||
- Recovery point: `CQRS-CACHE-DOCS-HARDENING-RP-001`
|
||||
- Current phase: `Phase 2`
|
||||
- Active focus:
|
||||
- validation completed for the targeted CQRS test project
|
||||
- the repository can resume from documentation follow-up or broader solution validation if needed
|
||||
|
||||
## Planned Work
|
||||
|
||||
- [x] Create the matching execution trace under `ai-plan/public/archive/cqrs-cache-docs-hardening/traces/`.
|
||||
- [x] Update `README.md` quick-install guidance to include CQRS runtime packages.
|
||||
- [x] Update the related Chinese setup/integration docs with CQRS runtime + source-generator wiring and a minimal
|
||||
working CQRS generator example.
|
||||
- [x] Replace static `ConcurrentDictionary<Assembly, ...>` / `ConcurrentDictionary<Type, ...>` CQRS caches with
|
||||
weak-key caches that do not permanently root collectible assemblies or handler/message types.
|
||||
- [x] Document the weak-cache thread-safety assumptions and recomputation behavior in code comments/XML docs.
|
||||
- [x] Expand CQRS tests to assert that cached metadata still results in successful registration in every container.
|
||||
- [x] Adapt dispatcher cache tests to the new unload-aware cache structure.
|
||||
- [x] Run targeted CQRS test validation and capture the results here and in the trace.
|
||||
|
||||
## Validation
|
||||
|
||||
Executed:
|
||||
|
||||
```bash
|
||||
dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release
|
||||
dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --no-restore
|
||||
```
|
||||
|
||||
Results:
|
||||
|
||||
- `dotnet test ... -c Release`: passed, `62` tests passed.
|
||||
- `dotnet test ... -c Release --no-restore`: passed, `62` tests passed.
|
||||
- The first run exposed one nullable warning in `GFramework.Cqrs/Internal/WeakKeyCache.cs`; the follow-up fixed the
|
||||
`WeakTypePairCache.TryGetValue(...)` null-flow and reran the full CQRS test project cleanly.
|
||||
- The CQRS doc update intentionally did not rename handler examples from `GFramework.Cqrs.Cqrs.*` to
|
||||
`GFramework.Cqrs.*`, because the current public handler base types still live under the double-`Cqrs` namespace.
|
||||
Instead, the docs now explain the split between message namespaces and handler namespaces explicitly.
|
||||
- A later follow-up added the missing `using GFramework.Cqrs.Command;` to the handler snippet so that the block can be
|
||||
copied independently, documented the private dispatch-binding types in `CqrsDispatcher`, restored the explicit
|
||||
`System.Runtime.CompilerServices` import in `WeakKeyCache.cs`, and removed the `WeakTypePairCache.GetOrAdd(...)`
|
||||
closure allocation by routing the secondary-key factory through a stateful overload.
|
||||
- A final documentation cleanup aligned the `WeakKeyCache<TKey, TValue>.GetOrAdd(...)` XML comments with the runtime
|
||||
contract by stating that `valueFactory` must not return `null`.
|
||||
|
||||
## Known Risks
|
||||
|
||||
- `ConditionalWeakTable`-based caches trade deterministic retention for unload safety, so the implementation must make
|
||||
the recomputation semantics explicit and keep hot-path lookups thread-safe.
|
||||
- The source-generator docs already cover multiple generator families; adding CQRS guidance must stay aligned with the
|
||||
actual runtime APIs and package split.
|
||||
- The existing CQRS docs mix conceptual snippets and minimal examples; changes should improve correctness without
|
||||
silently inventing APIs that the current packages do not expose.
|
||||
|
||||
## Recommended Resume Step
|
||||
|
||||
1. If broader regression confidence is needed, run solution-level CQRS/Core validation next.
|
||||
2. If the public CQRS handler namespaces are flattened in a future refactor, update the docs again in the same change.
|
||||
@ -0,0 +1,90 @@
|
||||
# CQRS Cache And Docs Hardening Trace
|
||||
|
||||
## 2026-04-17
|
||||
|
||||
### Stage: Discovery
|
||||
|
||||
- Read `AGENTS.md` and `.ai/environment/tools.ai.yaml` before selecting repository tools.
|
||||
- Confirmed `README.md` quick-install guidance omits `GeWuYou.GFramework.Cqrs` and
|
||||
`GeWuYou.GFramework.Cqrs.Abstractions` even though the module overview recommends CQRS as a first-class module.
|
||||
- Confirmed `docs/zh-CN/source-generators/index.md` lists the split CQRS generator package but does not provide a
|
||||
dedicated CQRS handler-registry section with package wiring, minimal usage, or compatibility notes.
|
||||
- Confirmed `GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs` has a cross-container metadata-cache test that
|
||||
only asserts reflection/attribute call counts and does not prove the second container still receives registrations.
|
||||
- Confirmed `GFramework.Cqrs/Internal/CqrsDispatcher.cs` and
|
||||
`GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs` currently use process-wide `ConcurrentDictionary<Type, ...>` /
|
||||
`ConcurrentDictionary<Assembly, ...>` caches that strongly retain collectible types and assemblies.
|
||||
- Reviewed `docs/zh-CN/getting-started/installation.md` as a related setup entry page; it also omits the CQRS runtime
|
||||
packages from the installation snippets.
|
||||
|
||||
### Current Recovery Point
|
||||
|
||||
- `CQRS-CACHE-DOCS-HARDENING-RP-001`
|
||||
|
||||
### Immediate Next Step
|
||||
|
||||
1. Update the tracking document and then patch the docs, runtime caches, and tests in one pass.
|
||||
|
||||
### Stage: Implementation
|
||||
|
||||
- Updated `README.md` quick-install guidance to include:
|
||||
- `GeWuYou.GFramework.Cqrs`
|
||||
- `GeWuYou.GFramework.Cqrs.Abstractions`
|
||||
- Updated `docs/zh-CN/getting-started/installation.md` to add CQRS runtime package rows and installation snippets, and
|
||||
corrected the installation verification sample to the current `GFramework.Core.Architectures` /
|
||||
`OnInitialize()` / `OnInit()` API shape.
|
||||
- Updated `docs/zh-CN/core/cqrs.md` to add explicit CQRS package wiring and document the current namespace split
|
||||
between message base types (`GFramework.Cqrs.*`) and handler base types (`GFramework.Cqrs.Cqrs.*`).
|
||||
- Updated `docs/zh-CN/source-generators/index.md` to add a dedicated `CQRS Handler Registry 生成器` section covering:
|
||||
- required runtime and generator packages
|
||||
- a minimal working architecture example
|
||||
- compatibility and migration notes for reflection fallback vs generated registries
|
||||
- Added `GFramework.Cqrs/Internal/WeakKeyCache.cs` with unload-aware weak-key cache helpers built on
|
||||
`ConditionalWeakTable`.
|
||||
- Replaced the CQRS runtime's process-wide strong-reference type/assembly caches with weak-key caches in:
|
||||
- `GFramework.Cqrs/Internal/CqrsDispatcher.cs`
|
||||
- `GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs`
|
||||
- Expanded `GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs` so the cross-container metadata-cache test now
|
||||
proves both containers still resolve the expected handlers after the cache is reused.
|
||||
- Updated `GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherCacheTests.cs` to assert cache reuse through observable cached
|
||||
entries instead of assuming `ConcurrentDictionary`-specific count semantics.
|
||||
- Evaluated the suggestion to change handler doc examples from `using GFramework.Cqrs.Cqrs.Command;` to
|
||||
`using GFramework.Cqrs.Command;`. The repository's actual handler base types still live under
|
||||
`GFramework.Cqrs.Cqrs.*`, so the implementation kept the real API and clarified the namespace split in the docs
|
||||
instead of documenting a non-existent namespace.
|
||||
|
||||
### Stage: Validation
|
||||
|
||||
- Ran:
|
||||
- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release`
|
||||
- Result:
|
||||
- passed, `62` tests passed
|
||||
- The first validation run surfaced one nullable warning in `GFramework.Cqrs/Internal/WeakKeyCache.cs` because the
|
||||
nested weak cache lookup did not prove the secondary cache was non-null to the compiler.
|
||||
|
||||
### Stage: Validation Follow-up
|
||||
|
||||
- Fixed `WeakTypePairCache<TValue>.TryGetValue(...)` to guard the nested weak cache reference explicitly.
|
||||
- Re-ran:
|
||||
- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --no-restore`
|
||||
- Result:
|
||||
- passed, `62` tests passed
|
||||
|
||||
### Immediate Next Step
|
||||
|
||||
1. Stop at `CQRS-CACHE-DOCS-HARDENING-RP-001` unless broader solution-level validation is requested.
|
||||
|
||||
### Stage: Minor Follow-up
|
||||
|
||||
- Updated `docs/zh-CN/core/cqrs.md` again so the standalone handler snippet now imports both:
|
||||
- `GFramework.Cqrs.Command`
|
||||
- `GFramework.Cqrs.Cqrs.Command`
|
||||
- Added XML documentation to the private nested binding types in `GFramework.Cqrs/Internal/CqrsDispatcher.cs` so the
|
||||
intent of the cached service-type/delegate bundles is explicit to future maintainers.
|
||||
- Restored the explicit `using System.Runtime.CompilerServices;` directive at the top of
|
||||
`GFramework.Cqrs/Internal/WeakKeyCache.cs` so `ConditionalWeakTable<,>` does not rely on ambient imports.
|
||||
- Added a stateful `WeakKeyCache<TKey, TValue>.GetOrAdd<TState>(...)` overload and updated
|
||||
`WeakTypePairCache<TValue>.GetOrAdd(...)` to use it, removing the captured lambda allocation from the secondary-key
|
||||
lookup path.
|
||||
- Updated both `WeakKeyCache<TKey, TValue>.GetOrAdd(...)` XML comments to state the full contract:
|
||||
`valueFactory` itself must be non-null, and it must not produce a null cache value.
|
||||
@ -0,0 +1,742 @@
|
||||
# CQRS 重写迁移跟踪
|
||||
|
||||
## 目标
|
||||
|
||||
围绕 `GFramework` 当前的双轨 CQRS 现状,完成一轮以“去 Mediator 外部依赖”为目标的架构迁移:
|
||||
|
||||
- 将 `Mediator` 从 GFramework 公共 API 和运行时主路径中移除
|
||||
- 基于 GFramework 自有抽象重建正式 CQRS runtime、行为管道和注册机制
|
||||
- 保留 `EventBus` 作为框架级事件系统,不与 CQRS notification 混同
|
||||
- 让 `CoreGrid-Migration` 直连本地 `GFramework`,作为真实迁移验证工程
|
||||
- 为复杂迁移建立明确恢复点与进度追踪,避免上下文过长或中断后失去状态
|
||||
|
||||
## 当前恢复点
|
||||
|
||||
- 恢复点编号:`CQRS-REWRITE-RP-042`
|
||||
- 当前阶段:`Phase 8`
|
||||
- 当前焦点:
|
||||
- 已将迁移目标修正为 `Core = App Runtime`、`CQRS = 默认集成进去的可替换子系统`,停止继续追求 `Core` 对 `Cqrs` 的零依赖
|
||||
- 已完成 `Core` 侧 CQRS 实现细节泄漏盘点,并将容器内的 handler 注册去重/调度细节下沉到 `GFramework.Cqrs`
|
||||
- 已将 generator 从“整程序集回退”推进到“可见 handlers 走 `typeof(...)` 直注册,不可直接引用但可定位的 handlers 走 generated registry 内部定向 `Assembly.GetType(...)` 注册”
|
||||
- 已将 partial reflection fallback 从“命中 marker 后整程序集 `GetTypes()`”收敛到“优先消费显式 `Type` / type-name metadata;且对 generator 已能重新定位的隐藏 handlers 不再额外依赖 runtime fallback marker”
|
||||
- 已为手写/第三方程序集补齐正式的精确 fallback 入口:`CqrsReflectionFallbackAttribute` 现在既支持 `string` type-name 清单,也支持直接传入 `Type` 集合
|
||||
- 已将“隐藏 implementation 但 handler interface 仍可直接引用”的 generator 输出进一步收敛为“仅对 implementation 执行一次 `Assembly.GetType(...)`,随后直接按 `typeof(handlerInterface)` 注册”,不再为这类场景额外生成 `GetInterfaces()` / `IsSupportedHandlerInterface(...)` 辅助逻辑
|
||||
- 已将“隐藏 implementation + 隐藏但可精确重建的 handler interface”进一步收敛为“生成期记录 open generic contract 与 type arguments,运行期只做 implementation/type-argument 定向 lookup + `MakeGenericType(...)`”,常见私有嵌套 request 场景已不再需要 `GetInterfaces()` 接口发现
|
||||
- 已将 `RuntimeTypeReference` 继续扩成递归结构,可表达数组与构造泛型;`List<HiddenType>`、`HiddenEnvelope<string>` 这类闭包类型现在也能按生成期已知结构直接重建,进一步贴近 `Mediator` 那种“生成期定结构,运行期只做常量时间绑定”的模式
|
||||
- 已在 `CqrsDispatcher` 热路径补齐 service-type 缓存,减少 `PublishAsync` / `SendAsync` / `CreateStream` 中重复的 `MakeGenericType` 开销
|
||||
- 已将 `CqrsDispatcher` 的 invoker method-definition 查找收敛为静态一次解析,并补齐 request/no-pipeline、request/with-pipeline、notification、stream 四条缓存路径的统一回归
|
||||
- 已将 `CqrsDispatcher` 的 request / pipeline invoker 从 `ValueTask<object?>` 缓存改为按 `TResponse` 分层的强类型委托缓存,减少 value-type 响应在热路径上的装箱与额外拆箱
|
||||
- 已将 `CqrsDispatcher` 的 notification/request/stream 热路径进一步收敛为聚合 dispatch binding cache,把服务类型与强类型 invoker 合并到单一缓存项;
|
||||
request 路径继续按 `TResponse` 分层,并减少单次分发里的重复字典命中
|
||||
- 已将 `CqrsHandlerRegistrar` 的程序集级 attribute 读取、registry 激活分析与未命中 generated-registry 时的 `GetTypes()` 扫描收敛为进程级缓存;
|
||||
同一 `Assembly` 对象跨容器重复接入时不再重复做 registry metadata / fallback metadata / loadable-types 分析
|
||||
- 已完成一轮公开入口文档收口:`README.md`、`CLAUDE.md`、`docs/zh-CN/core/cqrs.md` 与 `docs/zh-CN/source-generators/index.md`
|
||||
现已统一到 `GFramework.Cqrs` 与分拆后的 `Core/Game/Godot/Cqrs SourceGenerators` 模块命名,不再把公开入口写成旧聚合 `GFramework.SourceGenerators`
|
||||
- 已将 `CqrsHandlerRegistryGenerator` 从“同一 implementation 只走一种注册分支”的互斥输出,收敛为“按 handler interface 粒度组合 direct / reflected-implementation / precise-reflected 注册,并继续按接口名稳定排序”;
|
||||
同一 handler implementation 上的可静态绑定接口不再因为另一条接口需要更窄路径而被一起拖进更粗回退
|
||||
- 已将 `CqrsHandlerRegistryGenerator` 的“生成注册器是否能直接引用类型”判断改为基于 Roslyn `Compilation.IsSymbolAccessibleWithin(...)`,不再仅靠声明可见性粗判;
|
||||
对“当前继承/语义上下文可见、但生成注册器顶层上下文不可直接写”的外部 `protected internal` 嵌套类型,现会直接进入精确 type lookup,而不是误生成不可编译的 `typeof(...)`
|
||||
- 已删除 generated registry 内部的 `RegisterRemainingReflectedHandlerInterfaces(...)` implementation 级 `GetInterfaces()` 尾分支;
|
||||
当前合法 C# closed handler contract 已统一收敛到 direct / reflected-implementation / precise-reflected 三类注册路径
|
||||
- 若未来再出现新的 Roslyn 类型形态暂时无法编码进 `RuntimeTypeReferenceSpec`,生成器现会退回到“保留已知静态注册 + 通过程序集级 `CqrsReflectionFallbackAttribute` 对具体 handler implementation 定向补反射”;
|
||||
若 runtime 合同不提供该 marker,则生成器保守地不输出 registry,避免静默漏注册
|
||||
- 已完成一轮 review follow-up 小修:补齐 dispatcher `string` pipeline cache 清理,给 generator 可访问性判断默认分支加上假设说明,并收敛多程序集 generator 测试基础设施的运行时引用缓存与编译错误诊断输出
|
||||
- 已完成三轮 review follow-up:补齐 `ICqrsRuntime` 异常/上下文约束文档,明确 `DefaultCqrsRegistrationService` 的线程安全假设,收窄 `MicrosoftDiContainer` 未冻结态的实例别名去重范围,提前固定/校验 CQRS 程序集枚举输入,并把一类隐藏 handler 从“implementation + interface 运行时发现”进一步压缩到“implementation-only lookup + direct interface registration”
|
||||
- 已将 `GFramework.SourceGenerators` 按目标模块继续拆分为 `GFramework.Core.SourceGenerators` / `GFramework.Core.SourceGenerators.Abstractions` / `GFramework.Cqrs.SourceGenerators` / `GFramework.Game.SourceGenerators`,并同步修正 solution、测试项目与 `Game` schema targets 的引用链
|
||||
- 已删除公开 `Mediator` 兼容壳层:`RegisterMediatorBehavior<TBehavior>()`、`ContextAwareMediator*Extensions` 与 `MediatorCoroutineExtensions`,不再继续维护旧命名兼容表面
|
||||
- 已将 generator 对“外部程序集不可直引 protected nested 类型”的 residual contract 从 `RegisterRemainingReflectedHandlerInterfaces(...)` 补洞推进到“按程序集名 + metadata name 定向 lookup”;`IRequestHandler<Dep.VisibilityScope.ProtectedRequest, Dep.VisibilityScope.ProtectedResponse[]>` 这类场景现在走精确 type lookup,而不再退回 implementation 级 `GetInterfaces()`
|
||||
- 当前已确认在 C# 可编译约束下,generator 主路径已不再需要 implementation 级 `GetInterfaces()` 回退;下一步可继续转向 dispatch/invoker 反射收敛,或补做 source-generator namespace/docs 收口
|
||||
- 已将本任务后续参考的 `Mediator` 源码收口到仓库内 `ai-libs/Mediator`;`ai-libs/**` 现作为第三方只读参考区,不纳入常规实现修改范围
|
||||
- 已开始把 `docs/zh-CN/**` 中残留的旧 `GFramework.SourceGenerators.Abstractions.*` 示例命名空间收口到当前
|
||||
`GFramework.Core.SourceGenerators.Abstractions.*`,避免文档继续指向不存在的旧公开命名空间
|
||||
- 已新增项目级 `$gframework-pr-review` skill,可在当前分支下直接抓取 GitHub PR 页面、提取 CodeRabbit 评论、
|
||||
`Failed checks` 与测试结果,不再依赖 `gh` CLI
|
||||
- 已按 PR `#253` 当前公开 review 信号修正 `gframework-boot` 的 `resume/recovery` 语义、收窄 `AGENTS.md`
|
||||
中 `ai-libs/**` 观察写入 active plan/trace 的触发条件,并补齐模板/接口注释中的旧 `Rule` 命名空间残留
|
||||
|
||||
## 本轮计划
|
||||
|
||||
### Phase 0:工作流基础
|
||||
|
||||
- [x] 在 `ai-plan/public/cqrs-rewrite/todos/` 建立本任务跟踪文档
|
||||
- [x] 在 `ai-plan/public/cqrs-rewrite/traces/` 建立本任务追踪文档
|
||||
- [x] 将恢复点 / trace / subagent 协作规范写入 `AGENTS.md`
|
||||
|
||||
### Phase 1:本地验证链路
|
||||
|
||||
- [x] 确认 `CoreGrid-Migration` 当前引用形态
|
||||
- [x] 将 `CoreGrid-Migration` 从 NuGet 包切到本地 `GFramework` 工程引用
|
||||
- [x] 让 `CoreGrid-Migration` 使用本地 Source Generator 而不是外部已发布版本
|
||||
- [x] 验证本地引用链路至少能完成 restore / build
|
||||
|
||||
### Phase 2:CQRS 基础重建
|
||||
|
||||
- [x] 在 `GFramework.Core.Abstractions` 定义自有 CQRS 契约
|
||||
- [x] 在 `GFramework.Core` 落地 dispatcher / handler registry / behavior pipeline
|
||||
- [x] 清理 `IArchitectureContext` 中对 `Mediator.*` 的公共签名依赖
|
||||
- [x] 设计 CQRS 模块启用方式,替代 `Configurator => AddMediator(...)`
|
||||
|
||||
### Phase 3:接入迁移
|
||||
|
||||
- [x] 迁移 `GFramework.Core.Cqrs.*` 基类到新契约
|
||||
- [x] 迁移 `ContextAwareMediator*Extensions` 与协程扩展
|
||||
- [x] 迁移 `CoreGrid-Migration/scripts/cqrs/**` 到新契约
|
||||
- [x] 删除 `GameArchitecture.Configurator` 中的 `AddMediator(...)`
|
||||
|
||||
### Phase 4:收尾
|
||||
|
||||
- [x] 移除 `Mediator` 包依赖与相关测试/文档残留
|
||||
- [x] 运行目标构建与测试
|
||||
- [x] 记录剩余风险与下一恢复点
|
||||
- [x] 根据 review 收紧 `AbstractStreamCommandHandler` 的生命周期 XML 文档
|
||||
- [x] 将 `CqrsHandlerRegistrar` 容错回归改挂到 `RegisterHandlers` 真实入口
|
||||
- [x] 提升 `CqrsTestRuntime` 反射绑定的签名鲁棒性并补齐 XML 文档
|
||||
- [x] 将剩余 `Mediator` 兼容入口推进到正式弃用周期(隐藏 IntelliSense + 明确 future major 移除)
|
||||
- [x] 落地 CQRS handler source-generator MVP,并保留运行时反射回退
|
||||
- [x] 落地非默认程序集的显式 CQRS handler 注册入口,并避免重复程序集接入导致的重复 handler 映射
|
||||
|
||||
### Phase 5:模块边界再评估
|
||||
|
||||
- [x] 评估是否将 CQRS 拆分为独立的 abstractions 模块与 runtime 实现模块,并梳理 `GFramework.Core` 对其依赖倒置方案
|
||||
- [x] 若拆分收益成立,输出包边界与迁移草案:
|
||||
- `GFramework.Cqrs.Abstractions`:消息契约、handler 契约、pipeline 契约、registry 契约
|
||||
- `GFramework.Cqrs`:dispatcher、runtime 注册、behavior 执行、source-generator 接入
|
||||
- `GFramework.Core`:改为依赖 CQRS 抽象或可选 runtime 接入,而不是继续承载全部 CQRS 实现
|
||||
- [x] 评估拆分后的兼容影响:
|
||||
- `IArchitectureContext` / `ArchitectureBootstrapper` / `MicrosoftDiContainer` 的程序集依赖变化
|
||||
- 现有 `CoreGrid-Migration` 与其他消费端项目的引用路径变化
|
||||
- source-generator、AOT、冷启动、包发布与文档迁移成本
|
||||
|
||||
### Phase 6:拆分前置 seams
|
||||
|
||||
- [x] 在新 CQRS 抽象边界中定义 `ICqrsRuntime` / `ICqrsHandlerRegistrar` 等 runtime seam
|
||||
- [x] 将 `ArchitectureContext` 从直接 `new CqrsDispatcher(...)` 改为依赖容器解析的 runtime 抽象
|
||||
- [x] 将 `MicrosoftDiContainer.RegisterCqrsHandlersFromAssemblies(...)` 从直接调用 `CqrsHandlerRegistrar` 改为依赖 runtime 注册抽象
|
||||
- [x] 保持默认架构程序集 + `GFramework.Core` 程序集 handler 自动接入行为不变
|
||||
- [x] 跑通至少覆盖上述 seam 改造的定向测试
|
||||
|
||||
### Phase 7:项目骨架与类型迁移
|
||||
|
||||
- [x] 创建 `GFramework.Cqrs.Abstractions` 与 `GFramework.Cqrs` 项目骨架,并接入 solution / package graph
|
||||
- [x] 将纯 `GFramework.Core.Abstractions/Cqrs/*` 契约迁移到新 abstractions 项目,同时保持现有命名空间不变
|
||||
- [x] 将 CQRS 聚焦测试拆分到 `GFramework.Cqrs.Tests`,并在 `CI` 中纳入独立测试项目
|
||||
- [x] 将 `ICqrsRuntime` / `ICqrsHandlerRegistry` / `CqrsHandlerRegistryAttribute` 的最终归属与循环依赖处理方案单独收敛
|
||||
- [x] 收敛 `CqrsCoroutineExtensions` 的最终归属:
|
||||
- 结论改为“保留在 `GFramework.Core` 作为 App Runtime 对 CQRS 的协程桥接”,不再作为必须迁往 `GFramework.Cqrs` 的剩余项
|
||||
- [x] 处理 `GFramework.SourceGenerators` 对 CQRS 抽象程序集元数据名的引用迁移
|
||||
- [x] 跑通迁移后最小构建与回归
|
||||
|
||||
### Phase 8:方向修正后的收敛
|
||||
|
||||
- [x] 将 `ai-plan/migration/CQRS_MODULE_SPLIT_PLAN.md` 的目标边界正式改写为:
|
||||
- `GFramework.Core.Abstractions -> GFramework.Cqrs.Abstractions`
|
||||
- `GFramework.Core -> GFramework.Cqrs`
|
||||
- `Core` 默认集成 CQRS,但不依赖其细节结构
|
||||
- [x] 将后续实现约束明确写入计划:
|
||||
- CQRS runtime / generator / 低反射增强阶段必须优先参考 `ai-libs/Mediator` 中已经成熟可用的实现,而不是完全从零重做
|
||||
- [x] 盘点 `Core` 中仍可能泄漏 CQRS 实现细节的位置:
|
||||
- 直接实例化具体 runtime 类型
|
||||
- 直接依赖 generator 生成类型
|
||||
- 硬编码 handler/internal registry 结构
|
||||
- [x] 将 partial reflection fallback 从“整程序集 `GetTypes()` 扫描”推进到“generator 输出精确 handler type-name 清单后,runtime 定向 `Assembly.GetType(...)` 补扫”
|
||||
- [x] 为手写/第三方程序集补齐精确 fallback metadata 入口,避免这类场景只能依赖旧版空 marker 或脆弱的字符串约定
|
||||
- [x] 为 `CqrsDispatcher` 补齐 notification/request/stream service-type 缓存,减少热路径 `MakeGenericType` 重复开销
|
||||
- [x] 为 `CqrsDispatcher` 补齐 invoker method-definition 静态缓存,并把 request/no-pipeline、request/with-pipeline、notification、stream 四条缓存路径纳入统一回归
|
||||
- [x] 将 `CqrsDispatcher` 的分散 service-type / invoker cache 收敛为按消息类别聚合的 dispatch binding cache,进一步减少热路径重复缓存查询
|
||||
- [x] 将 `CqrsHandlerRegistrar` 的程序集级 metadata 分析与 loadable-types 扫描收敛为进程级缓存,减少跨容器重复初始化时的冷路径反射
|
||||
- [x] 收口公开入口文档中的旧 SourceGenerators 聚合模块表述与旧 CQRS 示例命名空间
|
||||
- [x] 将私有/不可直接引用 handler 从“generator 输出 fallback marker + runtime 补扫”推进到“generated registry 内部定向反射注册”,进一步压缩 runtime fallback 面积
|
||||
- [ ] 将后续主线转向 CQRS 子系统增强:
|
||||
- 参考 `ai-libs/Mediator` 现有成熟实现
|
||||
- generator 覆盖面继续扩大
|
||||
- 减少 dispatch/invoker 路径的反射占比
|
||||
- package/facade/兼容层收敛
|
||||
|
||||
## 当前完成结果
|
||||
|
||||
- `GFramework.Cqrs.Abstractions` 与 `GFramework.Cqrs` 项目骨架已创建,并已加入 `GFramework.sln`。
|
||||
- `GFramework.Core.Abstractions` 已新增到 `GFramework.Cqrs.Abstractions` 的项目引用。
|
||||
- `GFramework.Cqrs/CqrsReflectionFallbackAttribute.cs` 已扩展为可承载精确 fallback handler 类型名清单;当清单为空时,仍保持旧版“整程序集补扫”的兼容语义。
|
||||
- `GFramework.Cqrs/CqrsReflectionFallbackAttribute.cs` 现同时支持 `params string[]` 与 `params Type[]` 两种精确 fallback 声明方式,使手写/第三方程序集可以直接提供 handler 类型而不必再走字符串名称回查。
|
||||
- `GFramework.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs` 现会在检测到不可直接引用、但仍可按元数据名从当前程序集重新定位的 concrete handler 时,直接在生成注册器内部输出定向 `Assembly.GetType(...)` 注册逻辑,不再为这些场景额外生成 fallback marker。
|
||||
- `GFramework.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs` 已进一步将“隐藏 implementation,但 handler interface 仍可合法 `typeof(...)` 引用”的场景收敛为:
|
||||
- generated registry 只对 implementation 做一次 `Assembly.GetType(...)`
|
||||
- service type 继续使用 `typeof(IRequestHandler<...>)` / `typeof(INotificationHandler<...>)` / `typeof(IStreamRequestHandler<...>)`
|
||||
- 不再为该类场景生成 `GetInterfaces()` / `IsSupportedHandlerInterface(...)` / `GetRuntimeTypeDisplayName(...)` 等全反射辅助代码
|
||||
- `GFramework.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs` 已继续把“handler interface 本身不可直接引用,但其 open generic contract 与 type arguments 仍可精确表达”的场景收敛为:
|
||||
- 生成期记录 `IRequestHandler<,>` / `INotificationHandler<>` / `IStreamRequestHandler<,>` 的 open generic contract
|
||||
- 对不可直引、但属于当前编译程序集的 type argument 输出定向 `Assembly.GetType(...)`
|
||||
- 运行期通过 `MakeGenericType(...)` 重建精确 service type 并直接注册到隐藏 implementation
|
||||
- 当前合法 closed contract 已不再需要 generated registry 内部的 `GetInterfaces()` 本地回退辅助逻辑;
|
||||
若未来出现新的未覆盖类型形态,则改由程序集级 targeted fallback 合同补位
|
||||
- `GFramework.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs` 现已支持递归重建以下 `RuntimeTypeReference` 形态:
|
||||
- 直接 `typeof(...)` 可引用类型
|
||||
- 当前编译程序集内的隐藏类型定向 `Assembly.GetType(...)`
|
||||
- 数组类型 `T[]` / 多维数组的 `MakeArrayType(...)`
|
||||
- 构造泛型类型 `Generic<T1, T2, ...>` 的递归 `MakeGenericType(...)`
|
||||
- 因此常见“隐藏消息类型嵌套在数组或泛型响应里”的 handler interface,已不再需要退回 `GetInterfaces()` 接口发现。
|
||||
- `GFramework.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs` 已进一步修正同一 implementation 的注册组合策略:
|
||||
- direct registration、reflected-implementation registration 与 precise-reflected registration 不再互斥
|
||||
- 当一个 implementation 同时命中多种注册路径时,生成器会合并这些注册步骤,并继续按 `HandlerInterfaceLogName` 稳定排序输出
|
||||
- 这样可静态绑定的接口不会再因为同实现上的另一条接口需要更窄反射路径而被一起降级
|
||||
- `GFramework.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs` 现已把“生成注册器顶层上下文是否能合法书写某个类型”改为通过 `Compilation.IsSymbolAccessibleWithin(symbol, compilation.Assembly, null)` 判定:
|
||||
- 该调整修正了此前把 `protected internal` 等声明可见类型误当作“可直接 `typeof(...)`”的粗判
|
||||
- 对当前程序集私有类型仍继续走 metadata-name 定向 lookup
|
||||
- 对外部程序集里只能通过继承语义可见、但生成注册器顶层上下文不可直接写的类型,现会正确进入精确的程序集定向 lookup 路径
|
||||
- `GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs` 已删除 generated registry 内部的 implementation 级 `GetInterfaces()` 补洞辅助逻辑:
|
||||
- 不再生成 `RegisterRemainingReflectedHandlerInterfaces(...)`、`knownServiceTypes`、`GetRuntimeTypeDisplayName(...)` 等尾分支辅助代码
|
||||
- 对当前合法 closed handler contract,统一走 direct / reflected-implementation / precise-reflected 三类静态化路径
|
||||
- 若未来出现暂未被 `RuntimeTypeReferenceSpec` 表达的新类型形态,则仅通过程序集级 `CqrsReflectionFallbackAttribute` 对具体 handler implementation 定向补反射
|
||||
- `GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs` 已新增两条混合场景快照回归:
|
||||
- 同一可见 implementation 同时包含 direct + precise registration
|
||||
- 同一隐藏 implementation 同时包含 reflected-implementation + precise registration
|
||||
- `GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs` 已新增多程序集回归:
|
||||
- 通过额外元数据引用构造“外部基类携带 `protected internal` 嵌套 request/response 类型”的真实边界
|
||||
- 锁定生成器会保留已知直注册、输出 `ResolveReferencedAssemblyType(...)` 精确 lookup,且不会重新带回 `RegisterRemainingReflectedHandlerInterfaces(...)` 尾分支或对不可访问 protected-nested 类型生成 `typeof(...)`
|
||||
- `GFramework.SourceGenerators.Tests/Core/GeneratorTest.cs` 与 `MetadataReferenceTestBuilder.cs` 已补齐多程序集测试基础设施,允许 CQRS generator 回归显式追加内存元数据引用。
|
||||
- `GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs` 已改为优先消费 fallback metadata 中的显式 `Type` 集合,其次才按 type-name 清单定向 `Assembly.GetType(...)` 补扫;只有缺少精确元数据时才继续走整程序集 `GetTypes()` 扫描。
|
||||
- `GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs` 已新增回归,分别锁定“string type-name fallback 不触发 `GetTypes()` 全量扫描”与“direct `Type` fallback 同时不触发 `GetType()` / `GetTypes()`”行为;`GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs` 已同步覆盖“隐藏 handler 由 generated registry 自行定向注册”与“无论 runtime 是否暴露 legacy fallback marker,均不再为该场景输出 marker”。
|
||||
- `GFramework.Cqrs/Internal/CqrsDispatcher.cs` 已新增 notification/request/stream service-type 缓存,避免重复为同一消息类型组合执行 `MakeGenericType`。
|
||||
- `GFramework.Cqrs/Internal/CqrsDispatcher.cs` 已将 invoker 对应的泛型方法定义查找收敛为静态缓存,避免每种新消息类型首次命中 invoker cache 时再次执行 `GetMethod(...)` 查找。
|
||||
- `GFramework.Cqrs/Internal/CqrsDispatcher.cs` 已将 request/no-pipeline 与 request/with-pipeline 两条 invoker 路径收敛为按 `TResponse` 分层的强类型缓存:
|
||||
- `SendAsync<TResponse>(...)` 现在直接复用 `ValueTask<TResponse>` 委托
|
||||
- 不再通过 `object` 桥接承接 request 结果,减少 value-type 响应装箱
|
||||
- `GFramework.Cqrs/Internal/CqrsDispatcher.cs` 已继续把 notification/request/stream 的分散 service-type / invoker 缓存收敛为聚合 dispatch binding cache:
|
||||
- `NotificationDispatchBindings` / `RequestDispatchBindingCache<TResponse>` / `StreamDispatchBindings` 会把服务类型与强类型调用委托绑定到单一缓存项
|
||||
- request 路径在保持按 `TResponse` 分层的同时,进一步减少一次分发中的重复字典查询
|
||||
- `GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherCacheTests.cs` 已补齐回归,额外锁定 request invoker 会按 `int` / `string` 等不同响应类型分层建缓存,并在 `SetUp` 中显式清空 dispatcher 静态缓存,避免跨测试污染导致断言漂移。
|
||||
- `GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherCacheTests.cs` 已新增/更新回归,统一锁定 dispatcher 对 request/no-pipeline、request/with-pipeline、notification、stream 的 service-type / invoker cache 都满足“一次建缓存,多次复用”。
|
||||
- `GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs` 已新增三类进程级缓存:
|
||||
- 程序集级 `AssemblyMetadataCache`,复用 generated registry attribute 与 reflection fallback metadata 的分析结果
|
||||
- registry 类型级 `RegistryActivationMetadataCache`,复用接口/抽象态/无参构造激活分析
|
||||
- `LoadableTypesCache`,为未命中 generated registry 的程序集复用首次 `GetTypes()` / `ReflectionTypeLoadException` 恢复结果
|
||||
- 上述缓存对 `Assembly` 统一使用引用相等键,避免 mock/proxy 或自定义 `Assembly.Equals(...)` 语义干扰缓存命中。
|
||||
- `GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs` 已新增回归:
|
||||
- 锁定同一程序集对象跨两个容器重复接入时,`CqrsHandlerRegistryAttribute` / `CqrsReflectionFallbackAttribute` / `Assembly.GetType(...)` 只会解析一次
|
||||
- 锁定未命中 generated registry 时,同一程序集对象跨两个容器重复接入只会执行一次 `GetTypes()`,且两边都能复用首次恢复出的可加载 handler 集合
|
||||
- `README.md`、`CLAUDE.md`、`docs/zh-CN/core/cqrs.md` 与 `docs/zh-CN/source-generators/index.md` 已完成最小同步:
|
||||
- 根 README 的模块表、仓库结构与安装指引已补入 `GFramework.Cqrs` 以及拆分后的 SourceGenerators 家族
|
||||
- `CLAUDE.md` 的依赖图与模块结构说明已从旧 `GFramework.SourceGenerators` 聚合表述改为 `Core/Game/Godot/Cqrs SourceGenerators`
|
||||
- `docs/zh-CN/core/cqrs.md` 的示例命名空间已切到当前 `GFramework.Cqrs*` 公开路径,并把迁移说明改成“直接改用 `RegisterCqrsPipelineBehavior<TBehavior>()`”
|
||||
- `docs/zh-CN/source-generators/index.md` 已从“单一 `GFramework.SourceGenerators` 包”口径改为“按模块拆分的 Source Generators 家族”口径
|
||||
- 以下纯 CQRS 契约已从 `GFramework.Core.Abstractions/Cqrs/*` 迁移到 `GFramework.Cqrs.Abstractions/Cqrs/*`,并保持原命名空间不变:
|
||||
- `IRequest*` / `IStreamRequest*` / `INotification*`
|
||||
- `IPipelineBehavior`
|
||||
- `MessageHandlerDelegate`
|
||||
- `Unit`
|
||||
- `Command/Query/Request/Notification` 输入与标记契约
|
||||
- `ICqrsHandlerRegistrar`
|
||||
- `GFramework.Cqrs.Abstractions/GlobalUsings.cs` 已补齐基础 system 命名空间,避免新项目在关闭 `ImplicitUsings` 约束下丢失 `ValueTask` / `CancellationToken` / `IAsyncEnumerable`。
|
||||
- `GFramework.Cqrs.Tests` 已创建并加入 `GFramework.sln`,当前已承接以下 CQRS 聚焦测试:
|
||||
- `CqrsHandlerRegistrarTests`
|
||||
- `CqrsCoroutineExtensionsTests`
|
||||
- `MediatorAdvancedFeaturesTests`
|
||||
- `MediatorArchitectureIntegrationTests`
|
||||
- `MediatorComprehensiveTests`
|
||||
- `GFramework.Cqrs.Tests/GlobalUsings.cs` 与 `Logging/TestLogger.cs` 已补齐,确保新测试项目不再隐式依赖 `GFramework.Core.Tests` 的编译上下文。
|
||||
- `GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs` 已移除对已迁移测试类型的编译期耦合,改为复用本地最小 CQRS fixture 验证容器层程序集去重与重新注册行为。
|
||||
- `GFramework.Cqrs/ICqrsRegistrationService.cs` 与 `Internal/DefaultCqrsRegistrationService.cs` 已新增:
|
||||
- 将 CQRS handler 程序集接入的稳定键去重与 registrar 调度从 `MicrosoftDiContainer` 下沉到 CQRS runtime 内部
|
||||
- `MicrosoftDiContainer.RegisterCqrsHandlersFromAssemblies(...)` 现只保留公开委托入口,不再直接维护 handler 注册细节状态
|
||||
- `GFramework.Core/Services/Modules/CqrsRuntimeModule.cs` 与 `GFramework.Tests.Common/CqrsTestRuntime.cs` 已同步注册新的 `ICqrsRegistrationService`,确保生产路径与裸测试容器路径都通过同一协调服务接入 handler 程序集。
|
||||
- `GFramework.Cqrs/CqrsReflectionFallbackAttribute.cs` 已新增并保留,作为“手写/第三方程序集或 generator 仍未直接覆盖的场景需要 runtime 补反射”的程序集级兼容入口。
|
||||
- `GFramework.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs` 已从“发现任一不可见 handler 就整程序集不生成”收敛为:
|
||||
- 仍为可由生成代码合法引用的 handlers 生成 registry
|
||||
- 对私有/不可直接引用但可按元数据名重新定位的 handlers,在 generated registry 内部输出定向反射注册
|
||||
- runtime fallback marker 仅保留给手写 metadata 或未来仍无法由生成器自处理的真正残余回退场景;
|
||||
这些场景也不再通过 generated registry 内部 `GetInterfaces()` 尾分支处理
|
||||
- `GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs` 已支持“generated registry + reflection fallback”组合路径:
|
||||
- 当程序集带有 `CqrsReflectionFallbackAttribute` 时,运行时会在执行 generated registry 后继续补一次 reflection 扫描
|
||||
- 反射补扫前会按 `ServiceType + ImplementationType` 去重,避免已由 generated registry 注册的映射重复写入
|
||||
- 覆盖该行为的新增/更新测试已落地:
|
||||
- `GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs`
|
||||
- `GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs`
|
||||
- 本轮盘点结论已明确:
|
||||
- `ArchitectureContext -> ICqrsRuntime` 属于合理 seam,不视为实现细节泄漏
|
||||
- `Core` 当前未直接依赖任何 generator 生成类型名
|
||||
- 仍值得继续收敛的点主要是 `ArchitectureBootstrapper` 默认 handler 程序集硬编码,以及更长线的 `IIocContainer` / `IArchitecture` CQRS 装配 API 暴露面
|
||||
- `GFramework/.github/workflows/ci.yml` 已新增 `dotnet test GFramework.Cqrs.Tests ...`,使 CQRS 模块拆分后的测试在 PR CI 中继续被覆盖。
|
||||
- `GFramework.Core/Cqrs/Internal/CqrsDispatcher.cs`、`CqrsHandlerRegistrar.cs` 与 `DefaultCqrsHandlerRegistrar.cs` 已物理迁移到 `GFramework.Cqrs` 项目,同时保持现有 `GFramework.Core.Cqrs.Internal` 命名空间不变,避免消费端源码感知程序集拆分。
|
||||
- `GFramework.Core/Cqrs/Command/CommandBase.cs`、`Query/QueryBase.cs`、`Request/RequestBase.cs` 与 `Notification/NotificationBase.cs` 已迁移到 `GFramework.Cqrs`,继续保留原公开命名空间。
|
||||
- `GFramework.Core/Cqrs/Behaviors/LoggingBehavior.cs` 与 `PerformanceBehavior.cs` 已物理迁移到 `GFramework.Cqrs/Cqrs/Behaviors/*`,继续保留 `GFramework.Core.Cqrs.Behaviors` 公开命名空间,避免消费端源码感知程序集调整。
|
||||
- `GFramework.Core/Extensions/ContextAwareCqrsExtensions.cs`、`ContextAwareCqrsCommandExtensions.cs` 与 `ContextAwareCqrsQueryExtensions.cs` 已迁移到 `GFramework.Cqrs`,`GFramework.Godot` 与兼容层调用点无需修改源码即可继续解析这些扩展。
|
||||
- `GFramework.Core/Logging/LoggerFactoryResolver.cs` 已下沉到 `GFramework.Core.Abstractions/Logging/LoggerFactoryResolver.cs`,并保持 `GFramework.Core.Logging` 命名空间不变:
|
||||
- 默认 provider 会优先通过反射解析 `GFramework.Core` 中的 `ConsoleLoggerFactoryProvider`
|
||||
- 若宿主未加载默认日志实现,则退回静默 provider,避免 `GFramework.Cqrs -> GFramework.Core` 形成反向依赖
|
||||
- `GFramework.Core` 已通过 type forward 继续暴露该公开类型,降低已编译消费端的运行时兼容风险
|
||||
- `ICqrsHandlerRegistry` 与 `CqrsHandlerRegistryAttribute` 已从 `GFramework.Core.Abstractions` 收敛到 `GFramework.Cqrs` 运行时根命名空间:
|
||||
- 这两个类型依赖 `ILogger` / `IServiceCollection`,不再适合继续放在“纯消息契约”抽象层
|
||||
- 该调整避免了 `GFramework.Core.Abstractions <-> GFramework.Cqrs.Abstractions` 循环引用
|
||||
- `ICqrsRuntime` 已进一步收敛到 `GFramework.Cqrs.Abstractions`:
|
||||
- 新增轻量 marker seam `ICqrsContext`,让 runtime 契约不再直接依赖 `IArchitectureContext`
|
||||
- `IArchitectureContext` 现在实现 `ICqrsContext`,保留当前架构上下文作为默认 CQRS 分发上下文
|
||||
- 旧 `GFramework.Core.Abstractions.Cqrs.ICqrsRuntime` 保留为兼容别名,并由默认 runtime 模块同时注册新旧接口,避免立即打断历史公开路径
|
||||
- `GFramework.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs` 已完成 metadata name 迁移:
|
||||
- handler/interface 元数据名改为指向 `GFramework.Cqrs.Abstractions.Cqrs`
|
||||
- generated registry contract / attribute 元数据名改为指向 `GFramework.Cqrs`
|
||||
- `GFramework.Cqrs/CqrsRuntimeFactory.cs` 已新增为公开工厂,`GFramework.Core/Services/Modules/CqrsRuntimeModule.cs` 与 `GFramework.Tests.Common/CqrsTestRuntime.cs` 现在通过该工厂接线默认 runtime / registrar,而不再跨程序集直接 `new` 内部实现。
|
||||
- 已确认 `GFramework.Core/Coroutine/Extensions/CqrsCoroutineExtensions.cs` 暂不适合迁移到 `GFramework.Cqrs`:
|
||||
- 它直接依赖 `TaskCoroutineExtensions.AsCoroutineInstruction()` 等 `Core` 协程工具链
|
||||
- 若一并迁出会把非 CQRS 的协程基础设施反向拉进 runtime 项目
|
||||
- 在方向修正后,该类型不再作为“必须迁走”的剩余项,而是正式视为 `Core` 对 CQRS 的桥接层
|
||||
- 当前边界结论已修正为:
|
||||
- `GFramework.Core.Abstractions -> GFramework.Cqrs.Abstractions`
|
||||
- `GFramework.Core -> GFramework.Cqrs`
|
||||
- `Core` 默认集成 CQRS,但不应继续依赖其 dispatcher / generator / handler registry 细节结构
|
||||
- 当前主阻塞不再是“如何让 `Core` 摆脱 `Cqrs`”,而是:
|
||||
- 如何继续降低 runtime 对反射的依赖
|
||||
- 如何让 generator 从“注册器 MVP”继续走向更完整的低反射支持
|
||||
- 如何收敛 package/facade/兼容层,而不破坏 `CoreGrid-Migration` 等真实消费端
|
||||
- `GFramework.Core.Abstractions/Cqrs/ICqrsRuntime.cs` 与 `ICqrsHandlerRegistrar.cs` 已新增,形成 `ArchitectureContext` 与容器注册路径共享的 runtime seam。
|
||||
- `GFramework.Core/Cqrs/Internal/DefaultCqrsHandlerRegistrar.cs` 已新增,复用现有 `CqrsHandlerRegistrar` 静态流水线承接 `ICqrsHandlerRegistrar` 默认实现。
|
||||
- `GFramework.Core/Cqrs/Internal/CqrsDispatcher.cs` 已改为实现 `ICqrsRuntime`,并将当前 `IArchitectureContext` 作为调用参数传入,而不再由 `ArchitectureContext` 直接持有具体实现依赖。
|
||||
- `GFramework.Core/Architectures/ArchitectureContext.cs` 已从直接 `new CqrsDispatcher(...)` 改为解析 `ICqrsRuntime` seam。
|
||||
- `GFramework.Core/Ioc/MicrosoftDiContainer.cs` 已从直接调用 `CqrsHandlerRegistrar` 改为解析已注册的 `ICqrsHandlerRegistrar` seam。
|
||||
- `GFramework.Core/Services/Modules/CqrsRuntimeModule.cs` 已新增,并由 `ServiceModuleManager` 纳入 built-in modules,确保默认架构启动路径继续自动具备 CQRS runtime 与 handler 注册能力。
|
||||
- `GFramework.Core.Tests/CqrsTestRuntime.cs` 已补充裸测试容器的 CQRS seam 注册辅助,以便不经过 `ServiceModuleManager` 的单元测试继续观察正式 runtime 行为。
|
||||
- `GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs` 中“Clear 后重新接入 handler”回归已适配 seam 方案:在裸容器 `Clear()` 后显式补回测试基础设施,再验证程序集去重状态重置。
|
||||
- `ai-plan/migration/CQRS_MODULE_SPLIT_PLAN.md` 已新增,完成 Phase 5 的模块边界评估、依赖倒置方案与包拆分草案输出。
|
||||
- Phase 5 结论已收敛为“两阶段拆分”:
|
||||
- 先做 `Core -> runtime abstraction` 的 seam 改造
|
||||
- 再拆 `GFramework.Cqrs.Abstractions` / `GFramework.Cqrs` 项目与 public runtime 类型归属
|
||||
- 已确认当前真正阻塞 CQRS 拆分的硬耦合点主要是:
|
||||
- `ArchitectureContext` 直接实例化 `CqrsDispatcher`
|
||||
- `MicrosoftDiContainer` 直接调用 `CqrsHandlerRegistrar`
|
||||
- `ArchitectureBootstrapper` 在默认启动路径内建 CQRS handler 注册假设
|
||||
- 已确认当前消费端兼容面的主要压力来自:
|
||||
- `CoreGrid-Migration/scripts/cqrs/**` 对 `CommandBase`、`Abstract*Handler`、`GFramework.Core.Cqrs.Extensions` 的直接依赖
|
||||
- `GFramework.Godot/Coroutine/ContextAwareCoroutineExtensions.cs` 对 `GFramework.Core.Cqrs.Extensions` 的直接依赖
|
||||
- `CoreGrid-Migration` 已直连本地 `GFramework` 源码与本地 source generators。
|
||||
- `GameArchitecture` 已不再依赖 `collection.AddMediator(...)` 即可使用 CQRS。
|
||||
- `GFramework.Core.Abstractions` 与 `GFramework.Core.Tests` 已移除 `Mediator.Abstractions` /
|
||||
`Mediator.SourceGenerator` 包引用;`IServiceCollection` / `IServiceScope` 所需依赖改为显式引用
|
||||
`Microsoft.Extensions.DependencyInjection.Abstractions`。
|
||||
- `GFramework.Core.Abstractions` 新增自有 CQRS 契约:
|
||||
- `IRequest<TResponse>` / `INotification` / `IStreamRequest<TResponse>`
|
||||
- `IRequestHandler<,>` / `INotificationHandler<>` / `IStreamRequestHandler<,>`
|
||||
- `Unit`
|
||||
- `IPipelineBehavior<,>` / `MessageHandlerDelegate<,>`
|
||||
- `ArchitectureBootstrapper` 会在初始化阶段自动扫描并注册当前架构程序集与 `GFramework.Core` 程序集中的 CQRS handlers。
|
||||
- `IArchitecture`、`IIocContainer`、`Architecture`、`ArchitectureModules` 与 `MicrosoftDiContainer`
|
||||
已新增 `RegisterCqrsHandlersFromAssembly(...)` / `RegisterCqrsHandlersFromAssemblies(...)`,
|
||||
允许模块程序集或扩展包程序集在初始化阶段显式接入与默认路径相同的“生成注册器优先 + 反射回退”注册链路。
|
||||
- `MicrosoftDiContainer` 已把默认启动路径与显式扩展路径统一到同一个 CQRS handler 注册入口,
|
||||
并按稳定程序集键去重,避免默认扫描与后续模块接入重复写入同一程序集的 handler 映射。
|
||||
- `IArchitectureContext`、`QueryBase`、`Abstract*Handler` 以及 `MessageHandlerDelegate` 的 XML 文档已补齐迁移后的契约边界,明确旧
|
||||
Command/Query 总线与新 CQRS runtime 的推荐入口。
|
||||
- `CqrsDispatcher` 已支持:
|
||||
- request dispatch
|
||||
- notification publish
|
||||
- stream dispatch
|
||||
- context-aware handler 注入
|
||||
- request pipeline behavior 链式执行
|
||||
- `CqrsHandlerRegistrar` 已补齐三项运行时硬化:
|
||||
- 按程序集名、处理器类型名与处理器接口名稳定排序,避免通知处理顺序随反射枚举漂移
|
||||
- 在 `ReflectionTypeLoadException` 场景下保留可加载处理器并记录告警
|
||||
- 自动扫描到的 request / notification / stream handler 统一改为 transient,避免上下文感知处理器在并发分发时共享可变状态
|
||||
- `GFramework.Core.Tests` 中原依赖 `Mediator` 注册路径的测试已切换到框架内建 CQRS 注册路径;`CqrsTestRuntime` 现仅保留
|
||||
handler 注册职责,行为测试改为走 `ArchitectureContext.SendRequestAsync(...)` 正式入口。
|
||||
- `AbstractStreamCommandHandler<TCommand, TResponse>` 已把上下文注入窗口、瞬态实例复用约束和流创建/枚举取消边界写入显式
|
||||
`<remarks>`,避免公共流式命令处理器基类的生命周期约束只停留在摘要里。
|
||||
- `CqrsHandlerRegistrarTests` 已改为通过 `CqrsTestRuntime.RegisterHandlers(...)` 真实入口验证部分加载失败恢复路径,并同时断言
|
||||
“剩余 handler 仍被注册 + warning 已记录”。
|
||||
- `CqrsTestRuntime` 已补齐 XML 文档,并改为按 `IIocContainer + IEnumerable<Assembly> + ILogger` 精确绑定
|
||||
`RegisterHandlers(...)`,避免未来新增同名重载后出现运行时歧义。
|
||||
- `MediatorCoroutineExtensions` 已改为直接走 `IArchitectureContext.SendAsync(...)` 内建 CQRS 入口,不再从容器解析
|
||||
`IMediator`;该类型名仅作为历史兼容命名保留。
|
||||
- `RegisterCqrsPipelineBehavior<TBehavior>()` 已作为新的公开推荐入口加入 `IArchitecture`、`IIocContainer`、
|
||||
`Architecture` 与 `MicrosoftDiContainer`;旧的 `RegisterMediatorBehavior<TBehavior>()` 改为显式兼容转发。
|
||||
- `ContextAwareCqrsExtensions`、`ContextAwareCqrsCommandExtensions`、`ContextAwareCqrsQueryExtensions` 与
|
||||
`CqrsCoroutineExtensions` 已作为新的中性命名扩展入口加入;旧的 `ContextAwareMediator*Extensions` 与
|
||||
`MediatorCoroutineExtensions` 保留为 `[Obsolete]` 兼容包装层。
|
||||
- `RegisterMediatorBehavior<TBehavior>()`、`ContextAwareMediator*Extensions` 与 `MediatorCoroutineExtensions`
|
||||
已进一步收紧为“正式弃用”状态:
|
||||
- `Obsolete` 提示统一明确迁移目标与 “future major version” 移除预期
|
||||
- `EditorBrowsable(EditorBrowsableState.Never)` 已将这些历史别名从 IntelliSense 主路径隐藏
|
||||
- `GFramework.Core.Tests/Cqrs/MediatorCompatibilityDeprecationTests.cs` 已锁定上述弃用元数据,防止兼容层退回“仅名义弃用”
|
||||
- `GFramework.Cqrs` 已新增 `ICqrsHandlerRegistry` 与 `CqrsHandlerRegistryAttribute`,
|
||||
作为消费端程序集暴露源码生成注册器的正式 runtime 契约。
|
||||
- `GFramework.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs` 已落地 CQRS handler source-generator MVP:
|
||||
- 对当前消费端程序集中的 concrete request / notification / stream handlers 生成单一程序集级注册器
|
||||
- 生成注册顺序与 runtime 反射排序口径保持一致,按实现类型名与处理器接口名稳定排序
|
||||
- 对生成代码无法直接 `typeof(...)` 引用、但仍可按元数据名从当前程序集重新定位的处理器(例如私有嵌套 handler),生成注册器会改走定向反射注册,而不是退回整程序集补扫
|
||||
- `CqrsHandlerRegistrar` 已改为优先查找 `CqrsHandlerRegistryAttribute` 指向的生成注册器;
|
||||
当注册器缺失、元数据损坏或实例化失败时,会记录 warning 并自动回退到原有反射扫描路径。
|
||||
- `GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs` 与
|
||||
`GFramework.Core.Tests/Cqrs/CqrsHandlerRegistrarTests.cs` 已补齐:
|
||||
- 生成器输出快照测试
|
||||
- 私有嵌套 handler 走 generated registry 内部定向反射注册的测试
|
||||
- runtime 优先使用生成注册器的测试
|
||||
- 生成注册器无效时自动回退反射的测试
|
||||
- 新的 `ContextAwareCqrs*` / `CqrsCoroutineExtensions` 位于独立命名空间,避免与旧 `Mediator` 扩展在同一
|
||||
`using` 范围内触发扩展方法解析歧义。
|
||||
- `CoreGrid-Migration` 中命中的旧扩展调用点已切到新的 `ContextAwareCqrs*` 入口,避免新旧扩展同时可见时的重载歧义。
|
||||
- `docs/zh-CN/core/cqrs.md`、`docs/zh-CN/core/index.md` 与 `CLAUDE.md` 已从“依赖外部 Mediator”改写为
|
||||
“内建 CQRS runtime + 历史命名兼容”表述。
|
||||
- `docs/zh-CN/core/cqrs.md` 与 `CLAUDE.md` 已补充“如何显式接入非默认程序集的 CQRS handlers”说明,避免文档仍停留在“需要额外接入”
|
||||
但未给出正式入口的状态。
|
||||
- 在验证阶段发现 `CqrsHandlerRegistrar.cs` 缺少 `Microsoft.Extensions.DependencyInjection` 的 `using`,
|
||||
已补齐以恢复 `IServiceCollection` 编译通过。
|
||||
- 当前验证状态:
|
||||
- `dotnet build GFramework/GFramework.sln` 通过
|
||||
- `dotnet test GFramework/GFramework.Core.Tests/GFramework.Core.Tests.csproj --no-build` 通过,`1621` 个测试全部通过
|
||||
- `dotnet build CoreGrid-Migration/CoreGrid.sln` 通过
|
||||
-
|
||||
`dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Core.Tests.Cqrs.CqrsHandlerRegistrarTests|FullyQualifiedName~GFramework.Core.Tests.Architectures.ArchitectureModulesBehaviorTests|FullyQualifiedName~GFramework.Core.Tests.Mediator.MediatorAdvancedFeaturesTests|FullyQualifiedName~GFramework.Core.Tests.Mediator.MediatorArchitectureIntegrationTests|FullyQualifiedName~GFramework.Core.Tests.Mediator.MediatorComprehensiveTests"`
|
||||
通过,`49` 个测试全部通过
|
||||
-
|
||||
`dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Core.Tests.Cqrs.CqrsHandlerRegistrarTests|FullyQualifiedName~GFramework.Core.Tests.Mediator.MediatorComprehensiveTests|FullyQualifiedName~GFramework.Core.Tests.Mediator.MediatorArchitectureIntegrationTests|FullyQualifiedName~GFramework.Core.Tests.Mediator.MediatorAdvancedFeaturesTests"`
|
||||
通过,`47` 个测试全部通过
|
||||
- `dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release` 通过
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-build` 通过,`1624` 个测试全部通过
|
||||
- `dotnet build CoreGrid-Migration/CoreGrid.sln` 通过,仅存在既有 analyzer warnings 与 Godot generator warning
|
||||
- `dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release` 在历史命名中性化后再次通过
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-build` 在历史命名中性化后再次通过,`1624` 个测试全部通过
|
||||
- `dotnet build CoreGrid-Migration/CoreGrid.sln` 在迁移 `ContextAwareCqrs*` 调用点后再次通过,仅存在既有 analyzer warnings
|
||||
- `dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release` 在正式弃用兼容层后通过
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-build --filter "FullyQualifiedName~GFramework.Core.Tests.Cqrs.MediatorCompatibilityDeprecationTests|FullyQualifiedName~GFramework.Core.Tests.Architectures.ArchitectureModulesBehaviorTests|FullyQualifiedName~GFramework.Core.Tests.Coroutine.CqrsCoroutineExtensionsTests"` 通过,`8` 个测试全部通过
|
||||
- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release` 在 CQRS generator MVP 后通过
|
||||
- `dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release` 在 CQRS generator MVP 后通过
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-build --filter "FullyQualifiedName~GFramework.SourceGenerators.Tests.Cqrs.CqrsHandlerRegistryGeneratorTests"` 通过,`2` 个测试全部通过
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-build --filter "FullyQualifiedName~GFramework.Core.Tests.Cqrs.CqrsHandlerRegistrarTests|FullyQualifiedName~GFramework.Core.Tests.Architectures.ArchitectureModulesBehaviorTests|FullyQualifiedName~GFramework.Core.Tests.Mediator.MediatorArchitectureIntegrationTests|FullyQualifiedName~GFramework.Core.Tests.Mediator.MediatorComprehensiveTests"` 通过,`41` 个测试全部通过
|
||||
- `dotnet build GFramework/GFramework.sln -c Release`
|
||||
在当前 WSL 环境下命中既有 `GFramework.csproj` NuGet fallback package folder 配置问题
|
||||
(机器本地路径已省略),
|
||||
与本轮 CQRS 改动无关;`GFramework.Core.Tests` 相关项目构建与回归已通过
|
||||
- `dotnet build GFramework/GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release`
|
||||
在显式额外程序集 CQRS 注册入口落地后通过,仅存在既有 `MA0048` warnings
|
||||
- `dotnet test GFramework/GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-build --filter "FullyQualifiedName~GFramework.Core.Tests.Architectures.ArchitectureAdditionalCqrsHandlersTests|FullyQualifiedName~GFramework.Core.Tests.Architectures.ArchitectureModulesBehaviorTests|FullyQualifiedName~GFramework.Core.Tests.Cqrs.CqrsHandlerRegistrarTests|FullyQualifiedName~GFramework.Core.Tests.Architectures.RegistryInitializationHookBaseTests"`
|
||||
在显式额外程序集 CQRS 注册入口落地后通过,`13` 个测试全部通过
|
||||
- `dotnet build GFramework/GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release`
|
||||
在 runtime seam 落地后通过,`0` warnings / `0` errors
|
||||
- `dotnet test GFramework/GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Core.Tests.Ioc.MicrosoftDiContainerTests|FullyQualifiedName~GFramework.Core.Tests.Cqrs.CqrsHandlerRegistrarTests|FullyQualifiedName~GFramework.Core.Tests.Architectures.ArchitectureModulesBehaviorTests|FullyQualifiedName~GFramework.Core.Tests.Architectures.ArchitectureAdditionalCqrsHandlersTests|FullyQualifiedName~GFramework.Core.Tests.Architectures.RegistryInitializationHookBaseTests|FullyQualifiedName~GFramework.Core.Tests.Mediator.MediatorArchitectureIntegrationTests|FullyQualifiedName~GFramework.Core.Tests.Mediator.MediatorComprehensiveTests|FullyQualifiedName~GFramework.Core.Tests.Mediator.MediatorAdvancedFeaturesTests"`
|
||||
在 runtime seam 落地后通过,`97` 个测试全部通过
|
||||
- `dotnet build GFramework/GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release`
|
||||
在纯 CQRS 契约外提到 `GFramework.Cqrs.Abstractions` 后通过,仅存在既有 `MA0048` warnings(`IStreamCommand` / `IStreamQuery` 文件命名)
|
||||
- `dotnet test GFramework/GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-build --filter "FullyQualifiedName~GFramework.Core.Tests.Ioc.MicrosoftDiContainerTests|FullyQualifiedName~GFramework.Core.Tests.Cqrs.CqrsHandlerRegistrarTests|FullyQualifiedName~GFramework.Core.Tests.Architectures.ArchitectureModulesBehaviorTests|FullyQualifiedName~GFramework.Core.Tests.Architectures.ArchitectureAdditionalCqrsHandlersTests|FullyQualifiedName~GFramework.Core.Tests.Architectures.RegistryInitializationHookBaseTests|FullyQualifiedName~GFramework.Core.Tests.Mediator.MediatorArchitectureIntegrationTests|FullyQualifiedName~GFramework.Core.Tests.Mediator.MediatorComprehensiveTests|FullyQualifiedName~GFramework.Core.Tests.Mediator.MediatorAdvancedFeaturesTests"`
|
||||
在纯 CQRS 契约外提后再次通过,`97` 个测试全部通过
|
||||
- `dotnet build GFramework/GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release`
|
||||
在 CQRS 聚焦测试拆分后通过,`0` warnings / `0` errors
|
||||
- `dotnet test GFramework/GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --no-build`
|
||||
在 CQRS 聚焦测试拆分后通过,`54` 个测试全部通过
|
||||
- `dotnet build GFramework/GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release`
|
||||
在迁出 CQRS 聚焦测试并消除跨项目 fixture 耦合后再次通过,`0` warnings / `0` errors
|
||||
- `dotnet test GFramework/GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-build --filter "FullyQualifiedName~MicrosoftDiContainerTests|FullyQualifiedName~ArchitectureAdditionalCqrsHandlersTests|FullyQualifiedName~ArchitectureModulesBehaviorTests|FullyQualifiedName~MediatorCompatibilityDeprecationTests"`
|
||||
在测试拆分后通过,`44` 个测试全部通过
|
||||
|
||||
## 当前已知事实
|
||||
|
||||
- `GFramework` 当前仍同时维护:
|
||||
- 基于 `CommandExecutor` / `QueryExecutor` / `EventBus` 的轻量旧 CQRS
|
||||
- 基于 GFramework 自有抽象的新 CQRS runtime
|
||||
- CQRS runtime 主实现已迁到 `GFramework.Cqrs`,`GFramework.Core` 当前主要保留架构接线、默认 behaviors 与协程扩展;`Abstract*Handler` 基类已物理迁到 `GFramework.Cqrs` 并改为依赖轻量 `CqrsContextAwareHandlerBase`,不再直接继承 `GFramework.Core.Rule.ContextAwareBase`。
|
||||
- Phase 5 评估结论是“拆分收益成立,但必须先做依赖倒置 seam”,该 seam 已在当前恢复点落地完毕,下一步可以正式进入项目骨架拆分。
|
||||
- Phase 7 已验证可以先把“纯 CQRS 契约”独立成 `GFramework.Cqrs.Abstractions`,并把 runtime registry 契约收敛到 `GFramework.Cqrs`;当前真正残留的边界阻塞点主要是 `ICqrsRuntime -> IArchitectureContext`、`CqrsCoroutineExtensions -> TaskCoroutineExtensions` 的耦合,以及是否继续承诺旧 `GFramework.Core.Abstractions.Cqrs*` namespace 兼容。
|
||||
- Phase 7 已额外验证:CQRS 聚焦测试可以先拆到 `GFramework.Cqrs.Tests`,而架构壳层、容器 seam 与兼容层测试继续留在 `GFramework.Core.Tests`,不会破坏当前回归覆盖。
|
||||
- 仍存在 `Mediator` 残留的区域主要集中在:
|
||||
- 兼容 API 与测试目录中的历史命名
|
||||
- 少量本地计划/历史记录文档中的迁移过程描述
|
||||
- `CoreGrid-Migration` 已切到本地源码引用,并在当前恢复点完成构建验证
|
||||
- 本轮已接受的 review / subagent 结论:
|
||||
- `CqrsTestRuntime.ExecutePipelineAsync(...)` 会掩盖正式 dispatcher 行为,已移除
|
||||
- handler 自动注册必须保持稳定顺序、容忍部分类型加载失败,并避免单例上下文污染
|
||||
- `AbstractStreamCommandHandler<TCommand, TResponse>` 的上下文可用窗口、实例复用约束与取消边界需要放入显式
|
||||
`<remarks>`,避免公共基类被误用
|
||||
- `CqrsHandlerRegistrar` 的部分加载失败回归必须从 `RegisterHandlers` 公开测试入口观察,而不是反射私有
|
||||
`RecoverLoadableTypes(...)`
|
||||
- `CqrsTestRuntime` 反射绑定不能只按名称解析 `RegisterHandlers`,否则新增重载后会出现不确定行为
|
||||
- 生产代码与主文档中保留的 `Mediator` 标识目前只剩历史兼容命名,不再代表外部包依赖,可作为下一阶段
|
||||
API 命名统一任务单独处理
|
||||
|
||||
## 当前风险
|
||||
|
||||
- `GFramework` 仓库存在与本任务无关的既有改动,提交时必须避免覆盖
|
||||
- `CoreGrid-Migration` 是 worktree,WSL 下原生 `git` 解析该 worktree 路径有兼容问题
|
||||
- 当前 `RegisterMediatorBehavior`、`MediatorCoroutineExtensions` 等旧名已降级为兼容包装层;若后续要彻底移除历史命名,需要单独规划弃用周期
|
||||
- 当前历史 `Mediator` 兼容入口虽已隐藏 IntelliSense 并明确 future-major 移除,但仍保留公开签名以避免立即破坏旧调用方
|
||||
- 当前 handler 自动注册已具备“默认程序集自动接入 + 额外程序集显式接入”的统一入口,并支持“消费端程序集生成注册器 + 其余程序集反射回退”的双路径;若后续追求更强 AOT/冷启动收益,还需继续减少仍依赖反射回退的程序集范围
|
||||
- 若在依赖倒置 seam 落地前就直接拆项目,`GFramework.Core` 与新 `GFramework.Cqrs` 之间很容易形成“项目名分开、实现仍互相硬引用”的伪边界
|
||||
- 当前 `CoreGrid-Migration` 与 `GFramework.Godot` 均直接依赖 `GFramework.Core.Cqrs.*` public types;若后续拆分时同时改 namespace 或取消 transitive 依赖,会放大消费端迁移成本
|
||||
- 当前 `ICqrsRuntime -> IArchitectureContext` 仍会阻止完整搬空 `GFramework.Core.Abstractions/Cqrs`;若不先收敛这条依赖,后续很容易重新引入项目循环引用
|
||||
- 当前 `Abstract*Handler` 已摆脱 `GFramework.Core.Rule.ContextAwareBase`,但 `CqrsCoroutineExtensions` 仍依赖 `TaskCoroutineExtensions`;若还希望继续压缩 `GFramework.Core` 对 CQRS 的承载范围,下一步仍需处理协程工具链边界
|
||||
- `CoreGrid-Migration` 本轮通过 consumer 侧 namespace/extension 对齐恢复构建;若产品层仍希望承诺旧 `GFramework.Core.Abstractions.Cqrs*` 公共路径,需要单独设计兼容层或迁移策略,否则会在后续真实消费端继续重复暴露同类断点
|
||||
- 当前 `GFramework.Cqrs.Tests` 仍直接引用 `GFramework.Core`,说明测试已经按模块意图拆分,但 runtime 物理迁移尚未完成;若后续直接切断该依赖,测试会失去承载实现
|
||||
- `dotnet build GFramework.sln -c Release` 在当前 WSL 环境仍会受顶层 `GFramework.csproj` 的 Windows NuGet fallback package
|
||||
folder 配置影响,若需要恢复 solution 级全量验证,需先处理该环境问题
|
||||
|
||||
## 下次恢复建议
|
||||
|
||||
若本轮中断,优先从以下顺序恢复:
|
||||
|
||||
1. 查看 `ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md`
|
||||
2. 确认当前恢复点 `CQRS-REWRITE-RP-042` 已对应到最新提交
|
||||
3. 优先继续执行 `ai-plan/migration/CQRS_MODULE_SPLIT_PLAN.md` 中的 Phase 8(当前主线):
|
||||
- 先决定是否正式支持旧 `GFramework.Core.Abstractions.Cqrs*` / `GFramework.Core.Cqrs.Extensions` public namespace 兼容,还是明确要求消费端迁到当前 `GFramework.Cqrs*` 路径
|
||||
- 再评估 `CqrsCoroutineExtensions` 是否保留在 `GFramework.Core`,或连同所需协程辅助一起形成更小的可迁移边界
|
||||
4. 在 runtime 项目真正承接实现后,再处理 source-generator、meta package 与消费端 transitive 依赖的迁移
|
||||
5. 在规划 future major 版本时,再决定何时真正移除 `RegisterMediatorBehavior` / `MediatorCoroutineExtensions` / `ContextAwareMediator*Extensions`
|
||||
|
||||
## 2026-04-15 补充记录(RP-015)
|
||||
|
||||
### 阶段:轻量 handler 上下文基类与消费端兼容性收敛
|
||||
|
||||
- 建立 `CQRS-REWRITE-RP-015` 恢复点
|
||||
- `GFramework.Cqrs/Cqrs/CqrsContextAwareHandlerBase.cs` 已新增,作为仅依赖 `IContextAware` / `IArchitectureContext` 的轻量 CQRS handler 上下文基类:
|
||||
- 保留 `OnContextReady()` 生命周期扩展点
|
||||
- 去掉对 `GameContext` 的兜底依赖
|
||||
- 在运行时注入前访问 `Context` / `GetContext()` 时显式抛出异常,避免静默落回全局上下文
|
||||
- `AbstractCommandHandler` / `AbstractQueryHandler` / `AbstractRequestHandler` / `AbstractNotificationHandler` 及其 stream 变体已物理迁到 `GFramework.Cqrs`,并改为继承该轻量基类
|
||||
- `GFramework.Cqrs.Tests/Cqrs/AbstractCqrsHandlerContextTests.cs` 已新增,回归覆盖:
|
||||
- 未注入上下文时 fail-fast
|
||||
- 注入后 `Context` 可用
|
||||
- `OnContextReady()` 生命周期钩子仍然生效
|
||||
- 在真实迁移验证阶段,`CoreGrid-Migration` 暴露出旧 `GFramework.Core.Abstractions.Cqrs*` 与旧 `GFramework.Core.Cqrs.Extensions` 命名空间不再自动可用的问题
|
||||
- 本轮先采用最小 consumer 修复路径恢复验证链路:
|
||||
- `scripts/GlobalUsings.cs` 已补齐新的 `GFramework.Cqrs.Abstractions.Cqrs*`、`GFramework.Cqrs.*` 与 `GFramework.Core.Cqrs.*` handler namespace 导入
|
||||
- `scripts/cqrs/**` 中显式写死旧 `Unit` / `INotification` / `IQuery` 路径的文件已切到新的 `GFramework.Cqrs.Abstractions.Cqrs*`
|
||||
- `GlobalInputController.cs`、`PauseMenu.cs`、`OptionsMenu.cs` 与两个组合 handler 已改用 `GFramework.Cqrs.Extensions.ContextAwareCqrs*Extensions`
|
||||
|
||||
### 阶段:RP-015 验证
|
||||
|
||||
- `dotnet build CoreGrid-Migration/CoreGrid.sln`
|
||||
- 结果:通过
|
||||
- 备注:仅剩 `CoreGrid-Migration` 既有 analyzer warnings,无新增 CQRS 编译错误
|
||||
|
||||
## 备注
|
||||
|
||||
- 本文档是当前任务的主恢复点,后续每个关键阶段完成后都要更新
|
||||
- 发生方向调整时,不覆盖旧结论,直接追加阶段记录与新的恢复点编号
|
||||
|
||||
## 2026-04-15 补充记录
|
||||
|
||||
### 阶段:review 收尾修正(并发 lazy 初始化与共享测试运行时)
|
||||
|
||||
- `GFramework.Core/Architectures/ArchitectureContext.cs` 已将 `ICqrsRuntime` 的延迟解析从 `??=`
|
||||
改为 `Lazy<ICqrsRuntime>`,并显式使用 `LazyThreadSafetyMode.ExecutionAndPublication`
|
||||
保证并发首次访问时只执行一次容器解析
|
||||
- `ArchitectureContext` 已补齐公开构造函数 XML 文档,以及 `CqrsRuntime` 惰性初始化的并发语义说明
|
||||
- `GFramework.Core.Tests/Architectures/ArchitectureContextTests.cs` 已新增并发回归测试,
|
||||
锁定“多线程首次访问 `SendRequestAsync(...)` 时只解析一次 `ICqrsRuntime`”的行为
|
||||
- 原先位于 `GFramework.Core.Tests/CqrsTestRuntime.cs` 与 `GFramework.Cqrs.Tests/CqrsTestRuntime.cs`
|
||||
的重复实现已提取到共享源码文件 `tests/Shared/CqrsTestRuntime.cs`
|
||||
- `GFramework.Core.Tests` 与 `GFramework.Cqrs.Tests` 已通过链接编译同一份共享源码并补齐 `global using`,
|
||||
避免两个测试项目继续维护分叉的反射绑定逻辑
|
||||
- 共享版 `CqrsTestRuntime` 已顺手清理 `GetType(..., throwOnError: true)! ?? throw ...`
|
||||
里的不可达 `?? throw` 分支
|
||||
|
||||
### 阶段:review 收尾修正验证
|
||||
|
||||
- `dotnet test GFramework/GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsHandlerRegistrarTests|FullyQualifiedName~GFramework.Cqrs.Tests.Mediator.MediatorComprehensiveTests|FullyQualifiedName~GFramework.Cqrs.Tests.Mediator.MediatorArchitectureIntegrationTests|FullyQualifiedName~GFramework.Cqrs.Tests.Mediator.MediatorAdvancedFeaturesTests"`
|
||||
- 结果:通过
|
||||
- 明细:`49` 个测试全部通过
|
||||
- `dotnet test GFramework/GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-restore --filter "FullyQualifiedName~GFramework.Core.Tests.Architectures.ArchitectureContextTests|FullyQualifiedName~GFramework.Core.Tests.Ioc.MicrosoftDiContainerTests"`
|
||||
- 结果:通过
|
||||
- 明细:`57` 个测试全部通过
|
||||
- 首次并行执行两个 `dotnet test` 命令时,`NuGet` restore 在共享 `obj/project.assets.json` 上发生文件竞争;
|
||||
顺序重跑 `GFramework.Core.Tests --no-restore` 后验证通过,属于本地并行 restore 的环境噪音,不是代码回归
|
||||
|
||||
### 阶段:测试公共基础设施模块化
|
||||
|
||||
- 已新增 `GFramework.Tests.Common` 项目,并加入 `GFramework.sln`
|
||||
- 原先临时放在仓库根目录 `tests/Shared/CqrsTestRuntime.cs` 的共享源码已迁入
|
||||
`GFramework.Tests.Common/CqrsTestRuntime.cs`
|
||||
- `GFramework.Core.Tests` 与 `GFramework.Cqrs.Tests` 已从源码链接切换为显式 `ProjectReference`
|
||||
- 两个测试项目的 `global using` 已从 `GFramework.Tests.Shared` 迁到 `GFramework.Tests.Common`
|
||||
- 该调整保留了原有测试调用方式,但把公共测试基础设施收敛到单独模块,避免继续以目录约定模拟模块边界
|
||||
|
||||
### 阶段:测试公共基础设施模块化验证
|
||||
|
||||
- `dotnet test GFramework/GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Core.Tests.Architectures.ArchitectureContextTests|FullyQualifiedName~GFramework.Core.Tests.Ioc.MicrosoftDiContainerTests"`
|
||||
- 结果:通过
|
||||
- 明细:`57` 个测试全部通过
|
||||
- `dotnet test GFramework/GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsHandlerRegistrarTests|FullyQualifiedName~GFramework.Cqrs.Tests.Mediator.MediatorComprehensiveTests|FullyQualifiedName~GFramework.Cqrs.Tests.Mediator.MediatorArchitectureIntegrationTests|FullyQualifiedName~GFramework.Cqrs.Tests.Mediator.MediatorAdvancedFeaturesTests"`
|
||||
- 结果:通过
|
||||
- 明细:`49` 个测试全部通过
|
||||
|
||||
## 2026-04-15 补充记录(RP-016)
|
||||
|
||||
### 阶段:日志入口下沉与剩余 runtime behaviors 迁移
|
||||
|
||||
- 建立 `CQRS-REWRITE-RP-016` 恢复点
|
||||
- `GFramework.Core.Abstractions/Logging/LoggerFactoryResolver.cs` 已新增,继续使用 `GFramework.Core.Logging` 命名空间:
|
||||
- 该类型已从 `GFramework.Core` 实现层下沉到抽象层,允许 `GFramework.Cqrs` 在不反向引用 `GFramework.Core` 的前提下获取日志器
|
||||
- 默认 provider 会优先反射创建 `GFramework.Core.Logging.ConsoleLoggerFactoryProvider`
|
||||
- 当宿主仅加载抽象层时,会退回到静默 provider,避免抽象层默认日志入口因缺少实现程序集而崩溃
|
||||
- `GFramework.Core/Properties/TypeForwarders.cs` 已新增,对 `LoggerFactoryResolver` 做 type forwarding,避免把公开类型迁出 `GFramework.Core` 后留下运行时兼容断点
|
||||
- `GFramework.Core/Cqrs/Behaviors/LoggingBehavior.cs` 与 `PerformanceBehavior.cs` 已物理迁移到 `GFramework.Cqrs/Cqrs/Behaviors/*`
|
||||
- 上述两个 behavior 继续保留 `GFramework.Core.Cqrs.Behaviors` 命名空间,消费端源码无需改 using 即可继续解析
|
||||
- 通过这一步,`GFramework.Core/Cqrs/*` 已完全搬空;Phase 7 的 runtime 物理迁移残项现只剩 `CqrsCoroutineExtensions`
|
||||
- 并行 explorer 结论已收敛:
|
||||
- `ICqrsRuntime` 当前不能迁到 `GFramework.Cqrs.Abstractions`,根因不是单个 using,而是整条 `ICqrsRuntime -> IArchitectureContext -> IContextAware / handler Context` 的上下文模型仍绑定 `GFramework.Core.Abstractions`
|
||||
- `CqrsCoroutineExtensions` 本质上是 `GFramework.Core` 协程桥接层,不是纯 CQRS runtime 代码;若原样迁到 `GFramework.Cqrs`,会重新形成 `GFramework.Cqrs -> GFramework.Core` 反向依赖
|
||||
- 下一阶段的最小可行方案应优先考虑“新增 CQRS 专用上下文 seam + 继续让协程扩展留在 `GFramework.Core`”,而不是先新增组合项目
|
||||
|
||||
### 阶段:RP-016 验证
|
||||
|
||||
- `dotnet build GFramework/GFramework.Core.Abstractions/GFramework.Core.Abstractions.csproj -c Release`
|
||||
- 结果:通过
|
||||
- `dotnet build GFramework/GFramework.Cqrs/GFramework.Cqrs.csproj -c Release`
|
||||
- 结果:通过
|
||||
- `dotnet build GFramework/GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release`
|
||||
- 结果:通过
|
||||
- `dotnet build GFramework/GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release`
|
||||
- 结果:通过
|
||||
- `dotnet test GFramework/GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-build --filter "FullyQualifiedName~GFramework.Core.Tests.Logging.LoggerFactoryTests|FullyQualifiedName~GFramework.Core.Tests.Architectures.ArchitectureModulesBehaviorTests|FullyQualifiedName~GFramework.Core.Tests.Cqrs.MediatorCompatibilityDeprecationTests"`
|
||||
- 结果:通过
|
||||
- 明细:`20` 个测试全部通过
|
||||
- `dotnet test GFramework/GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --no-build --filter "FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsHandlerRegistrarTests|FullyQualifiedName~GFramework.Cqrs.Tests.Coroutine.CqrsCoroutineExtensionsTests|FullyQualifiedName~GFramework.Cqrs.Tests.Mediator.MediatorArchitectureIntegrationTests|FullyQualifiedName~GFramework.Cqrs.Tests.Mediator.MediatorComprehensiveTests|FullyQualifiedName~GFramework.Cqrs.Tests.Mediator.MediatorAdvancedFeaturesTests"`
|
||||
- 结果:通过
|
||||
- 明细:`54` 个测试全部通过
|
||||
|
||||
## 2026-04-16 补充记录(RP-020)
|
||||
|
||||
### 阶段:review 收尾修正(线程安全文档、未冻结态去重与 runtime 契约说明)
|
||||
|
||||
- 建立 `CQRS-REWRITE-RP-020` 恢复点
|
||||
- `GFramework.Cqrs/Internal/DefaultCqrsRegistrationService.cs` 已在类级 `<remarks>` 中明确“该类型不是线程安全的,必须由外部同步边界串行访问”的设计约束
|
||||
- `GFramework.Cqrs.Abstractions/Cqrs/ICqrsRuntime.cs` 已补齐三个公开方法的 XML 契约:
|
||||
- `null` 参数对应 `ArgumentNullException`
|
||||
- handler 缺失或上下文不满足 `IArchitectureContext` 注入前提时对应 `InvalidOperationException`
|
||||
- `CreateStream(...)` 额外说明了上下文需覆盖整个异步枚举生命周期
|
||||
- `GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs` 已补充线性去重扫描的性能特征注释,并记录未来若出现大服务集合热点,可在更高层批处理中引入 `HashSet<(Type, Type)>`
|
||||
- `GFramework.Core/Ioc/MicrosoftDiContainer.cs` 已将未冻结态 `GetAll<T>()` / `GetAll(Type)` 的引用去重逻辑收窄为:
|
||||
- 仍会折叠“不同 `ServiceType` 指向同一实例”的兼容别名重复
|
||||
- 不再吞掉“同一 `ServiceType` 对同一实例的重复显式注册”
|
||||
- 当同一实例同时暴露多个服务类型时,优先保留请求类型对应分组,否则保留注册次数最多且首次出现最早的分组,以维持确定性
|
||||
- `GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs` 已新增两个回归测试,分别锁定泛型与非泛型 `GetAll` 在未冻结态下的上述语义
|
||||
|
||||
### 阶段:RP-020 验证
|
||||
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Core.Tests.Ioc.MicrosoftDiContainerTests"`
|
||||
- 结果:通过
|
||||
- 明细:`40` 个测试全部通过
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-restore --filter "FullyQualifiedName~GFramework.Core.Tests.Ioc.MicrosoftDiContainerTests"`
|
||||
- 结果:通过
|
||||
- 明细:`40` 个测试全部通过
|
||||
|
||||
### 下一步
|
||||
|
||||
1. 继续评估 `CqrsHandlerRegistrar` / generated registry 的命中率提升空间,优先减少需要 `GetTypes()` 全量扫描的回退场景
|
||||
2. 若后续 `MicrosoftDiContainer` 继续保留“未冻结时支持按可赋值类型观察实例”的宽语义,可考虑为 mixed alias + duplicate registration 组合场景再补一条更细的回归测试
|
||||
|
||||
## 2026-04-16 补充记录(RP-033)
|
||||
|
||||
### 阶段:review 小修(缓存清理、诊断信息与测试辅助性能)
|
||||
|
||||
- 建立 `CQRS-REWRITE-RP-033` 恢复点
|
||||
- `GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherCacheTests.cs` 的 `ClearDispatcherCaches()` 已补齐
|
||||
`RequestPipelineInvokerCache<string>.Invokers` 清理,避免未来新增 `string` pipeline 路径测试时残留静态状态
|
||||
- `GFramework.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs` 已在
|
||||
`CanReferenceFromGeneratedRegistry(...)` 的默认分支补充注释,明确当前把 `dynamic`、error type 等其他 Roslyn 类型种类视为暂时可引用的实现假设
|
||||
- `GFramework.SourceGenerators.Tests/Core/MetadataReferenceTestBuilder.cs` 已将运行时可信平台程序集引用收敛为惰性静态缓存,避免多程序集生成器测试反复解析
|
||||
`TRUSTED_PLATFORM_ASSEMBLIES`
|
||||
- `GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs` 已增强 `RunGenerator(...)`
|
||||
中的编译错误断言消息,失败时会输出完整 diagnostics 文本,便于直接定位生成代码问题
|
||||
|
||||
### 阶段:RP-033 验证
|
||||
|
||||
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.SourceGenerators.Tests.Cqrs.CqrsHandlerRegistryGeneratorTests"`
|
||||
- 结果:通过
|
||||
- 明细:`11` 个测试全部通过
|
||||
- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsDispatcherCacheTests"`
|
||||
- 结果:通过
|
||||
- 明细:`2` 个测试全部通过
|
||||
|
||||
## 2026-04-18 补充记录(RP-040)
|
||||
|
||||
### 阶段:第三方参考源码治理
|
||||
|
||||
- 建立 `CQRS-REWRITE-RP-040` 恢复点
|
||||
- `AGENTS.md` 已新增仓库级约束:
|
||||
- `ai-libs/` 用于存放第三方项目源码副本,仅供对照、追踪与设计参考
|
||||
- `ai-libs/**` 默认为只读区域,除非用户明确要求同步或更新第三方快照,否则不允许修改
|
||||
- 后续计划、trace、评审与设计说明引用第三方实现时,优先写明仓库内参考路径,而不是使用模糊的外部项目名
|
||||
- CQRS 迁移主线已将 `Mediator` 的本地参考源正式收口到 `ai-libs/Mediator`
|
||||
- 本跟踪文档中“后续继续参考 Mediator 成熟实现”的执行语义已同步更新为“优先参考 `ai-libs/Mediator`”
|
||||
|
||||
### 阶段:RP-040 验证
|
||||
|
||||
- `dotnet build GFramework.Cqrs/GFramework.Cqrs.csproj -c Release`
|
||||
- 结果:通过
|
||||
- 备注:存在既有 `MA0051` 与 `MA0158` analyzer warnings,无新增构建错误
|
||||
- `rg -n "ai-libs/Mediator|只读|第三方项目源码副本" AGENTS.md ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md`
|
||||
- 结果:通过
|
||||
- 备注:`AGENTS.md`、tracking 与 trace 均已命中新规则和本地参考路径说明
|
||||
|
||||
### 下一步
|
||||
|
||||
1. 后续若继续推进 CQRS runtime、generator 或低反射优化,统一以 `ai-libs/Mediator` 作为本地参考源
|
||||
2. 若未来需要升级 `Mediator` 参考副本,单独作为“同步第三方快照”任务处理,不与框架实现改动混在同一变更里
|
||||
|
||||
## 2026-04-18 补充记录(RP-041)
|
||||
|
||||
### 阶段:source-generator 文档命名空间收口
|
||||
|
||||
- 建立 `CQRS-REWRITE-RP-041` 恢复点
|
||||
- 已将 `docs/zh-CN/**` 中残留的旧示例 `using GFramework.SourceGenerators.Abstractions.*;` 批量改为当前公开命名空间:
|
||||
- `GFramework.Core.SourceGenerators.Abstractions.Rule`
|
||||
- `GFramework.Core.SourceGenerators.Abstractions.Bases`
|
||||
- `GFramework.Core.SourceGenerators.Abstractions.Logging`
|
||||
- `GFramework.Core.SourceGenerators.Abstractions.Enums`
|
||||
- `GFramework.Core.SourceGenerators.Abstractions.Architectures`
|
||||
- 已同步修正文档中的叙述性旧口径:
|
||||
- `docs/zh-CN/source-generators/logging-generator.md` 不再把日志生成器描述成旧聚合 `GFramework.SourceGenerators`
|
||||
- `docs/zh-CN/source-generators/context-get-generator.md` 改为明确由 `GFramework.Core.SourceGenerators` 执行注册可见性分析
|
||||
- `docs/zh-CN/api-reference/index.md` 将“`GFramework.SourceGenerators` 单一模块”改写为当前拆分后的 Source Generators 家族说明
|
||||
|
||||
### 阶段:RP-041 验证
|
||||
|
||||
- `rg -n "using GFramework\\.SourceGenerators\\.Abstractions\\.|### GFramework\\.SourceGenerators|GFramework\\.SourceGenerators 自动生成|GFramework\\.SourceGenerators 现在还会分析" docs/zh-CN`
|
||||
- 结果:通过
|
||||
- 备注:`docs/zh-CN/**` 中上述旧公开命名空间与旧聚合表述已清理完毕
|
||||
|
||||
### 下一步
|
||||
|
||||
1. 若继续文档主线,扩大清理范围到更多说明页中的旧 API 参考与历史命名残留,优先处理 adoption 入口最靠前的页面
|
||||
2. 若切回实现主线,则重新盘点 `GFramework.Cqrs` 中仍值得继续压缩的冷路径/热路径反射点,优先选择能带来明确收益的低复杂度改动
|
||||
|
||||
## 2026-04-19 补充记录(RP-042)
|
||||
|
||||
### 阶段:PR review 技能接入与 PR-253 follow-up
|
||||
|
||||
- 已新增项目级 `$gframework-pr-review` skill:
|
||||
- 目录:`.codex/skills/gframework-pr-review/`
|
||||
- 作用:定位当前分支对应的 GitHub PR,并优先通过 GitHub PR / issue comments / review comments API 提取
|
||||
CodeRabbit 汇总、最新 head commit review threads、`Failed checks` 与 CTRF 测试结果
|
||||
- 约束:不依赖 `gh` CLI;不再把重型 PR HTML 页面当作主数据源
|
||||
- 已根据 PR `#253` 的公开 review 内容完成本地修正:
|
||||
- `.codex/skills/gframework-boot/SKILL.md` 的恢复 heuristics 不再把 `next step/continue/继续`
|
||||
直接映射为 `recovery`
|
||||
- `AGENTS.md` 中 `ai-libs/**` 观察写入 active plan/trace 的规则已收窄到“多步/复杂任务或已有 active
|
||||
tracking document”
|
||||
- `Godot/script_templates/Node/*.cs` 与 `GFramework.Core.Abstractions/Controller/IController.cs`
|
||||
中旧 `Rule` 命名空间残留已同步修正
|
||||
- `fetch_current_pr_review.py` 已改为:
|
||||
- Git 路径支持环境变量覆盖并回退到 `git.exe` / `git`
|
||||
- `--pr` 模式不再强制读取当前分支
|
||||
- `--branch` 到 PR 编号的解析改为走 GitHub PR API
|
||||
- CodeRabbit summary / CTRF 测试报告改为走 issue comments API
|
||||
- 最新 review 依据改为 latest head commit review threads,而不是只看汇总块
|
||||
- `ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md` 已移除公开文档中的机器本地绝对路径,并统一
|
||||
下次恢复建议里的恢复点编号
|
||||
|
||||
### 阶段:RP-042 验证
|
||||
|
||||
- `python3 - <<'PY' ... ast.parse(...) ... PY`
|
||||
- 结果:通过
|
||||
- 备注:`fetch_current_pr_review.py` 语法正确
|
||||
- `python3 .codex/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --pr 253`
|
||||
- 结果:通过
|
||||
- 备注:已通过 API-first 路径解析 PR 元数据、latest head commit review threads、CodeRabbit summary
|
||||
与 CTRF 测试结果,不再依赖 PR HTML
|
||||
- `python3 .codex/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --branch feat/cqrs-optimization`
|
||||
- 结果:通过
|
||||
- 备注:已验证 branch -> PR 解析同样通过 GitHub API 工作
|
||||
- `dotnet build GFramework.Core.Abstractions/GFramework.Core.Abstractions.csproj -c Release`
|
||||
- 结果:通过
|
||||
- 备注:相关 C# 改动已完成构建验证
|
||||
|
||||
### 下一步
|
||||
|
||||
1. 若要让 PR `#253` 上的 latest head review threads 反映本轮本地修正,需要先提交并推送当前分支,再重新执行 `$gframework-pr-review`
|
||||
2. PR 当前公开 warning 仍包含 `Docstring Coverage`,若后续要继续消除此项,需要单独规划并提交文档注释覆盖率改进
|
||||
1444
ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md
Normal file
1444
ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md
Normal file
File diff suppressed because it is too large
Load Diff
@ -434,9 +434,11 @@ Godot 引擎集成模块。
|
||||
|
||||
## 源码生成器
|
||||
|
||||
### GFramework.SourceGenerators
|
||||
### Source Generators 家族
|
||||
|
||||
自动代码生成工具。
|
||||
自动代码生成工具按模块拆分为 `GFramework.Core.SourceGenerators`、`GFramework.Game.SourceGenerators`、
|
||||
`GFramework.Godot.SourceGenerators` 与 `GFramework.Cqrs.SourceGenerators`。面向业务代码声明的 Attribute
|
||||
主要来自 `GFramework.Core.SourceGenerators.Abstractions.*` 与对应模块的 runtime/generator 包。
|
||||
|
||||
#### 支持的生成器
|
||||
|
||||
|
||||
@ -657,7 +657,7 @@ public partial class UIController : IController
|
||||
### 事件组合
|
||||
|
||||
```csharp
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
// 使用 OrEvent 组合多个事件
|
||||
[ContextAware]
|
||||
|
||||
@ -33,7 +33,7 @@ public class CombatSystem : AbstractSystem
|
||||
}
|
||||
|
||||
using GFramework.Core.Abstractions.Controller;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
public partial class PlayerController : IController
|
||||
@ -182,7 +182,7 @@ public class StorageUtility : IUtility { }
|
||||
|
||||
```csharp
|
||||
using GFramework.Core.Abstractions.Controller;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
public partial class MyController : IController
|
||||
|
||||
@ -278,7 +278,7 @@ public class PoolMonitorSystem : AbstractSystem
|
||||
|
||||
```csharp
|
||||
using GFramework.Core.Abstractions.Controller;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
// ✅ 好的做法:正确管理事件订阅
|
||||
[ContextAware]
|
||||
|
||||
@ -57,7 +57,7 @@ public class SimpleCommand : AbstractCommand
|
||||
|
||||
// 使用命令
|
||||
using GFramework.Core.Abstractions.Controller;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
public partial class GameController : IController
|
||||
@ -221,7 +221,7 @@ public class StartGameCommand : AbstractCommand<StartGameInput>
|
||||
|
||||
// 使用命令
|
||||
using GFramework.Core.Abstractions.Controller;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
public partial class GameController : IController
|
||||
|
||||
@ -622,7 +622,7 @@ public class SettingsSystem : AbstractSystem
|
||||
### 在 Controller 中使用
|
||||
|
||||
```csharp
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
public partial class SettingsController : IController
|
||||
|
||||
@ -354,7 +354,7 @@ public class CombatSystem : AbstractSystem
|
||||
|
||||
```csharp
|
||||
using GFramework.Core.Abstractions.Controller;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
public partial class GameController : IController
|
||||
@ -452,7 +452,7 @@ public class EventBridge : AbstractSystem
|
||||
|
||||
```csharp
|
||||
using GFramework.Core.Abstractions.Controller;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
public partial class TutorialController : IController
|
||||
@ -502,7 +502,7 @@ public class AchievementSystem : AbstractSystem
|
||||
|
||||
```csharp
|
||||
using GFramework.Core.Abstractions.Controller;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
public partial class MyController : IController
|
||||
|
||||
@ -109,7 +109,7 @@ event EventHandler<PauseStateChangedEventArgs>? OnPauseStateChanged;
|
||||
### 1. 获取暂停管理器
|
||||
|
||||
```csharp
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
public partial class GameController : IController
|
||||
@ -127,7 +127,7 @@ public partial class GameController : IController
|
||||
### 2. 简单的暂停/恢复
|
||||
|
||||
```csharp
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
public partial class PauseMenuController : IController
|
||||
@ -163,7 +163,7 @@ public partial class PauseMenuController : IController
|
||||
### 3. 使用作用域自动管理
|
||||
|
||||
```csharp
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
public partial class DialogController : IController
|
||||
@ -215,7 +215,7 @@ public class GameplaySystem : AbstractSystem
|
||||
### 1. 嵌套暂停
|
||||
|
||||
```csharp
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
public partial class UIManager : IController
|
||||
@ -254,7 +254,7 @@ public partial class UIManager : IController
|
||||
### 2. 分组暂停
|
||||
|
||||
```csharp
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
public partial class GameManager : IController
|
||||
@ -362,7 +362,7 @@ public class GameInitializer
|
||||
### 4. 监听暂停状态变化
|
||||
|
||||
```csharp
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
public partial class PauseIndicator : IController
|
||||
@ -404,7 +404,7 @@ public partial class PauseIndicator : IController
|
||||
### 5. 调试暂停状态
|
||||
|
||||
```csharp
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
public partial class PauseDebugger : IController
|
||||
@ -441,7 +441,7 @@ public partial class PauseDebugger : IController
|
||||
### 6. 紧急恢复
|
||||
|
||||
```csharp
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
public partial class EmergencyController : IController
|
||||
@ -682,7 +682,7 @@ public class ThreadSafeUsage
|
||||
在组件销毁时注销处理器和事件:
|
||||
|
||||
```csharp
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
public partial class ProperCleanup : IController
|
||||
@ -784,7 +784,7 @@ public class SelectiveSystem : AbstractSystem
|
||||
A: 暂停系统控制是否执行,时间缩放需要使用时间系统:
|
||||
|
||||
```csharp
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
public partial class SlowMotionController : IController
|
||||
@ -829,7 +829,7 @@ _pauseManager.Push("AI 系统", PauseGroup.Custom3);
|
||||
A: 使用 `PauseScope` 配合 `async/await`:
|
||||
|
||||
```csharp
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
public partial class AsyncPauseExample : IController
|
||||
|
||||
@ -240,7 +240,7 @@ public class PlayerModel : AbstractModel
|
||||
|
||||
```csharp
|
||||
using GFramework.Core.Abstractions.Controller;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
public partial class PlayerUI : Control, IController
|
||||
@ -384,7 +384,7 @@ public class PlayerModel : AbstractModel
|
||||
|
||||
```c#
|
||||
using GFramework.Core.Abstractions.Controller;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
public partial class CombatController : Node, IController
|
||||
|
||||
@ -145,7 +145,7 @@ public class LoadPlayerDataQuery : AbstractAsyncQuery<LoadPlayerDataInput, Playe
|
||||
|
||||
```csharp
|
||||
using GFramework.Core.Abstractions.Controller;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
public partial class ShopUI : IController
|
||||
|
||||
@ -129,7 +129,7 @@ public class GameArchitecture : Architecture
|
||||
|
||||
```csharp
|
||||
using GFramework.Core.Abstractions.Controller;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
public partial class GameController : IController
|
||||
@ -221,7 +221,7 @@ public class LoadingState : AsyncContextAwareStateBase
|
||||
|
||||
```csharp
|
||||
using GFramework.Core.Abstractions.Controller;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
public partial class GameController : IController
|
||||
|
||||
@ -390,7 +390,7 @@ using GFramework.Core.Abstractions.Controller;
|
||||
using GFramework.Core.Abstractions.Events;
|
||||
using GFramework.Core.Events;
|
||||
using GFramework.Core.Extensions;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
public partial class PlayerPanelController : IController
|
||||
|
||||
@ -186,7 +186,7 @@ public class GameArchitecture : Architecture
|
||||
```csharp
|
||||
// 在 Controller 中
|
||||
using GFramework.Core.Abstractions.Controller;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
public partial class GameController : IController
|
||||
|
||||
@ -164,7 +164,7 @@ public class GameArchitecture : Architecture
|
||||
```csharp
|
||||
using Arch.Core;
|
||||
using GFramework.Core.Abstractions.Rule;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
using MyGame.Components;
|
||||
|
||||
[ContextAware]
|
||||
@ -508,7 +508,7 @@ World.Query(in query, (Entity entity, ref Position pos) =>
|
||||
|
||||
```csharp
|
||||
using GFramework.Core.Abstractions.bases;
|
||||
using GFramework.SourceGenerators.Abstractions.bases;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Bases;
|
||||
|
||||
// 使用 Priority 特性设置优先级
|
||||
[Priority(10)] // 高优先级,先执行
|
||||
|
||||
@ -131,7 +131,7 @@ public class SaveData : IVersionedData
|
||||
|
||||
```csharp
|
||||
using GFramework.Core.Abstractions.Controller;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
public partial class SaveController : IController
|
||||
@ -238,7 +238,7 @@ public async Task ShowSaveList()
|
||||
|
||||
```csharp
|
||||
using GFramework.Core.Abstractions.Controller;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
public partial class AutoSaveController : IController
|
||||
@ -367,7 +367,7 @@ public async Task<SaveData> LoadGame(int slot)
|
||||
|
||||
```csharp
|
||||
using GFramework.Core.Abstractions.Controller;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
public partial class SettingsController : IController
|
||||
|
||||
@ -165,7 +165,7 @@ public class GameSceneRegistry : IGameSceneRegistry
|
||||
|
||||
```csharp
|
||||
using GFramework.Core.Abstractions.Controller;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
public partial class GameController : IController
|
||||
@ -353,7 +353,7 @@ sceneRouter.AddTransitionHandler(new FadeTransitionHandler());
|
||||
|
||||
```csharp
|
||||
using GFramework.Core.Abstractions.Controller;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
public partial class SceneNavigationController : IController
|
||||
@ -437,7 +437,7 @@ public class GameplayScene : IScene
|
||||
|
||||
```csharp
|
||||
using GFramework.Core.Abstractions.Controller;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
public partial class PreloadController : IController
|
||||
|
||||
@ -94,7 +94,7 @@ public class GameArchitecture : Architecture
|
||||
使用泛型 API 序列化对象:
|
||||
|
||||
```csharp
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
public class PlayerData
|
||||
{
|
||||
@ -175,7 +175,7 @@ public void SerializeRuntimeType()
|
||||
```csharp
|
||||
using GFramework.Core.Abstractions.Storage;
|
||||
using GFramework.Game.Storage;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
public partial class DataManager : IController
|
||||
|
||||
@ -127,7 +127,7 @@ public class MainMenuPage : IUiPage
|
||||
|
||||
```csharp
|
||||
using GFramework.Core.Abstractions.Controller;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
public partial class UiController : IController
|
||||
@ -305,7 +305,7 @@ uiRouter.RegisterHandler(new FadeTransitionHandler());
|
||||
|
||||
```csharp
|
||||
using GFramework.Core.Abstractions.Controller;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
public partial class DialogController : IController
|
||||
@ -338,7 +338,7 @@ public partial class DialogController : IController
|
||||
|
||||
```csharp
|
||||
using GFramework.Core.Abstractions.Controller;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
public partial class NavigationController : IController
|
||||
@ -374,7 +374,7 @@ public partial class NavigationController : IController
|
||||
|
||||
```csharp
|
||||
using GFramework.Core.Abstractions.Controller;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
public partial class LayerController : IController
|
||||
|
||||
@ -341,7 +341,7 @@ public class GameArchitecture : AbstractArchitecture
|
||||
```csharp
|
||||
using Godot;
|
||||
using GFramework.Core.Abstractions.Controller;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
public partial class Player : CharacterBody2D, IController
|
||||
@ -519,7 +519,7 @@ public partial class GameRoot : Node
|
||||
使用 `[ContextAware]` 特性或直接使用单例:
|
||||
|
||||
```csharp
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
// 方式 1: 使用 [ContextAware] 特性(推荐)
|
||||
[ContextAware]
|
||||
|
||||
@ -29,7 +29,7 @@ using GFramework.Core.Abstractions.Architectures;
|
||||
using GFramework.Core.Abstractions.Model;
|
||||
using GFramework.Core.Abstractions.Systems;
|
||||
using GFramework.Core.Abstractions.Utility;
|
||||
using GFramework.SourceGenerators.Abstractions.Architectures;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Architectures;
|
||||
|
||||
public sealed class RunStateModel : IModel
|
||||
{
|
||||
|
||||
@ -126,7 +126,7 @@ partial class MainMenu
|
||||
```csharp
|
||||
using GFramework.Godot.SourceGenerators.Abstractions.UI;
|
||||
using GFramework.Game.Abstractions.Enums;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
using Godot;
|
||||
|
||||
[ContextAware]
|
||||
|
||||
@ -640,7 +640,7 @@ private void OnQuitButtonPressed() { }
|
||||
在需要架构访问的场景中,与 `[ContextAware]` 结合:
|
||||
|
||||
```csharp
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Godot.SourceGenerators.Abstractions;
|
||||
|
||||
[ContextAware]
|
||||
|
||||
@ -21,7 +21,7 @@ ContextAware 生成器为标记了 `[ContextAware]` 属性的类自动生成 `IC
|
||||
使用 `[ContextAware]` 属性标记需要访问架构上下文的类:
|
||||
|
||||
```csharp
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.Abstractions.Controller;
|
||||
|
||||
[ContextAware]
|
||||
@ -233,7 +233,7 @@ public partial class GameFlowController : IController
|
||||
|
||||
```csharp
|
||||
using GFramework.Core.Abstractions.Controller;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
public partial class PlayerController : Node, IController
|
||||
|
||||
@ -45,7 +45,7 @@ Context Get 注入依赖 `[ContextAware]` 特性提供的上下文访问能力
|
||||
使用 `[GetModel]`、`[GetSystem]`、`[GetUtility]` 和 `[GetService]` 注入单个实例:
|
||||
|
||||
```csharp
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.Abstractions.Model;
|
||||
using GFramework.Core.Abstractions.Systems;
|
||||
using GFramework.Core.Abstractions.Utility;
|
||||
@ -80,7 +80,7 @@ public partial class PlayerController
|
||||
使用 `[GetModels]`、`[GetSystems]`、`[GetUtilities]` 和 `[GetServices]` 注入多个实例:
|
||||
|
||||
```csharp
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
public partial class StrategyManager
|
||||
@ -108,7 +108,7 @@ public partial class StrategyManager
|
||||
`[GetAll]` 特性标记在类上,自动推断所有符合类型的字段并注入:
|
||||
|
||||
```csharp
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
[GetAll]
|
||||
@ -280,7 +280,7 @@ public partial class GameNode : Node
|
||||
|
||||
```csharp
|
||||
using GFramework.Core.Abstractions.Controller;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
[GetAll]
|
||||
@ -328,7 +328,7 @@ public partial class StrategyManager
|
||||
|
||||
```csharp
|
||||
using Godot;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[ContextAware]
|
||||
[GetAll]
|
||||
@ -623,7 +623,7 @@ public partial class GameController
|
||||
|
||||
## 注册可见性分析
|
||||
|
||||
除了生成注入方法,`GFramework.SourceGenerators` 现在还会分析 `Model`、`System`、`Utility` 的使用点是否存在静态可见注册。
|
||||
除了生成注入方法,`GFramework.Core.SourceGenerators` 现在还会分析 `Model`、`System`、`Utility` 的使用点是否存在静态可见注册。
|
||||
|
||||
当前支持的使用点:
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
## 基础使用
|
||||
|
||||
```csharp
|
||||
using GFramework.SourceGenerators.Abstractions.Enums;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Enums;
|
||||
|
||||
[GenerateEnumExtensions]
|
||||
public enum GameState
|
||||
|
||||
@ -172,7 +172,7 @@ public partial class Example : Control
|
||||
|
||||
```csharp
|
||||
using GFramework.Godot.SourceGenerators.Abstractions;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
using Godot;
|
||||
|
||||
[ContextAware]
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# 日志生成器
|
||||
|
||||
> GFramework.SourceGenerators 自动生成日志代码,减少样板代码
|
||||
> GFramework.Core.SourceGenerators 自动生成日志代码,减少样板代码
|
||||
|
||||
## 概述
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
### 标记类
|
||||
|
||||
```csharp
|
||||
using GFramework.SourceGenerators.Abstractions.Logging;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Logging;
|
||||
|
||||
[Log]
|
||||
public partial class MyService
|
||||
@ -165,8 +165,8 @@ public partial class MySystem : AbstractSystem
|
||||
|
||||
```csharp
|
||||
using GFramework.Core.Abstractions.Controller;
|
||||
using GFramework.SourceGenerators.Abstractions.Logging;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Logging;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[Log]
|
||||
[ContextAware]
|
||||
|
||||
@ -27,7 +27,7 @@ Priority 生成器通过源代码生成器自动实现 `IPrioritized` 接口,
|
||||
使用 `[Priority]` 特性为类标记优先级:
|
||||
|
||||
```csharp
|
||||
using GFramework.SourceGenerators.Abstractions.Bases;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Bases;
|
||||
|
||||
[Priority(10)]
|
||||
public partial class MySystem
|
||||
@ -91,7 +91,7 @@ foreach (var system in sorted)
|
||||
|
||||
```csharp
|
||||
using GFramework.Core.Abstractions.Bases;
|
||||
using GFramework.SourceGenerators.Abstractions.Bases;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Bases;
|
||||
|
||||
[Priority(PriorityGroup.Critical)] // -100
|
||||
public partial class InputSystem : AbstractSystem { }
|
||||
@ -132,7 +132,7 @@ public static class PriorityGroup
|
||||
|
||||
```csharp
|
||||
using GFramework.Core.Abstractions.Systems;
|
||||
using GFramework.SourceGenerators.Abstractions.Bases;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Bases;
|
||||
using GFramework.Core.Abstractions.Bases;
|
||||
|
||||
// 输入系统最先初始化
|
||||
@ -550,9 +550,9 @@ public partial class GenericSystem<T> : ISystem
|
||||
Priority 可以与其他源代码生成器特性组合使用:
|
||||
|
||||
```csharp
|
||||
using GFramework.SourceGenerators.Abstractions.Bases;
|
||||
using GFramework.SourceGenerators.Abstractions.Logging;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Bases;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Logging;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
[Priority(PriorityGroup.High)]
|
||||
[Log]
|
||||
@ -662,7 +662,7 @@ public class ManualPrioritySystem : IPrioritized
|
||||
|
||||
```csharp
|
||||
using GFramework.Core.Abstractions.Systems;
|
||||
using GFramework.SourceGenerators.Abstractions.Bases;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Bases;
|
||||
using GFramework.Core.Abstractions.Bases;
|
||||
|
||||
// 输入系统(最先初始化)
|
||||
|
||||
@ -187,7 +187,7 @@ architecture.RegisterModel<ICounterModel>(new CounterModel());
|
||||
```csharp
|
||||
using GFramework.Core.Abstractions.Controller;
|
||||
using GFramework.Core.Extensions;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
using Godot;
|
||||
using MyGFrameworkGame.scripts.Model;
|
||||
|
||||
@ -219,7 +219,7 @@ public partial class App : Control, IController // ← 实现 IController 接
|
||||
```csharp
|
||||
using GFramework.Core.Abstractions.Controller;
|
||||
using GFramework.Core.Extensions;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
using Godot;
|
||||
using MyGFrameworkGame.scripts.Model;
|
||||
|
||||
|
||||
@ -174,7 +174,7 @@ public class DecreaseCountCommand : AbstractCommand
|
||||
```csharp
|
||||
using GFramework.Core.Abstractions.Controller;
|
||||
using GFramework.Core.Extensions;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
using Godot;
|
||||
using MyGFrameworkGame.scripts.Command;
|
||||
using MyGFrameworkGame.scripts.Model;
|
||||
|
||||
@ -474,7 +474,7 @@ namespace MyShooterGame.Systems
|
||||
// Scripts/Controllers/PlayerController.cs
|
||||
using GFramework.Core.Abstractions.Controller;
|
||||
using GFramework.Core.Extensions;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
using MyShooterGame.Architecture;
|
||||
using MyShooterGame.Models;
|
||||
using Godot;
|
||||
@ -630,7 +630,7 @@ public partial class Main : Node
|
||||
using Godot;
|
||||
using GFramework.Core.Abstractions.Controller;
|
||||
using GFramework.Core.Extensions;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
using MyShooterGame.Architecture;
|
||||
using MyShooterGame.Systems;
|
||||
|
||||
@ -673,7 +673,7 @@ using Godot;
|
||||
using GFramework.Core.Abstractions.Controller;
|
||||
using GFramework.Core.Abstractions.Architecture;
|
||||
using GFramework.Core.Extensions;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
using MyShooterGame.Architecture;
|
||||
using MyShooterGame.Systems;
|
||||
using MyShooterGame.Models;
|
||||
|
||||
@ -324,7 +324,7 @@ namespace MyGame.Systems
|
||||
```csharp
|
||||
using GFramework.Core.Abstractions.Pause;
|
||||
using GFramework.Core.Abstractions.Controller;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
namespace MyGame.Controllers
|
||||
{
|
||||
@ -664,7 +664,7 @@ namespace MyGame
|
||||
```csharp
|
||||
using GFramework.Core.Abstractions.Pause;
|
||||
using GFramework.Core.Abstractions.Controller;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MyGame.Controllers
|
||||
|
||||
@ -398,7 +398,7 @@ using GFramework.Core.Abstractions.State;
|
||||
using GFramework.Core.Extensions;
|
||||
using MyGame.States;
|
||||
using System.Threading.Tasks;
|
||||
using GFramework.SourceGenerators.Abstractions.Rule;
|
||||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||||
|
||||
namespace MyGame.Controllers
|
||||
{
|
||||
|
||||
21
third-party-licenses/Mediator/LICENSE
Normal file
21
third-party-licenses/Mediator/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Martin Othamar
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
Loading…
x
Reference in New Issue
Block a user