diff --git a/.codex b/.codex deleted file mode 100644 index e69de29b..00000000 diff --git a/.codex/skills/gframework-boot/SKILL.md b/.codex/skills/gframework-boot/SKILL.md new file mode 100644 index 00000000..a1c5eea5 --- /dev/null +++ b/.codex/skills/gframework-boot/SKILL.md @@ -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//todos/` and `ai-plan/public//traces/` across active + topics, and ignore `ai-plan/public/archive/` unless the user explicitly asks for historical context. +6. If `ai-plan/private//` 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//todos/` with completed work, validation results, + risks, and the next recovery point. +- Update the matching public trace under `ai-plan/public//traces/` with key decisions, delegated scope, and the + immediate next step. +- Move stage-complete artifacts into `ai-plan/public//archive/`, and move completed topics into + `ai-plan/public/archive//` 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. diff --git a/.codex/skills/gframework-boot/agents/openai.yaml b/.codex/skills/gframework-boot/agents/openai.yaml new file mode 100644 index 00000000..1196a926 --- /dev/null +++ b/.codex/skills/gframework-boot/agents/openai.yaml @@ -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." diff --git a/.codex/skills/gframework-boot/references/startup-artifacts.md b/.codex/skills/gframework-boot/references/startup-artifacts.md new file mode 100644 index 00000000..2ffb67d8 --- /dev/null +++ b/.codex/skills/gframework-boot/references/startup-artifacts.md @@ -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//todos/` directories +- the selected `ai-plan/public//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//` 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. I will and start with .` diff --git a/.codex/skills/gframework-pr-review/SKILL.md b/.codex/skills/gframework-pr-review/SKILL.md new file mode 100644 index 00000000..d9805f17 --- /dev/null +++ b/.codex/skills/gframework-pr-review/SKILL.md @@ -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` diff --git a/.codex/skills/gframework-pr-review/agents/openai.yaml b/.codex/skills/gframework-pr-review/agents/openai.yaml new file mode 100644 index 00000000..e82522e7 --- /dev/null +++ b/.codex/skills/gframework-pr-review/agents/openai.yaml @@ -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." diff --git a/.codex/skills/gframework-pr-review/scripts/fetch_current_pr_review.py b/.codex/skills/gframework-pr-review/scripts/fetch_current_pr_review.py new file mode 100644 index 00000000..51f42a17 --- /dev/null +++ b/.codex/skills/gframework-pr-review/scripts/fetch_current_pr_review.py @@ -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 = "" +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", + ["
\n✅ Passed checks", "", ""], + ) + 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( + "
\n🤖 Prompt for all review comments with AI agents", + 1, + )[0] + pattern = re.compile( + r"" + r"((?:[^<\n]+/)*[^<\n]+\.(?:cs|md|csproj|yaml|yml|json|txt|props|targets)|AGENTS\.md|CLAUDE\.md|README\.md|\.gitignore)" + r" \((\d+)\)
\s*(.*?)\s*(?:(?:
)|(?:))", + re.S, + ) + + for path, _, body in pattern.findall(primary_block): + finding_match = re.search(r"`([^`]+)`: \*\*(.*?)\*\*", body, re.S) + prompt_match = re.search(r"🤖 Prompt for AI Agents\s*```(.*?)```", body, re.S) + suggestion_match = re.search(r"✏️ 建议文案调整\s*```diff(.*?)```", body, re.S) + + body_without_details = body.split("
", 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"🤖 Prompt for all review comments with AI agents\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", "", "[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) diff --git a/.gitignore b/.gitignore index d6abff17..5f35f8bd 100644 --- a/.gitignore +++ b/.gitignore @@ -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/ diff --git a/AGENTS.md b/AGENTS.md index 9cf4ec07..058e2d2a 100644 --- a/AGENTS.md +++ b/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 `/`, where `` 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//todos/`: repository-safe recovery documents for an active topic + - `ai-plan/public//traces/`: repository-safe execution traces for an active topic + - `ai-plan/public//archive/`: archived stage-level artifacts that still belong to an active topic + - `ai-plan/public/archive//`: 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//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//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//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//` 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 diff --git a/GFramework.Core.Abstractions/Controller/IController.cs b/GFramework.Core.Abstractions/Controller/IController.cs index 1208f72a..4aee0b7c 100644 --- a/GFramework.Core.Abstractions/Controller/IController.cs +++ b/GFramework.Core.Abstractions/Controller/IController.cs @@ -13,7 +13,7 @@ /// 自动生成上下文访问能力: /// /// -/// using GFramework.SourceGenerators.Abstractions.Rule; +/// using GFramework.Core.SourceGenerators.Abstractions.Rule; /// /// [ContextAware] /// public partial class PlayerController : IController @@ -35,4 +35,4 @@ /// 可使用 this.GetModel()、this.GetSystem() 等扩展方法访问架构 /// /// -public interface IController; \ No newline at end of file +public interface IController; diff --git a/GFramework.csproj b/GFramework.csproj index 33baacae..e4af07c1 100644 --- a/GFramework.csproj +++ b/GFramework.csproj @@ -70,16 +70,17 @@ + - - + + - - - + + + GFramework.Core.SourceGenerators\enums\EnumExtensionsGenerator.cs @@ -89,69 +90,71 @@ GFramework.Core.SourceGenerators\logging\LoggerGenerator.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Godot/script_templates/Node/ControllerTemplate.cs b/Godot/script_templates/Node/ControllerTemplate.cs index 5931e7ff..ff4875a8 100644 --- a/Godot/script_templates/Node/ControllerTemplate.cs +++ b/Godot/script_templates/Node/ControllerTemplate.cs @@ -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 { } } - diff --git a/Godot/script_templates/Node/PageControllerTemplate.cs b/Godot/script_templates/Node/PageControllerTemplate.cs index 24601e21..d30623c1 100644 --- a/Godot/script_templates/Node/PageControllerTemplate.cs +++ b/Godot/script_templates/Node/PageControllerTemplate.cs @@ -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] diff --git a/README.md b/README.md index 5e7ba73e..2c4f8634 100644 --- a/README.md +++ b/README.md @@ -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 ## 许可证 diff --git a/ai-plan/README.md b/ai-plan/README.md new file mode 100644 index 00000000..5fcab16b --- /dev/null +++ b/ai-plan/README.md @@ -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//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//traces/` + - Repository-safe execution traces for one active topic. + - Record decisions, validation milestones, and the immediate next step. +- `public//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//` + - 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//` 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//archive/`. +- When a topic is complete, move the whole topic directory into `public/archive//` 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/` diff --git a/ai-plan/public/README.md b/ai-plan/public/README.md new file mode 100644 index 00000000..a56d8aa9 --- /dev/null +++ b/ai-plan/public/README.md @@ -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//`. +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. diff --git a/ai-plan/public/ai-plan-governance/todos/ai-plan-governance-tracking.md b/ai-plan/public/ai-plan-governance/todos/ai-plan-governance-tracking.md new file mode 100644 index 00000000..82d4949b --- /dev/null +++ b/ai-plan/public/ai-plan-governance/todos/ai-plan-governance-tracking.md @@ -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//todos/` 与 `ai-plan/public//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/|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/`,统一按“公共索引 + 活动主题 + 归档主题 + 私有目录”扩展,不再恢复平铺结构 diff --git a/ai-plan/public/ai-plan-governance/traces/ai-plan-governance-trace.md b/ai-plan/public/ai-plan-governance/traces/ai-plan-governance-trace.md new file mode 100644 index 00000000..b9f6ef08 --- /dev/null +++ b/ai-plan/public/ai-plan-governance/traces/ai-plan-governance-trace.md @@ -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//` 下创建,不再向共享目录追加本地临时状态 +2. 若将来需要进一步限制格式,可再为 `public/**` 与 `private/` 各自补一个模板文件,但本轮先把目录语义和安全边界固定下来 + +### 阶段:主题分组与启动索引(RP-003) + +- 建立 `AI-PLAN-GOV-RP-003` 恢复点 +- 用户进一步指出:即使 public/private 已分层,只要多 worktree 并行,扁平的活动主题集合仍会让 `boot` 随着 + `ai-plan/public` 增长而退化成大范围扫描 +- 已据此完成第二轮治理: + - 将活动共享文档迁移到 `ai-plan/public//todos/` 与 `ai-plan/public//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/|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/` diff --git a/ai-plan/public/archive/cqrs-cache-docs-hardening/todos/cqrs-cache-docs-hardening-tracking.md b/ai-plan/public/archive/cqrs-cache-docs-hardening/todos/cqrs-cache-docs-hardening-tracking.md new file mode 100644 index 00000000..85c9b8a9 --- /dev/null +++ b/ai-plan/public/archive/cqrs-cache-docs-hardening/todos/cqrs-cache-docs-hardening-tracking.md @@ -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` / `ConcurrentDictionary` 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.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. diff --git a/ai-plan/public/archive/cqrs-cache-docs-hardening/traces/cqrs-cache-docs-hardening-trace.md b/ai-plan/public/archive/cqrs-cache-docs-hardening/traces/cqrs-cache-docs-hardening-trace.md new file mode 100644 index 00000000..8ad8ed58 --- /dev/null +++ b/ai-plan/public/archive/cqrs-cache-docs-hardening/traces/cqrs-cache-docs-hardening-trace.md @@ -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` / + `ConcurrentDictionary` 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.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.GetOrAdd(...)` overload and updated + `WeakTypePairCache.GetOrAdd(...)` to use it, removing the captured lambda allocation from the secondary-key + lookup path. +- Updated both `WeakKeyCache.GetOrAdd(...)` XML comments to state the full contract: + `valueFactory` itself must be non-null, and it must not produce a null cache value. diff --git a/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md b/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md new file mode 100644 index 00000000..db67b893 --- /dev/null +++ b/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md @@ -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`、`HiddenEnvelope` 这类闭包类型现在也能按生成期已知结构直接重建,进一步贴近 `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` 缓存改为按 `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()`、`ContextAwareMediator*Extensions` 与 `MediatorCoroutineExtensions`,不再继续维护旧命名兼容表面 + - 已将 generator 对“外部程序集不可直引 protected nested 类型”的 residual contract 从 `RegisterRemainingReflectedHandlerInterfaces(...)` 补洞推进到“按程序集名 + metadata name 定向 lookup”;`IRequestHandler` 这类场景现在走精确 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` 的递归 `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(...)` 现在直接复用 `ValueTask` 委托 + - 不再通过 `object` 桥接承接 request 结果,减少 value-type 响应装箱 +- `GFramework.Cqrs/Internal/CqrsDispatcher.cs` 已继续把 notification/request/stream 的分散 service-type / invoker 缓存收敛为聚合 dispatch binding cache: + - `NotificationDispatchBindings` / `RequestDispatchBindingCache` / `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()`” + - `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` / `INotification` / `IStreamRequest` + - `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` 已把上下文注入窗口、瞬态实例复用约束和流创建/枚举取消边界写入显式 + ``,避免公共流式命令处理器基类的生命周期约束只停留在摘要里。 +- `CqrsHandlerRegistrarTests` 已改为通过 `CqrsTestRuntime.RegisterHandlers(...)` 真实入口验证部分加载失败恢复路径,并同时断言 + “剩余 handler 仍被注册 + warning 已记录”。 +- `CqrsTestRuntime` 已补齐 XML 文档,并改为按 `IIocContainer + IEnumerable + ILogger` 精确绑定 + `RegisterHandlers(...)`,避免未来新增同名重载后出现运行时歧义。 +- `MediatorCoroutineExtensions` 已改为直接走 `IArchitectureContext.SendAsync(...)` 内建 CQRS 入口,不再从容器解析 + `IMediator`;该类型名仅作为历史兼容命名保留。 +- `RegisterCqrsPipelineBehavior()` 已作为新的公开推荐入口加入 `IArchitecture`、`IIocContainer`、 + `Architecture` 与 `MicrosoftDiContainer`;旧的 `RegisterMediatorBehavior()` 改为显式兼容转发。 +- `ContextAwareCqrsExtensions`、`ContextAwareCqrsCommandExtensions`、`ContextAwareCqrsQueryExtensions` 与 + `CqrsCoroutineExtensions` 已作为新的中性命名扩展入口加入;旧的 `ContextAwareMediator*Extensions` 与 + `MediatorCoroutineExtensions` 保留为 `[Obsolete]` 兼容包装层。 +- `RegisterMediatorBehavior()`、`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` 的上下文可用窗口、实例复用约束与取消边界需要放入显式 + ``,避免公共基类被误用 + - `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`,并显式使用 `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` 已在类级 `` 中明确“该类型不是线程安全的,必须由外部同步边界串行访问”的设计约束 +- `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()` / `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.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`,若后续要继续消除此项,需要单独规划并提交文档注释覆盖率改进 diff --git a/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md b/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md new file mode 100644 index 00000000..bde7925b --- /dev/null +++ b/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md @@ -0,0 +1,1444 @@ +# CQRS 重写迁移追踪 + +## 2026-04-14 + +### 阶段:初始化 + +- 建立 `CQRS-REWRITE-RP-001` 恢复点 +- 已确认本次迁移目标: + - 彻底参考 `Mediator` 思路重写 GFramework 正式 CQRS + - 不保留对 `Mediator` 的兼容层 + - 使用 `abstractions + runtime 可选模块` 边界 + - 保留 `EventBus`,不与 CQRS notification 合并 + +### 已确认的实现前提 + +- `CoreGrid-Migration` 当前仍依赖 NuGet 版 `GeWuYou.GFramework*` +- `CoreGrid/scripts/core/GameArchitecture.cs` 与 `CoreGrid-Migration/scripts/core/GameArchitecture.cs` 通过 `AddMediator(...)` 启用基于生成器的 runtime +- `GFramework` 当前 `IArchitectureContext` 与一批 CQRS 基类直接引用 `Mediator.*` +- `CoreGrid/scripts/cqrs/**` 的 handler 很薄,主要迁移成本在框架 runtime 和注册机制,不在业务逻辑本身 + +### 当前动作 + +- 准备更新 `AGENTS.md`,补充恢复点 / trace / subagent 协作规范 +- 准备将 `CoreGrid-Migration` 切换为本地项目引用,建立真实验证链路 + +### 下一步 + +1. 完成 `AGENTS.md` 规则补充 +2. 改造 `CoreGrid-Migration/CoreGrid.csproj` 为本地项目与本地生成器引用 +3. 进行第一次构建验证,确认本地链路可用 + +### 阶段:CQRS 主路径迁移完成 + +- `CoreGrid-Migration/CoreGrid.csproj` 已切到本地 `ProjectReference` + 本地 source generators +- `CoreGrid-Migration/scripts/core/GameArchitecture.cs` 已删除 `AddMediator(...)` 配置钩子 +- `GFramework.Core.Abstractions` 新增 GFramework 自有 CQRS 契约与 `Unit` +- `IArchitectureContext` / `ArchitectureContext` 已切到自有 CQRS 签名 +- `ArchitectureBootstrapper` 已内建 handler 扫描注册,使用方无需再显式调用 `AddMediator(...)` +- `CqrsDispatcher` 已补齐 request/notification/stream dispatch 与 pipeline behavior 执行 +- `GFramework.Core.Cqrs.*` 基类、`ContextAwareMediator*Extensions`、Godot 协程上下文扩展均已迁到新契约 +- `GFramework.Core.Tests` 中原依赖旧 `Mediator` 注册入口的测试已迁移到 `CqrsTestRuntime` 反射注册路径 + +### 阶段:验证 + +- `dotnet build GFramework.Core/GFramework.Core.csproj` + - 结果:通过 +- `dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj` + - 结果:通过 +- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj --no-build` + - 结果:通过 + - 明细:`1621` 个测试全部通过 +- `dotnet build GFramework.sln` + - 结果:通过 +- `dotnet build ../CoreGrid-Migration/CoreGrid.sln` + - 结果:通过 + - 备注:仅存在既有 analyzer warnings,无新增构建错误 + +### 阶段:CQRS 收尾修正 + +- 建立 `CQRS-REWRITE-RP-003` 恢复点 +- 修正 `IArchitectureContext`、`QueryBase`、`Abstract*Handler` 与 `MessageHandlerDelegate` 的 XML 文档,明确旧 + Command/Query 总线与新 CQRS runtime 的兼容边界 +- `CqrsHandlerRegistrar` 改为按程序集名、处理器类型名与处理器接口名稳定排序 +- `CqrsHandlerRegistrar` 在 `ReflectionTypeLoadException` 场景下会记录告警并保留可加载类型继续注册 +- 自动扫描到的 request / notification / stream handler 改为 transient,避免 `ContextAwareBase` 派生处理器在并发请求间共享可变上下文 +- `CqrsTestRuntime` 移除自建 pipeline 执行逻辑,测试改为走 `ArchitectureContext.SendRequestAsync(...)` 正式入口 +- `MediatorAdvancedFeaturesTests` 为断路器静态状态补上统一重置,消除测试间污染 + +### 阶段:补充验证 + +- +`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` 个测试全部通过 + +### 阶段:review 收尾修正 + +- 建立 `CQRS-REWRITE-RP-004` 恢复点 +- `AbstractStreamCommandHandler` 已把上下文注入窗口、瞬态实例约束与流创建/枚举取消边界移入显式 + `` +- `CqrsHandlerRegistrarTests` 已改为从 `CqrsTestRuntime.RegisterHandlers(...)` 真实入口覆盖部分类型加载失败场景,不再反射 + `RecoverLoadableTypes(...)` +- `CqrsTestRuntime` 已补齐 XML 文档,并改为按 `IIocContainer + IEnumerable + ILogger` 精确绑定 + `RegisterHandlers(...)` 反射签名,避免未来重载漂移 +- 定向验证期间发现 `CqrsHandlerRegistrar.cs` 缺少 `Microsoft.Extensions.DependencyInjection` 的 `using`,导致 + `IServiceCollection` 无法编译;该编译阻塞已一并修复 + +### 阶段:review 修正验证 + +- `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` 个测试全部通过 + +### 阶段:去外部依赖收尾 + +- 建立 `CQRS-REWRITE-RP-005` 恢复点 +- `GFramework.Core.Abstractions` 已移除 `Mediator.Abstractions` 包引用,并显式改依赖 + `Microsoft.Extensions.DependencyInjection.Abstractions` 以承接 `IServiceCollection` / `IServiceScope` +- `GFramework.Core.Tests` 已移除 `Mediator.Abstractions` 与 `Mediator.SourceGenerator` 包引用 +- `MediatorCoroutineExtensions` 已改为直接走 `IArchitectureContext.SendAsync(...)` 内建 CQRS 入口,不再解析 + `IMediator` +- `Architecture` / `ArchitectureModules` / `IIocContainer` / `MicrosoftDiContainer` / `ArchitectureBootstrapper` + 与主文档已补充“历史命名保留,但 runtime 已为内建 CQRS”说明 +- 并行 subagent 盘点确认:生产代码与主文档中剩余的 `Mediator` 主要是历史兼容命名,不再对应任何外部包依赖 + +### 阶段:去外部依赖验证 + +- `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 与 `GodotProjectDir` 缺失导致的 generator warning,无新增构建错误 +- `dotnet build GFramework.sln -c Release` + - 结果:失败 + - 原因:当前 WSL 环境下顶层 `GFramework.csproj` 仍引用 Windows NuGet fallback package folder + `D:\Tool\Development Tools\Microsoft Visual Studio\Shared\NuGetPackages` + - 结论:属于既有环境配置问题,与本轮 CQRS 去 `Mediator` 改动无关 + +### 阶段:历史命名中性化 + +- 建立 `CQRS-REWRITE-RP-006` 恢复点 +- `IArchitecture` / `IIocContainer` / `Architecture` / `MicrosoftDiContainer` 已新增 + `RegisterCqrsPipelineBehavior()` 推荐入口 +- 旧的 `RegisterMediatorBehavior()` 已降级为 `[Obsolete]` 兼容包装层,并转发到新的 CQRS 中性命名 +- 新增 `ContextAwareCqrsExtensions`、`ContextAwareCqrsCommandExtensions`、`ContextAwareCqrsQueryExtensions` + 与 `CqrsCoroutineExtensions` +- 旧的 `ContextAwareMediator*Extensions` 与 `MediatorCoroutineExtensions` 已改为 `[Obsolete]` 兼容包装层 +- 新的 `ContextAwareCqrs*` / `CqrsCoroutineExtensions` 已放入独立命名空间,避免与旧扩展同时导入时产生调用歧义 +- `ArchitectureModulesBehaviorTests`、`CqrsCoroutineExtensionsTests`、`docs/zh-CN/core/cqrs.md`、 + `docs/zh-CN/core/index.md` 与 `CoreGrid-Migration` 命中的旧扩展调用点已迁到新的中性命名入口 + +### 阶段:历史命名中性化验证 + +- `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 + +### 阶段:兼容层正式弃用 + +- 建立 `CQRS-REWRITE-RP-007` 恢复点 +- `IArchitecture`、`IIocContainer`、`Architecture`、`ArchitectureModules` 与 `MicrosoftDiContainer` + 上剩余的 `RegisterMediatorBehavior()` 已统一补充 future-major 移除说明 +- `ContextAwareMediatorExtensions`、`ContextAwareMediatorCommandExtensions`、 + `ContextAwareMediatorQueryExtensions` 与 `MediatorCoroutineExtensions` + 已统一补充 future-major 移除说明 +- 上述历史兼容入口已统一加上 `EditorBrowsable(EditorBrowsableState.Never)`,从 IntelliSense 主路径隐藏, + 将迁移默认路径进一步收敛到新的 `Cqrs` 命名入口 +- `docs/zh-CN/core/cqrs.md` 与 `CLAUDE.md` 已同步记录“兼容别名进入正式弃用周期”的事实 +- `MediatorCompatibilityDeprecationTests` 已新增并通过反射断言锁定: + - 公开兼容方法保留行为兼容,但必须带有 `Obsolete` + - 公开兼容方法与兼容扩展类型必须带有 `EditorBrowsable(EditorBrowsableState.Never)` + - 弃用消息必须明确新的 CQRS 迁移目标与 future-major 移除预期 + +### 阶段:兼容层正式弃用验证 + +- `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` 个测试全部通过 + +### 阶段:CQRS source-generator 注册 MVP + +- 建立 `CQRS-REWRITE-RP-008` 恢复点 +- `GFramework.Core.Abstractions` 已新增: + - `ICqrsHandlerRegistry` + - `CqrsHandlerRegistryAttribute` +- `CqrsHandlerRegistrar` 已优先尝试读取程序集级 `CqrsHandlerRegistryAttribute` + 指向的生成注册器;当生成注册器缺失、类型无效或实例化失败时,会记录 warning 并自动回退到原有反射扫描路径 +- `GFramework.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs` 已新增: + - 为当前消费端程序集中的 concrete request / notification / stream handlers 生成单一程序集级注册器 + - 生成注册顺序与 runtime 反射口径对齐,按实现类型与处理器接口名稳定排序 + - 当程序集包含生成代码无法合法引用的 concrete handler(例如私有嵌套 handler)时,直接放弃生成,让 runtime 保持完整反射回退 +- `docs/zh-CN/core/cqrs.md` 与 `CLAUDE.md` 已同步记录“生成注册器优先,反射扫描兜底”的当前行为 + +### 阶段:CQRS source-generator 注册验证 + +- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release` + - 结果:通过 +- `dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release` + - 结果:通过 +- `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` 个测试全部通过 + +### 阶段:review 收尾修正(并发 lazy 初始化与共享测试运行时) + +- `ArchitectureContext` 的 `ICqrsRuntime` 惰性解析已从 `??=` 改为 `Lazy`, + 并显式指定 `LazyThreadSafetyMode.ExecutionAndPublication` +- 新增 `ArchitectureContextTests.SendRequestAsync_Should_ResolveCqrsRuntime_OnlyOnce_When_AccessedConcurrently` + 以回归锁定并发首次访问行为 +- 两个测试项目中的 `CqrsTestRuntime` 重复实现已收敛为单一共享源码文件 `tests/Shared/CqrsTestRuntime.cs` +- 共享 `CqrsTestRuntime` 已移除 `GetType(..., throwOnError: true)` 后不可达的 `?? 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` 时命中过一次 `project.assets.json` 文件竞争; + 顺序重跑 `GFramework.Core.Tests --no-restore` 后通过,确认是 restore 并发噪音而非实现缺陷 + +### 阶段:测试公共基础设施模块化 + +- 已将临时共享源码目录 `tests/Shared` 收敛为正式项目 `GFramework.Tests.Common` +- `CqrsTestRuntime` 已迁入 `GFramework.Tests.Common` 并改为由测试项目通过 `ProjectReference` 访问 + +## 2026-04-16 + +### 阶段:review 收尾修正(线程安全文档、未冻结态去重与 runtime 契约说明) + +- 建立 `CQRS-REWRITE-RP-020` 恢复点 +- `GFramework.Cqrs/Internal/DefaultCqrsRegistrationService.cs` 已在类级 `` 中明确“该类型不是线程安全的,必须由外部同步边界串行访问”的设计约束 +- `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()` / `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 + +### 阶段:review 收尾修正(CQRS 程序集枚举输入固定与前置校验) + +- 建立 `CQRS-REWRITE-RP-021` 恢复点 +- `GFramework.Core/Ioc/MicrosoftDiContainer.cs` 已将 `RegisterCqrsHandlersFromAssemblies(...)` 调整为: + - 在进入容器写锁前先 `ToArray()` 固定输入枚举 + - 逐项执行 `ArgumentNullException.ThrowIfNull(...)` + - 再把固定后的数组交给 `ICqrsRegistrationService` +- 该调整把 `null` 元素和依赖外部可变状态的延迟枚举拦截在容器入口处,避免更深层的反射/注册路径才暴露输入问题,也减少在写锁内枚举外部序列的非确定性 +- `GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs` 已新增空程序集元素回归测试,锁定“先校验,再委托”的失败边界 + +### 阶段:RP-021 验证 + +- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Core.Tests.Ioc.MicrosoftDiContainerTests"` + - 结果:通过 + - 明细:`41` 个测试全部通过 + +### 下一步 + +1. 若后续 review 继续聚焦容器显式注册入口,可考虑补一条“延迟枚举只在入口处物化一次”的行为测试 +- `GFramework.sln` 已纳入 `GFramework.Tests.Common`,测试公共基础设施不再悬空在 solution 外 + +### 阶段:测试公共基础设施模块化验证 + +- `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` 个测试全部通过 + +### 阶段:日志入口下沉与剩余 runtime behaviors 迁移 + +- 建立 `CQRS-REWRITE-RP-016` 恢复点 +- `LoggerFactoryResolver` 已从 `GFramework.Core` 下沉到 `GFramework.Core.Abstractions`,继续保留 `GFramework.Core.Logging` 命名空间 +- 新的抽象层 `LoggerFactoryResolver` 默认会优先反射解析 `GFramework.Core.Logging.ConsoleLoggerFactoryProvider` +- 当宿主未加载 `GFramework.Core` 默认日志实现时,`LoggerFactoryResolver` 会退回静默 provider,避免上层模块只为拿日志入口而重新依赖实现层 +- `GFramework.Core` 已新增 type forward,继续对外暴露 `LoggerFactoryResolver`,降低已编译消费端的运行时兼容风险 +- `LoggingBehavior` 与 `PerformanceBehavior` 已迁移到 `GFramework.Cqrs`,同时继续保留 `GFramework.Core.Cqrs.Behaviors` 命名空间 +- 迁移后 `GFramework.Core/Cqrs/*` 已全部搬空;当前 runtime 物理迁移残项只剩 `CqrsCoroutineExtensions` +- 并行 explorer 结论: + - `ICqrsRuntime` 的真实阻塞链路是 `ICqrsRuntime -> IArchitectureContext -> IContextAware / handler Context` + - 只要 CQRS handler 上下文模型仍直接依赖完整 `IArchitectureContext`,`ICqrsRuntime` 就不能无损迁到 `GFramework.Cqrs.Abstractions` + - `CqrsCoroutineExtensions` 依赖 `TaskCoroutineExtensions` 与 `WaitForTask*`,本质属于 `Core` 协程桥接层,不宜原样迁到 `GFramework.Cqrs` + - 若只追求下一步最小可行推进,应优先设计更窄的 CQRS 专用上下文 seam,并继续把协程桥接保留在 `GFramework.Core` + +### 阶段:RP-016 验证 + +## 2026-04-16 + +### 阶段:ICqrsRuntime 归属收敛与兼容别名落地 + +- 建立 `CQRS-REWRITE-RP-017` 恢复点 +- `GFramework.Cqrs.Abstractions/Cqrs/ICqrsContext.cs` 已新增,作为 CQRS runtime 使用的最小上下文 marker seam: + - 该 seam 仅用于切断 `ICqrsRuntime -> IArchitectureContext` 的编译期循环依赖 + - 当前 runtime 仍允许在实现层识别更具体的 `IArchitectureContext`,以兼容现有 `IContextAware` handler 注入模型 +- `GFramework.Cqrs.Abstractions/Cqrs/ICqrsRuntime.cs` 已新增并成为正式 runtime seam 归属 +- `GFramework.Core.Abstractions/Cqrs/ICqrsRuntime.cs` 已改为兼容别名: + - 旧接口现在继承新的 `GFramework.Cqrs.Abstractions.Cqrs.ICqrsRuntime` + - 通过 `EditorBrowsable(EditorBrowsableState.Never)` 隐藏历史路径,避免新代码继续绑定旧 namespace +- `IArchitectureContext` 已实现 `ICqrsContext`,继续作为默认架构内的 CQRS 分发上下文 +- `CqrsDispatcher` 已改为按 `ICqrsContext` 接收分发上下文: + - 对现有 `IContextAware` handler 的注入保留运行时兼容检查 + - 若未来引入非架构型 CQRS context,实现层可显式阻止将其注入到仍要求 `IArchitectureContext` 的历史 handler +- `ArchitectureContext`、`CqrsRuntimeFactory` 与默认 runtime 注册路径已切到新的 `GFramework.Cqrs.Abstractions.Cqrs.ICqrsRuntime` +- `CqrsRuntimeModule` 与 `GFramework.Tests.Common/CqrsTestRuntime.cs` 已同时注册新旧 `ICqrsRuntime` 接口: + - 主代码路径默认解析新接口 + - 历史公开路径仍可解析到同一个 runtime 实例,避免 consumer 或测试基础设施立即断裂 +- 本轮结论同步收敛: + - 旧 `GFramework.Core.Abstractions.Cqrs*` 兼容面只保留 `ICqrsRuntime` 这一最小公共别名 + - `GFramework.Core.Cqrs.Extensions` 不再视为需要继续正式维护的旧路径;仓库与 `CoreGrid-Migration` 已切到新的 `GFramework.Cqrs.Extensions` 入口 + +### 阶段:迁移目标修正为“Core = App Runtime,CQRS = 集成子系统” + +- 在重新评估 Phase 5/7 的边界目标后,确认此前“继续推动 `Core` 尽量不依赖 `Cqrs`”的方向已经偏离本任务的真正目标 +- 迁移计划现正式修正为: + - `GFramework.Core.Abstractions -> GFramework.Cqrs.Abstractions` + - `GFramework.Core -> GFramework.Cqrs` + - `Core` 作为 App Runtime,默认集成 CQRS 子系统 + - `CQRS` 作为被集成的正式子系统,继续承接抽象层、实现层、generator 层 +- 本次修正特别明确两点: + - `Core` 强依赖 `Cqrs` 是允许的,不再以“彻底零依赖”为目标 + - 但 `Core` 不应依赖 CQRS 的细节结构,例如直接 `new` 具体 dispatcher、直接依赖 generator 生成类型、硬编码 handler/internal registry 细节 +- 因此后续健康依赖标准改为: + - 单向依赖 + - 通过 seam / 模块入口装配 + - 默认集成,但理论上可替换 runtime +- `CqrsCoroutineExtensions` 的定位也一并修正: + - 它不再是“尚未迁移干净的 CQRS runtime 残项” + - 而是 `Core` 对 CQRS 的协程桥接层,保留在 `Core` 是合理结果 +- 方向修正后的下一步主线: + 1. 停止继续为了纯边界推进高成本的上下文/桥接迁移 + 2. 保持 `Core -> Cqrs` 的健康单向依赖,继续避免细节泄漏 + 3. 把精力转向 CQRS 子系统增强,尤其是 generator 覆盖面与低反射路径 +- 同时补充一条实现约束: + - 后续 CQRS runtime、pipeline、generator 与低反射路径的设计与实现,应明确参考 `Mediator` 中已经成熟可用的实现 + - 目标是吸收其已验证的结构与经验,减少重复踩坑,而不是把这些运行时细节完全从零发明 + +- `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` 个测试全部通过 + +### 阶段:Core 泄漏盘点与 handler 注册协调器下沉 + +- 建立 `CQRS-REWRITE-RP-018` 恢复点 +- 并行 explorer 盘点结论已收敛: + - `ArchitectureContext -> ICqrsRuntime` 属于合理的 runtime seam,不视为实现细节泄漏 + - `Core` 当前未直接依赖任何 generator 生成类型名或生成注册器具体类型 + - 主要值得继续收敛的实现细节泄漏点集中在: + - `MicrosoftDiContainer` 里原本维护的 handler 程序集去重状态与 registrar 调度 + - `ArchitectureBootstrapper` 对默认 handler 程序集的硬编码 + - 更长线的 `IIocContainer` / `IArchitecture` CQRS 装配 API 暴露面 +- 本轮先完成最小落地收敛: + - `GFramework.Cqrs/ICqrsRegistrationService.cs` 已新增,作为 CQRS handler 程序集接入协调入口 + - `GFramework.Cqrs/Internal/DefaultCqrsRegistrationService.cs` 已新增,将稳定程序集键去重与 `ICqrsHandlerRegistrar` 调度统一收敛到 CQRS runtime 内部 + - `GFramework.Cqrs/CqrsRuntimeFactory.cs` 已新增 `CreateRegistrationService(...)` + - `GFramework.Core/Services/Modules/CqrsRuntimeModule.cs` 现会同时注册: + - `ICqrsRuntime` + - 旧 `GFramework.Core.Abstractions.Cqrs.ICqrsRuntime` 兼容别名 + - `ICqrsHandlerRegistrar` + - `ICqrsRegistrationService` + - `GFramework.Core/Ioc/MicrosoftDiContainer.cs` 已移除本地 `_registeredCqrsHandlerAssemblyKeys` 状态与 registrar 直连逻辑,`RegisterCqrsHandlersFromAssemblies(...)` 现仅委托给 `ICqrsRegistrationService` + - `GFramework.Tests.Common/CqrsTestRuntime.cs` 已同步补齐 `ICqrsRegistrationService` 接线,保持裸测试容器与生产路径一致 +- 这一轮结论是: + - `Core -> Cqrs` 的单向依赖可以保留 + - 但 CQRS handler 注册细节应继续下沉到 CQRS 子系统,而不是分散在容器壳层中 + - 下一步主线可开始转向 generator / 低反射增强,而不是继续为边界纯化推进高成本桥接迁移 + +### 阶段:RP-018 验证 + +- `dotnet test GFramework/GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Core.Tests.Ioc.MicrosoftDiContainerTests|FullyQualifiedName~GFramework.Core.Tests.Architectures.ArchitectureAdditionalCqrsHandlersTests|FullyQualifiedName~GFramework.Core.Tests.Architectures.ArchitectureModulesBehaviorTests"` + - 结果:通过 + - 明细:`43` 个测试全部通过 +- `dotnet test GFramework/GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Cqrs.Tests.Mediator.MediatorArchitectureIntegrationTests|FullyQualifiedName~GFramework.Cqrs.Tests.Mediator.MediatorComprehensiveTests|FullyQualifiedName~GFramework.Cqrs.Tests.Mediator.MediatorAdvancedFeaturesTests"` + - 结果:通过 + - 明细:`45` 个测试全部通过 +- 在一次并行执行两条 `dotnet test` 的尝试中再次命中 `GFramework.Cqrs.deps.json` 文件锁冲突; + 顺序重跑后稳定通过,确认属于本地并发构建输出竞争,而不是本轮实现缺陷 + +### 阶段:generator 局部回退落地 + +- 建立 `CQRS-REWRITE-RP-019` 恢复点 +- 已将 CQRS handler generator 从“整程序集回退”推进到“局部 generated + 局部 reflection fallback”: + - `GFramework.Cqrs/CqrsReflectionFallbackAttribute.cs` 已新增,作为程序集级 fallback marker + - `GFramework.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs` 现会: + - 继续为可由生成代码合法引用的 handlers 生成 registry + - 当程序集内同时存在不可见 handlers 且 runtime 合同支持 marker 时,额外生成 `CqrsReflectionFallbackAttribute` + - 若消费端仍缺少该 marker 合同,则保持旧的整程序集回退行为,避免静默漏掉不可见 handlers +- `GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs` 已配套支持新的组合路径: + - 优先执行 generated registry + - 若程序集带有 `CqrsReflectionFallbackAttribute`,则继续补一次 reflection 扫描 + - reflection 补扫会按 `ServiceType + ImplementationType` 去重,避免已由 generated registry 注册的映射重复写入 +- `GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs` 已更新/新增: + - 正常生成快照继续覆盖 + - “私有嵌套 handler” 场景改为断言“仍生成可见 handler + 输出 fallback marker” + - 新增“旧 runtime 合同缺少 marker 时仍整程序集回退”的兼容回归 +- `GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs` 已新增组合路径回归: + - 当 generated registry 与 reflection fallback 同时参与时,剩余 handlers 会被补齐 + - 已由 generated registry 注册的 handler 映射不会重复写入服务集合 + +### 阶段:RP-019 验证 + +- `dotnet test GFramework/GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.SourceGenerators.Tests.Cqrs.CqrsHandlerRegistryGeneratorTests"` + - 结果:通过 + - 明细:`4` 个测试全部通过 +- `dotnet test GFramework/GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsHandlerRegistrarTests"` + - 结果:通过 + - 明细:`5` 个测试全部通过 +- `dotnet test GFramework/GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Core.Tests.Architectures.ArchitectureAdditionalCqrsHandlersTests"` + - 结果:通过 + - 明细:`2` 个测试全部通过 +- 在并行执行三条 `dotnet test` 时,`SourceGenerators` 与 `Cqrs` 两条命中过一次本地 restore / deps 文件竞争; + 顺序重跑后全部通过,确认属于本地并发构建输出竞争,不是本轮实现缺陷 + +### 阶段:runtime 物理迁移(第一批) + +- 建立 `CQRS-REWRITE-RP-014` 恢复点 +- `GFramework.Core/Cqrs/Internal/CqrsDispatcher.cs`、`CqrsHandlerRegistrar.cs` 与 + `DefaultCqrsHandlerRegistrar.cs` 已迁移到 `GFramework.Cqrs` 项目,保留原 `GFramework.Core.Cqrs.Internal` + 命名空间,降低消费端源码层面的 breaking change +- `GFramework.Core/Cqrs/Command/CommandBase.cs`、`Query/QueryBase.cs`、`Request/RequestBase.cs` 与 + `Notification/NotificationBase.cs` 已迁移到 `GFramework.Cqrs` +- `GFramework.Core/Extensions/ContextAwareCqrsExtensions.cs`、`ContextAwareCqrsCommandExtensions.cs` 与 + `ContextAwareCqrsQueryExtensions.cs` 已迁移到 `GFramework.Cqrs` +- `ICqrsHandlerRegistry` 与 `CqrsHandlerRegistryAttribute` 已从 `GFramework.Core.Abstractions` + 收敛到 `GFramework.Cqrs` 根命名空间,作为 runtime 专属契约: + - 这样避免了把依赖 `ILogger` / `IServiceCollection` 的类型继续塞进 `GFramework.Cqrs.Abstractions` + - 同时规避 `GFramework.Core.Abstractions <-> GFramework.Cqrs.Abstractions` 循环引用 +- `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, + 不再跨程序集直接实例化内部实现 +- 迁移过程中确认 `GFramework.Core/Coroutine/Extensions/CqrsCoroutineExtensions.cs` + 仍依赖 `TaskCoroutineExtensions.AsCoroutineInstruction()` 等 `Core` 协程工具链, + 因此本轮暂留 `GFramework.Core`,未强行迁移 + +### 阶段:runtime 物理迁移(第一批)验证 + +- `dotnet build GFramework/GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release` + - 结果:通过 +- `dotnet test GFramework/GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-build --filter "FullyQualifiedName~GFramework.SourceGenerators.Tests.Cqrs.CqrsHandlerRegistryGeneratorTests"` + - 结果:通过 + - 明细:`3` 个测试全部通过 +- `dotnet build GFramework/GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release` + - 结果:通过 +- `dotnet test GFramework/GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsHandlerRegistrarTests|FullyQualifiedName~GFramework.Cqrs.Tests.Mediator.MediatorArchitectureIntegrationTests|FullyQualifiedName~GFramework.Cqrs.Tests.Mediator.MediatorComprehensiveTests|FullyQualifiedName~GFramework.Cqrs.Tests.Mediator.MediatorAdvancedFeaturesTests"` + - 结果:通过 + - 明细:`49` 个测试全部通过 +- `dotnet build GFramework/GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release` + - 结果:通过 +- `dotnet test GFramework/GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Core.Tests.Architectures.ArchitectureAdditionalCqrsHandlersTests|FullyQualifiedName~GFramework.Core.Tests.Architectures.ArchitectureModulesBehaviorTests|FullyQualifiedName~GFramework.Core.Tests.Ioc.MicrosoftDiContainerTests|FullyQualifiedName~GFramework.Core.Tests.Architectures.ArchitectureContextTests"` + - 结果:通过 + - 明细:`63` 个测试全部通过 +- 并行执行两个会触发编译输出写入同一 `GFramework.Tests.Common.dll` 的一次性 copy retry warning; + 单次顺序验证通过,确认属于本地并行构建噪音,不是实现缺陷 + +## 2026-04-15 + +### 阶段:非默认程序集显式 CQRS 接入 + +- 建立 `CQRS-REWRITE-RP-009` 恢复点 +- `IArchitecture` / `IIocContainer` / `Architecture` / `ArchitectureModules` / `MicrosoftDiContainer` + 已新增: + - `RegisterCqrsHandlersFromAssembly(Assembly assembly)` + - `RegisterCqrsHandlersFromAssemblies(IEnumerable assemblies)` +- `ArchitectureBootstrapper` 已改为复用容器公开入口完成默认程序集注册, + 让“默认启动路径”和“额外程序集显式接入路径”统一走同一套 CQRS handler 注册逻辑 +- `MicrosoftDiContainer` 已按稳定程序集键去重 CQRS handler 注册, + 避免同一程序集被默认路径、模块安装或用户初始化阶段重复接入时写入重复 handler 映射 +- `ArchitectureAdditionalCqrsHandlersTests` 已新增并锁定两类行为: + - 显式接入扩展程序集时,运行时会通过程序集级 `CqrsHandlerRegistryAttribute` 成功注册 handlers + - 同一额外程序集被重复声明时,不会重复注册 handlers +- `docs/zh-CN/core/cqrs.md` 与 `CLAUDE.md` 已同步补充新的显式接入入口与行为说明 + +### 阶段:非默认程序集显式 CQRS 接入验证 + +- `dotnet build GFramework/GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release` + - 结果:通过 + - 备注:仅存在既有 `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"` + - 结果:通过 + - 明细:`13` 个测试全部通过 + +### 下一步 + +1. 评估如何进一步降低“显式额外程序集接入”场景中的反射回退占比,让更多程序集直接命中生成注册器 +2. 评估是否将 CQRS 正式拆分为独立 abstractions 模块与 runtime 实现模块,并明确 `GFramework.Core` 的最终依赖边界 +3. 为 future-major 版本准备兼容层真正移除时的 API 清理清单与升级说明 + +### 当前残留 + +- 兼容 API、测试目录与部分历史文档仍保留 `Mediator` 前缀,但主入口已新增中性 CQRS 命名,且兼容 API 已进入正式弃用周期 +- handler 自动注册当前已具备默认路径与显式扩展路径的统一入口,但仍有一部分程序集只能走反射回退 +- solution 级 `dotnet build GFramework.sln -c Release` 仍受既有 Windows NuGet fallback package folder 配置影响 + +### 下一步建议 + +1. 优先补齐“非默认程序集”的 CQRS handler generator 接入点,例如显式模块程序集或扩展包程序集 +2. 在 source-generator 覆盖范围更明确后,评估是否把现有 CQRS runtime 从 `GFramework.Core` 中继续外提为独立模块 +3. 再规划 `RegisterMediatorBehavior`、`MediatorCoroutineExtensions` 与 `ContextAwareMediator*Extensions` 的最终删除窗口 +4. 评估是否为兼容层移除提供 analyzer 或 upgrade note,以降低 future-major 迁移成本 + +### 阶段:review 跟进微调 + +- `ArchitectureAdditionalCqrsHandlersTests` 已改为复用现有 `SyncTestArchitecture` + `AddPostRegistrationHook(...)` + 测试基建,不再维护仅用于注入初始化逻辑的专用 `AdditionalHandlersTestArchitecture` +- “显式额外程序集去重”回归用例已加强为两个不同 `Assembly` mock 实例,但共享相同 `FullName` 稳定键, + 直接锁定 `MicrosoftDiContainer` 的程序集键去重语义,而不是只验证同一 mock 实例的重复注册 + +### 下一步 + +1. 运行 `ArchitectureAdditionalCqrsHandlersTests` 定向回归,确认基建复用后显式程序集接入与去重行为保持通过 +2. 如需继续收敛测试样板,可再盘点 `Architecture` 相关测试里仍然只用于初始化注入的本地架构类型 + +### 阶段:CQRS 模块边界评估 + +- 建立 `CQRS-REWRITE-RP-010` 恢复点 +- 已完成 Phase 5 的模块边界再评估,并新增 `ai-plan/migration/CQRS_MODULE_SPLIT_PLAN.md` +- 本轮结论: + - 将 CQRS 拆为独立 abstractions/runtime 模块是成立的 + - 但当前不能直接做“搬项目”式拆分,必须先完成 `GFramework.Core -> CQRS runtime abstraction` 的依赖倒置 +- 已确认的硬耦合点: + - `ArchitectureContext` 直接实例化 `CqrsDispatcher` + - `MicrosoftDiContainer` 直接调用 `CqrsHandlerRegistrar` + - `ArchitectureBootstrapper` 在默认启动路径内隐含 CQRS runtime 已存在 +- 已确认的消费端兼容压力: + - `CoreGrid-Migration/scripts/cqrs/**` 大量直接依赖 `CommandBase`、`Abstract*Handler` 与 `GFramework.Core.Cqrs.Extensions` + - `GFramework.Godot/Coroutine/ContextAwareCoroutineExtensions.cs` 直接依赖 `GFramework.Core.Cqrs.Extensions` +- 新拆分草案明确了推荐目标: + - `GFramework.Cqrs.Abstractions`:正式 CQRS 契约 + runtime seam + - `GFramework.Cqrs`:dispatcher、handler registrar、base handlers、behaviors、extensions + - `GFramework.Core.Abstractions`:转为依赖 `GFramework.Cqrs.Abstractions` + - `GFramework.Core`:只保留架构集成与兼容壳层 + +### 下一步 + +1. 进入 Phase 6,优先为 runtime 建立 `ICqrsRuntime` / `ICqrsHandlerRegistrar` seam +2. 将 `ArchitectureContext` 与 `MicrosoftDiContainer` 改为依赖该 seam,而不是直接依赖 `CqrsDispatcher` / `CqrsHandlerRegistrar` +3. 在默认启动行为与消费端源码保持兼容的前提下,补齐针对 seam 改造的定向测试 +4. 待 seam 稳定后,再创建 `GFramework.Cqrs.Abstractions` / `GFramework.Cqrs` 项目骨架并推进源码迁移 + +### 阶段:CQRS runtime seam 落地 + +- 建立 `CQRS-REWRITE-RP-011` 恢复点 +- `GFramework.Core.Abstractions/Cqrs/ICqrsRuntime.cs` 与 `ICqrsHandlerRegistrar.cs` 已新增,明确 runtime 调度与 handler 接入的依赖倒置 seam +- `CqrsDispatcher` 已改为实现 `ICqrsRuntime`,并改为在调用时接收 `IArchitectureContext` +- `ArchitectureContext` 已改为解析 `ICqrsRuntime`,不再直接 `new CqrsDispatcher(...)` +- `MicrosoftDiContainer.RegisterCqrsHandlersFromAssemblies(...)` 已改为解析 `ICqrsHandlerRegistrar`,不再直接调用 `CqrsHandlerRegistrar` +- `DefaultCqrsHandlerRegistrar` 与 `CqrsRuntimeModule` 已落地,使默认架构启动路径继续自动具备 CQRS runtime 与 handler 注册能力 +- `CqrsTestRuntime.RegisterInfrastructure(...)` 已新增,用于为裸 `MicrosoftDiContainer` 测试容器补齐与生产路径一致的 seam 基础设施 +- 针对 `Clear()` 后重新接入 handler 的回归已补上“先恢复测试基础设施再验证 dedup 状态重置”的显式步骤 + +### 阶段:CQRS runtime seam 验证 + +- `dotnet build GFramework/GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release` + - 结果:通过 +- `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"` + - 结果:通过 + - 明细:`97` 个测试全部通过 + +### 下一步 + +1. 进入 Phase 7,创建 `GFramework.Cqrs.Abstractions` / `GFramework.Cqrs` 项目骨架 +2. 先迁移 `GFramework.Core.Abstractions/Cqrs/*`,并保持现有命名空间,避免同时引入 namespace breaking change +3. 再迁移 `GFramework.Core/Cqrs/*`、`GFramework.Core.Cqrs.Extensions` 与相关协程扩展 +4. 最后处理 `GFramework.SourceGenerators`、顶层 meta package 与消费端 transitive 依赖图 + +### 阶段:CQRS abstractions 项目骨架与纯契约迁移 + +- 建立 `CQRS-REWRITE-RP-012` 恢复点 +- `GFramework.Cqrs.Abstractions` 与 `GFramework.Cqrs` 项目骨架已创建,并加入 `GFramework.sln` +- `GFramework.Core.Abstractions` 已改为引用 `GFramework.Cqrs.Abstractions` +- 以下纯 CQRS 契约已迁移到 `GFramework.Cqrs.Abstractions`,同时保持原命名空间 `GFramework.Core.Abstractions.Cqrs*` 不变: + - `IRequest*` / `IStreamRequest*` / `INotification*` + - `IPipelineBehavior` + - `MessageHandlerDelegate` + - `Unit` + - `Command/Query/Request/Notification` 输入与标记契约 + - `ICqrsHandlerRegistrar` +- 在实际迁移中确认: + - `ICqrsRuntime -> IArchitectureContext` + - `ICqrsHandlerRegistry -> ILogger` + 这两条依赖会导致 `GFramework.Core.Abstractions <-> GFramework.Cqrs.Abstractions` 循环引用风险,因此当前暂不迁出这三类类型: + - `ICqrsRuntime` + - `ICqrsHandlerRegistry` + - `CqrsHandlerRegistryAttribute` + +### 阶段:CQRS abstractions 迁移验证 + +- `dotnet build GFramework/GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release` + - 结果:通过 + - 备注:仅存在既有 `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"` + - 结果:通过 + - 明细:`97` 个测试全部通过 + +### 下一步 + +1. 先为 `ICqrsRuntime` / `ICqrsHandlerRegistry` 收敛新的边界方案,避免 `Core.Abstractions` 与 `Cqrs.Abstractions` 形成循环引用 +2. 再开始迁移 `GFramework.Core/Cqrs/*`、`GFramework.Core.Cqrs.Extensions` 与协程扩展到 `GFramework.Cqrs` +3. 迁移 runtime 后,再处理 `GFramework.SourceGenerators` 与 meta package 的依赖图更新 + +### 阶段:CQRS 聚焦测试拆分与 CI 接入 + +- 建立 `CQRS-REWRITE-RP-013` 恢复点 +- 已新增 `GFramework.Cqrs.Tests` 项目,并加入 `GFramework.sln` +- 以下 CQRS 聚焦测试已从 `GFramework.Core.Tests` 迁移到 `GFramework.Cqrs.Tests`: + - `Cqrs/CqrsHandlerRegistrarTests.cs` + - `Coroutine/CqrsCoroutineExtensionsTests.cs` + - `Mediator/MediatorAdvancedFeaturesTests.cs` + - `Mediator/MediatorArchitectureIntegrationTests.cs` + - `Mediator/MediatorComprehensiveTests.cs` +- `GFramework.Cqrs.Tests/GlobalUsings.cs` 已补齐 `NUnit` / `Moq` / `System.*` / `Microsoft.Extensions.DependencyInjection` 等基础编译上下文 +- `GFramework.Cqrs.Tests/Logging/TestLogger.cs` 已新增,使新测试项目不再依赖 `GFramework.Core.Tests/Logging/LoggerTests.cs` 中的内嵌测试辅助 +- `GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs` 已改为依赖本地 `ContainerRegistrationFixtures.cs`,避免继续编译期引用已迁出的 CQRS 测试类型 +- `GFramework/.github/workflows/ci.yml` 已新增 `dotnet test GFramework.Cqrs.Tests ...` step,确保 PR CI 覆盖新测试项目 + +### 阶段:CQRS 聚焦测试拆分验证 + +- `dotnet build GFramework/GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release` + - 结果:通过 +- `dotnet test GFramework/GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --no-build` + - 结果:通过 + - 明细:`54` 个测试全部通过 +- `dotnet build GFramework/GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release` + - 结果:通过 +- `dotnet test GFramework/GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-build --filter "FullyQualifiedName~MicrosoftDiContainerTests|FullyQualifiedName~ArchitectureAdditionalCqrsHandlersTests|FullyQualifiedName~ArchitectureModulesBehaviorTests|FullyQualifiedName~MediatorCompatibilityDeprecationTests"` + - 结果:通过 + - 明细:`44` 个测试全部通过 + +### 下一步 + +1. 开始迁移 `GFramework.Core/Cqrs/*`、`GFramework.Core.Cqrs.Extensions` 与相关协程扩展到 `GFramework.Cqrs` +2. 在 runtime 迁移过程中保持 `GFramework.Cqrs.Tests` 绿灯,持续作为 CQRS 模块的主回归项目 +3. 等 runtime 物理迁移完成后,再处理 source-generator 引用图与顶层 package/transitive 依赖整理 + +### 阶段:轻量 handler 上下文基类与 CoreGrid 兼容性收敛 + +- 建立 `CQRS-REWRITE-RP-015` 恢复点 +- `GFramework.Cqrs/Cqrs/CqrsContextAwareHandlerBase.cs` 已新增,作为只依赖 `IContextAware` / `IArchitectureContext` 的轻量 CQRS handler 基类 +- 各类 `Abstract*Handler` 已改为继承该轻量基类,不再直接依赖 `GFramework.Core.Rule.ContextAwareBase` +- 新增 `GFramework.Cqrs.Tests/Cqrs/AbstractCqrsHandlerContextTests.cs`,回归锁定“未注入前 fail-fast、注入后可访问 Context、`OnContextReady()` 仍生效”的行为 +- 真实消费端验证阶段,`CoreGrid-Migration` 暴露了旧 `GFramework.Core.Abstractions.Cqrs*` 与旧 `GFramework.Core.Cqrs.Extensions` namespace 已不再自动可用的问题 +- 为了先恢复迁移验证链路,本轮先采取 consumer 侧最小修复: + - `scripts/GlobalUsings.cs` 已补齐新的 `GFramework.Cqrs.Abstractions.Cqrs*`、`GFramework.Cqrs.*` 与 `GFramework.Core.Cqrs.*` 导入 + - `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 编译错误 + +### 下一步 + +1. 明确是否正式承诺旧 `GFramework.Core.Abstractions.Cqrs*` / `GFramework.Core.Cqrs.Extensions` public namespace 兼容 +2. 若不承诺兼容,补充迁移文档并继续沿 `GFramework.Cqrs*` 命名空间推进 +3. 若承诺兼容,再单独设计兼容层,而不是让 `CoreGrid-Migration` 这类消费端各自散落修补 +4. 无论兼容策略如何,下一阶段都应继续收敛 `ICqrsRuntime -> IArchitectureContext` 与 `CqrsCoroutineExtensions -> TaskCoroutineExtensions` 的剩余边界 + +## 2026-04-16 + +### 阶段:review 收尾修正(partial reflection fallback 精确定向补扫) + +- 建立 `CQRS-REWRITE-RP-022` 恢复点 +- `GFramework.Cqrs/CqrsReflectionFallbackAttribute.cs` 已扩展为可携带精确 fallback handler 类型名清单: + - 生成器能够把“无法在生成代码中直接引用”的 concrete handler 记录为程序集级 metadata + - 当 metadata 为空时,runtime 仍保持旧版“整程序集补扫”的兼容语义 +- `GFramework.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs` 已改为: + - 对不可直接引用的 concrete handler 生成精确的 runtime type-name 清单 + - 若消费端 runtime 仅支持旧版无参 fallback marker,则自动退回旧语义,避免破坏兼容 + - 无 unsupported handler 时不再错误输出 fallback marker +- `GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs` 已改为: + - 生成注册器命中后,优先读取 `CqrsReflectionFallbackAttribute` 中的精确 type-name 清单 + - 若清单存在,则按稳定顺序定向 `Assembly.GetType(...)` 解析剩余 handlers,而不是重新 `GetTypes()` 扫描整个程序集 + - 只有在旧 marker 或手写 marker 未提供清单时,才继续回退到整程序集扫描路径 +- `GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs` 已新增/更新回归: + - 锁定“partial fallback 通过精确 type-name 补扫” + - 断言该路径不会调用 `Assembly.GetTypes()` +- `GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs` 已新增/更新回归: + - 锁定“私有嵌套 handler => 输出精确 type-name fallback marker” + - 锁定“旧版 runtime 仅支持无参 marker 时 => 生成器自动退回旧语义” + +### 阶段:RP-022 验证 + +- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.SourceGenerators.Tests.Cqrs.CqrsHandlerRegistryGeneratorTests"` + - 结果:通过 + - 明细:`5` 个测试全部通过 +- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsHandlerRegistrarTests"` + - 结果:通过 + - 明细:`5` 个测试全部通过 + +### 下一步 + +1. 继续扩大 generator 命中面,优先盘点仍会落到“旧 marker / 无精确清单”路径的实际场景 +2. 评估是否需要为手写/第三方程序集提供更正式的精确 fallback metadata 入口,而不是只依赖 generator 自动产出 +3. 在 handler 注册路径之外,继续评估 dispatch / invoker 链路可否进一步减少运行时反射 + +## 2026-04-16 + +### 阶段:review 收尾修正(手写/第三方程序集的精确 fallback metadata 入口) + +- 建立 `CQRS-REWRITE-RP-023` 恢复点 +- `GFramework.Cqrs/CqrsReflectionFallbackAttribute.cs` 已扩展为同时支持: + - `params string[] fallbackHandlerTypeNames` + - `params Type[] fallbackHandlerTypes` + - 无参兼容 marker +- 这使手写或第三方程序集在“有 generated registry,但仍需补少量 reflection-only handlers”时,可以直接声明可引用的 handler `Type`,避免再走字符串名称回查。 +- `GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs` 已改为按以下优先级处理 fallback metadata: + - 先消费显式 `Type` 集合 + - 再消费字符串 type-name 清单 + - 只有没有任何精确 metadata 时,才继续回退到整程序集 `GetTypes()` 扫描 +- registrar 对 direct `Type` fallback 的程序集一致性校验已从对象引用比较收敛为稳定程序集键比较,避免代理/测试场景下把语义等价的程序集误判为不一致。 +- `GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs` 已新增回归: + - 锁定“direct `Type` fallback 不触发 `Assembly.GetType()`” + - 锁定“direct `Type` fallback 也不触发 `Assembly.GetTypes()`” + +### 阶段:RP-023 验证 + +- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsHandlerRegistrarTests"` + - 结果:通过 + - 明细:`6` 个测试全部通过 + +### 下一步 + +1. handler 注册层的“手写 fallback 精确入口”已补齐,后续优先转向 `CqrsDispatcher` 热路径 +2. 盘点 `PublishAsync` / `SendAsync` / `CreateStream` 中每次分发都会发生的 `MakeGenericType` / service-type 构造,评估是否用稳定缓存进一步减少反射 +3. 若热路径缓存收益成立,再补一组聚焦 `CqrsDispatcher` 的回归或性能守护测试 + +## 2026-04-16 + +### 阶段:review 收尾修正(dispatcher 热路径 service-type 缓存) + +- 建立 `CQRS-REWRITE-RP-024` 恢复点 +- `GFramework.Cqrs/Internal/CqrsDispatcher.cs` 已新增三组热路径 service-type 缓存: + - `NotificationHandlerServiceTypes` + - `RequestServiceTypes` + - `StreamHandlerServiceTypes` +- 这些缓存把以下重复工作收敛为“首次构造一次,后续复用”: + - `PublishAsync(...)` 中 `typeof(INotificationHandler<>).MakeGenericType(...)` + - `SendAsync(...)` 中 request handler / pipeline behavior 的服务类型构造 + - `CreateStream(...)` 中 stream handler 的服务类型构造 +- 现有 invoker delegate 缓存继续保留;这轮补的是容器 service lookup 前的泛型服务类型构造缓存,而不是替换已有 invoker cache。 +- `GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherCacheTests.cs` 已新增回归: + - 用唯一的 request / notification / stream 测试类型命中三条 dispatcher 路径 + - 通过反射读取 dispatcher 内部缓存字典,锁定“首次分发新增一条缓存,后续同类型分发不再增长” + +### 阶段:RP-024 验证 + +- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsHandlerRegistrarTests|FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsDispatcherCacheTests"` + - 结果:通过 + - 明细:`7` 个测试全部通过 + +### 下一步 + +1. 继续评估 request pipeline / invoker 路径是否还有值得继续缓存或收敛的动态构造 +2. 若收益有限,再回到 generator 命中面扩张与 legacy fallback 面积压缩 +3. 若后续要做更强性能守护,可考虑补一个更窄的 benchmark 或轻量性能回归,而不是只依赖功能性缓存测试 + +## 2026-04-16 + +### 阶段:review 收尾修正(dispatcher invoker method-definition 静态缓存与统一缓存回归) + +- 建立 `CQRS-REWRITE-RP-025` 恢复点 +- `GFramework.Cqrs/Internal/CqrsDispatcher.cs` 已将以下泛型方法定义查找收敛为静态一次解析: + - `InvokeRequestHandlerAsync` + - `InvokeRequestPipelineAsync` + - `InvokeNotificationHandlerAsync` + - `InvokeStreamHandler` +- 这样每种新消息类型首次命中 invoker cache 时,只剩 `MakeGenericMethod(...) + CreateDelegate(...)`,不再重复执行 `GetMethod(...)` 查找。 +- `GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherCacheTests.cs` 已扩展为同时覆盖: + - request/no-pipeline 的 `RequestInvokers` + - request/with-pipeline 的 `RequestPipelineInvokers` + - notification 的 `NotificationInvokers` + - stream 的 `StreamInvokers` + - 以及对应的 service-type cache +- 测试通过显式注册 `DispatcherPipelineCacheBehavior` 命中 pipeline 分支,避免当前缓存回归只覆盖无行为请求路径。 + +### 阶段:RP-025 验证 + +- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsHandlerRegistrarTests|FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsDispatcherCacheTests"` + - 结果:通过 + - 明细:`7` 个测试全部通过 + +### 下一步 + +1. dispatcher 侧基础低反射缓存已覆盖 service-type 与 invoker 两层,后续优先判断 pipeline delegate 链的每次分发构造是否仍值得继续收敛 +2. 若 pipeline 链构造收益不大,则回到 generator 命中面扩张与 legacy fallback 面积压缩 +3. 若需要进一步证明收益,可考虑增加更窄的性能守护而不是继续堆功能性反射缓存测试 + +## 2026-04-16 + +### 阶段:review 收尾修正(generated registry 内部定向反射注册隐藏 handler) + +- 建立 `CQRS-REWRITE-RP-026` 恢复点 +- `GFramework.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs` 已将“不可直接 `typeof(...)` 引用的 concrete handler”从程序集级 fallback marker 输出收敛为 generated registry 内部的定向反射注册: + - 对可见 handler 仍保持 `typeof(...)` 直注册路径 + - 对私有/不可直接引用但仍可按元数据名从当前程序集重新定位的 handler,改为生成 `Assembly.GetType(...)` + `GetInterfaces()` 的本地注册逻辑 + - 这类场景不再额外要求 runtime registrar 读取 `CqrsReflectionFallbackAttribute` +- 生成器不再依赖 runtime 是否暴露 `CqrsReflectionFallbackAttribute` 才决定是否产出 registry;隐藏 handler 已由生成代码自身覆盖,因此旧版“无 marker 就整程序集放弃生成”的分支已去除。 +- `GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs` 已同步更新: + - 私有嵌套 handler 快照改为断言 generated registry 自行调用 `RegisterReflectedHandler(...)` + - 旧版无参 marker 合同场景改为断言“不再输出 legacy marker” + - 完全不存在 fallback marker 合同时,仍断言 generator 会继续产出 registry 并覆盖隐藏 handler + +### 阶段:RP-026 验证 + +- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.SourceGenerators.Tests.Cqrs.CqrsHandlerRegistryGeneratorTests"` + - 结果:通过 + - 明细:`5` 个测试全部通过 +- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore --filter "FullyQualifiedName~GFramework.SourceGenerators.Tests.Cqrs.CqrsHandlerRegistryGeneratorTests"` + - 结果:通过 + - 明细:`5` 个测试全部通过 +- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsHandlerRegistrarTests|FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsDispatcherCacheTests"` + - 结果:通过 + - 明细:`7` 个测试全部通过 + +### 下一步 + +1. 继续盘点 generator 仍未覆盖的 handler 形态,确认是否还存在必须依赖 runtime fallback metadata 的真实残余场景 +2. 若 generator 命中面已接近稳定,再回到 dispatcher pipeline delegate 链构造,判断是否值得继续做低反射/低分配收敛 +3. 若后续继续参考 `Mediator`,优先找“生成期已知、运行期只做常量时间绑定”的模式,而不是新增另一层宽反射补扫 + +## 2026-04-16 + +### 阶段:review 收尾修正(隐藏 implementation + 可见 handler interface 的直连接线) + +- 建立 `CQRS-REWRITE-RP-027` 恢复点 +- 并行/本地盘点结论收敛: + - `CqrsDispatcher` 当前剩余开销主要是容器解析、pipeline 链重建与 `object` 装箱,不再是优先级最高的“反射缺口” + - 因此本轮继续回到 generator 主线,而不是提前做 dispatcher 微优化 +- `GFramework.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs` 已新增一条更窄的生成路径: + - 当 handler implementation 不能被生成代码直接 `typeof(...)` 引用,但 handler interface 仍可直接引用时 + - generated registry 现在只会对 implementation 做一次 `Assembly.GetType(...)` + - 随后直接以 `typeof(IRequestHandler<...>)` / `typeof(INotificationHandler<...>)` / `typeof(IStreamRequestHandler<...>)` 完成注册 + - 不再为这类场景额外生成 `GetInterfaces()`、`IsSupportedHandlerInterface(...)` 与 `GetRuntimeTypeDisplayName(...)` 辅助逻辑 +- 该调整把“隐藏 handler 的 generated registry 内部处理”继续从“implementation + interface 运行时发现”收敛为“implementation-only lookup + direct interface binding” +- `GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs` 已新增快照回归: + - 锁定“隐藏 implementation + 可见 interface”场景只生成 implementation lookup + direct service registration + - 同时保持已有“隐藏 implementation + 隐藏 contract”场景仍走旧的本地 `RegisterReflectedHandler(...)` 辅助路径 +- 本轮实现过程中命中过一次生成器运行时异常: + - 原因是新增分支后 `ImmutableArray` builder 不再总是满容量,`MoveToImmutable()` 触发 `Count equals Capacity` 约束 + - 已收敛为 `ToImmutable()`,并同步修正 generated source 分支顺序与 helper 输出条件 + +### 阶段:RP-027 验证 + +- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.SourceGenerators.Tests.Cqrs.CqrsHandlerRegistryGeneratorTests"` + - 结果:通过 + - 明细:`6` 个测试全部通过 +- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsHandlerRegistrarTests"` + - 结果:通过 + - 明细:`6` 个测试全部通过 + +### 下一步 + +1. 继续盘点 generator 仍会落到 `GetInterfaces()` 或 runtime fallback metadata 的 handler 形态,优先找还能压成“生成期已知 service type + implementation lookup”的场景 +2. 若 generator 侧残余面积已经很小,再回到 dispatcher,重点考虑是否真的值得为值类型响应装箱与 pipeline 链重建做更复杂的 typed-invoker 优化 +3. 若要继续参考 `Mediator`,优先寻找“生成期把接口/handler 绑定静态化”的模式,而不是继续扩大运行时辅助反射工具面 + +## 2026-04-16 + +### 阶段:review 收尾修正(隐藏 interface 的精确 service type 重建) + +- 建立 `CQRS-REWRITE-RP-028` 恢复点 +- 在继续盘点 generator 命中面后,本轮把一类仍落到 `GetInterfaces()` 的常见场景继续收窄: + - implementation 不可直接 `typeof(...)` + - handler interface 也不可直接 `typeof(...)` + - 但 open generic contract 与闭包 type arguments 仍然能在生成期被精确表达 +- `GFramework.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs` 已新增更窄的精确重建路径: + - 对 `IRequestHandler<,>` / `INotificationHandler<>` / `IStreamRequestHandler<,>` 记录 open generic contract + - 对不可直引、但属于当前编译程序集的 type arguments 输出定向 `Assembly.GetType(...)` + - 运行期通过 `MakeGenericType(...)` 重建精确 service type + - 再把该 service type 直接绑定到 implementation,而不是先 `GetInterfaces()` 再做支持接口筛选 +- 这一轮已覆盖的典型场景包括: + - 私有嵌套 request + 私有 handler + 可见 response + - 可见 implementation + 隐藏消息类型 + - 隐藏 implementation + 隐藏消息类型 +- 为避免误把“当前仍无法精确表达的 type 形态”静默漏掉,生成器仍保留旧的本地回退辅助分支: + - 当闭包 type arguments 含有当前 helper 还无法精确重建的形态(本轮用“隐藏元素类型数组”做回归)时 + - 仍会退回 `RegisterReflectedHandler(...)` + `GetInterfaces()` 的安全路径 +- 本轮顺手修复了两处实现细节问题: + - `ImmutableArray` builder 在分支化后不再总是满容量,`MoveToImmutable()` 已收敛为 `ToImmutable()` + - open generic contract 的生成文本已从 `IRequestHandler` 修正为合法的 `IRequestHandler<,>` 形式 + +### 阶段:RP-028 验证 + +- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.SourceGenerators.Tests.Cqrs.CqrsHandlerRegistryGeneratorTests"` + - 结果:通过 + - 明细:`7` 个测试全部通过 +- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsHandlerRegistrarTests"` + - 结果:通过 + - 明细:`6` 个测试全部通过 + +### 下一步 + +1. 继续评估 `RuntimeTypeReference` 是否可以递归覆盖数组/更复杂闭包类型,进一步缩小仍需 `GetInterfaces()` 的残余场景 +2. 若 generator 侧只剩极少数复杂形态,再回到 dispatcher,重新判断 typed-invoker 去装箱优化是否值得进入实现阶段 +3. 若后续继续参考 `Mediator`,优先找“生成器输出精确 closed service type,运行期只负责常量时间绑定”的对应模式 + +## 2026-04-16 + +### 阶段:review 收尾修正(递归 generic type reconstruction) + +- 建立 `CQRS-REWRITE-RP-029` 恢复点 +- 继续按“优先参考 Mediator 的生成期定结构思路,而不是新增运行时宽反射”推进 generator 主线: + - 本轮没有新增任何新的 runtime 接口发现分支 + - 而是把 `RuntimeTypeReference` 从“直接类型 + 数组”扩成递归可组合结构 +- `GFramework.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs` 现支持递归重建: + - 直接可引用类型 + - 当前编译程序集内的隐藏类型定向 `Assembly.GetType(...)` + - 数组类型的 `MakeArrayType(...)` + - 构造泛型类型的 `MakeGenericType(...)` +- 因此以下场景已能直接生成 closed service type,而不再退回 `GetInterfaces()`: + - `HiddenResponse[]` + - `List` 这类“可引用泛型定义 + 隐藏实参” + - `HiddenEnvelope` 这类“隐藏泛型定义 + 可重建实参” +- 实现过程中额外收敛了两处细节: + - open generic definition 的直接引用改为通过 `ConstructUnboundGenericType()` 输出合法 `typeof(List<>)` / `typeof(IRequestHandler<,>)` 形式 + - `TryCreateRuntimeTypeReference(...)` 的失败分支改为显式 `null` 输出,避免新的递归结构继续引入可空警告 +- `GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs` 已新增/更新快照回归: + - 隐藏数组响应场景现在锁定为 `MakeArrayType()` 路径 + - 新增“隐藏泛型定义 + 常量实参”场景,锁定递归 `MakeGenericType()` 输出 + +### 阶段:RP-029 验证 + +- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.SourceGenerators.Tests.Cqrs.CqrsHandlerRegistryGeneratorTests"` + - 结果:通过 + - 明细:`8` 个测试全部通过 +- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsHandlerRegistrarTests"` + - 结果:通过 + - 明细:`6` 个测试全部通过 + +### 下一步 + +1. 继续盘点仍必须落回 `GetInterfaces()` 的残余类型形态,优先判断是否只剩极少数低价值边角分支 +2. 若 generator 命中面已足够高,再回到 dispatcher,评估 typed-invoker 去装箱是否值得作为下一条独立优化线 +3. 若继续参考 `Mediator`,优先找“生成器直接输出 closed contract 表”的更强静态化模式,而不是继续给 runtime 补更多推断能力 + +## 2026-04-16 + +### 阶段:review 收尾修正(dispatcher typed invoker cache 去装箱) + +- 建立 `CQRS-REWRITE-RP-030` 恢复点 +- 在完成 `RuntimeTypeReference` 递归扩展后,本轮先对 dispatcher 做一轮低风险热路径优化: + - 重新盘点后确认,当前 C# 可编译的常见 handler interface 形态里,generator 剩余 `GetInterfaces()` 回退已接近低价值尾部 + - 因此本轮不再继续堆新的 runtime 类型推断分支,而是转回 dispatcher,落实此前待定的 typed-invoker 去装箱优化 +- `GFramework.Cqrs/Internal/CqrsDispatcher.cs` 已将 request 两条调用路径改为按 `TResponse` 分层的强类型委托缓存: + - `RequestInvokerCache` 负责无 pipeline 的 request handler 调用缓存 + - `RequestPipelineInvokerCache` 负责带 pipeline 的 request 调用缓存 + - 缓存键从 `(RequestType, ResponseType)` 收敛为当前 `TResponse` 层内的 `RequestType` + - `InvokeRequestHandlerAsync(...)` 与 `InvokeRequestPipelineAsync(...)` 现直接返回 `ValueTask`,不再通过 `ValueTask` 桥接 request 结果 +- 这一轮的实现约束是: + - 不改变 notification / stream 现有缓存结构 + - 不改变公开 runtime 契约与 observable dispatch 语义 + - 只减少 request 热路径里 value-type 响应的装箱/拆箱成本 +- `GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherCacheTests.cs` 已同步更新: + - 原有“一次建缓存,多次复用”的 service-type / invoker 回归已适配新的泛型嵌套缓存结构 + - 新增 `Dispatcher_Should_Cache_Request_Invokers_Per_Response_Type()`,锁定 `int` 与 `string` 响应会分别命中各自的 request invoker cache + - `SetUp()` 现在会显式清空 dispatcher 静态缓存,避免跨测试共享进程级状态导致缓存计数断言漂移 + +### 阶段:RP-030 验证 + +- `dotnet test GFramework/GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsDispatcherCacheTests|FullyQualifiedName~GFramework.Cqrs.Tests.Mediator.MediatorArchitectureIntegrationTests|FullyQualifiedName~GFramework.Cqrs.Tests.Mediator.MediatorComprehensiveTests|FullyQualifiedName~GFramework.Cqrs.Tests.Mediator.MediatorAdvancedFeaturesTests"` + - 结果:通过 + - 明细:`47` 个测试全部通过 + +### 下一步 + +1. 回到 generator 主线,确认当前剩余 `GetInterfaces()` 本地回退是否只服务于极少数几乎不可达的边角形态 +2. 若该尾部分支确实价值有限,优先评估是否直接推进“生成器输出 closed contract 表”的更强静态化方案 +3. 若后续还要继续做 dispatcher 微优化,再单独评估 notification / stream 路径是否存在同等级别、且有明确收益的可观测热点 + +## 2026-04-16 + +### 阶段:review 收尾修正(generator mixed registration composition) + +- 建立 `CQRS-REWRITE-RP-031` 恢复点 +- 在回到 generator 主线后,确认当前更值得优先修正的问题不是继续扩 `RuntimeTypeReference`,而是生成器输出粒度过粗: + - `TransformHandlerCandidate(...)` 已能把同一 implementation 上的不同 handler interface 分流到 direct / reflected-implementation / precise-reflected 三条路径 + - 但原 `GenerateSource(...)` 仍按 implementation 做互斥分支选择 + - 因此一旦同一个 implementation 同时命中多种路径,后面的分支会吞掉前面本已可静态绑定的注册 +- `GFramework.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs` 已将该逻辑收敛为“按 handler interface 组合输出”: + - direct-only implementation 仍保持原有直注册输出 + - 只要同一 implementation 上存在 reflected-implementation 或 precise-reflected 注册,生成器现在会先把三类注册步骤合并到一个有序列表 + - 合并后的步骤继续按 `HandlerInterfaceLogName` 做稳定排序,避免因为内部 bucket 化而偏离原先按接口名排序的生成顺序 + - 对可见 implementation,会复用 `typeof(ImplementationType)` 作为统一 implementation 变量 + - 对隐藏 implementation,则继续只做一次 `Assembly.GetType(...)`,再在同一块里完成可见接口直绑与精确 `MakeGenericType(...)` 注册 +- 这一轮的收敛点是: + - 不新增新的 runtime 宽反射分支 + - 也不改变“真正 unresolved 形态仍可整实现退回 `RegisterReflectedHandler(...)`”的安全边界 + - 只修正“本可静态绑定的接口被同实现上的另一条更窄路径连带降级”的生成器粒度问题 +- `GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs` 已新增两条快照回归: + - `Generates_Mixed_Direct_And_Precise_Registrations_For_Same_Implementation()` + - `Generates_Mixed_Reflected_Implementation_And_Precise_Registrations_For_Same_Implementation()` + - 这两条测试分别锁定“可见 implementation”与“隐藏 implementation”上的混合路径输出,防止后续再退回 implementation 级互斥分支 + +### 阶段:RP-031 验证 + +- `dotnet test GFramework/GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.SourceGenerators.Tests.Cqrs.CqrsHandlerRegistryGeneratorTests"` + - 结果:通过 + - 明细:`10` 个测试全部通过 + +### 下一步 + +1. 继续盘点当前剩余必须整实现退回 `RegisterReflectedHandler(...)` 的类型形态,确认是否真的只剩低价值尾部分支 +2. 若该判断成立,下一步优先评估“生成器直接输出 closed contract 表”的静态化方案,而不是继续给 runtime 增加推断辅助 +3. 若要继续参考 `Mediator`,重点比较其生成代码是否已经把“多接口 implementation 的 contract 表”静态化到比当前更细的粒度 + +## 2026-04-16 + +### 阶段:review 收尾修正(partial full-fallback 与 generated-registry accessibility) + +- 建立 `CQRS-REWRITE-RP-032` 恢复点 +- 在继续盘点 generator 剩余 full-fallback 后,确认这一轮真正需要同时收敛的是两件事: + - 先前“是否能在生成注册器里直接 `typeof(...)` 某个类型”的判断只看声明可见性,无法区分“当前语义上下文可见”和“生成注册器顶层上下文可直接书写” + - 即使某个 implementation 上只剩一条未知接口,旧逻辑也会直接清空此前已收集注册,整实现退回 `GetInterfaces()` 发现 +- `GFramework.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs` 已将可访问性判断改为: + - `Compilation.IsSymbolAccessibleWithin(symbol, compilation.Assembly, null)` + - 这使生成器现在按“顶层 generated registry 所在上下文”判断能否直接引用类型,而不再把 `protected internal` 之类的声明可见类型粗暴视为一定可直引 + - 同程序集私有/隐藏类型仍继续走 metadata-name lookup,不影响当前已落地的精确定向注册路径 +- `TransformHandlerCandidate(...)` 已不再在遇到单个未知接口时直接 `return` 丢掉已收集注册: + - 改为设置 `RequiresRuntimeInterfaceDiscovery = true` + - 继续把同实现上仍可确定的 direct / reflected-implementation / precise-reflected 注册收集完整 +- 生成代码模型已新增“局部补洞”路径: + - `ImplementationRegistrationSpec` 现携带 `RequiresRuntimeInterfaceDiscovery` + - 当同一 implementation 仍残留未知接口时,生成代码会先建立 `knownServiceTypes` + - 先完成所有已知 service type 的注册,并把这些 service type 加入 `knownServiceTypes` + - 再调用 `RegisterRemainingReflectedHandlerInterfaces(...)`,只对 `GetInterfaces()` 中尚未出现在 `knownServiceTypes` 的 supported handler interface 做补注册 +- 这一轮的结果是: + - full-fallback 仍保留为最后的安全边界 + - 但已从“整实现吞掉已知注册”收敛为“implementation 内部局部补洞” + - 同时修正了外部 `protected internal` 嵌套类型这类边界下,旧判断可能误生成不可编译 `typeof(...)` 的风险 +- 为了覆盖这个真实边界,测试基础设施也做了最小扩展: + - `GFramework.SourceGenerators.Tests/Core/GeneratorTest.cs` 新增带 `AdditionalReferences` 的重载 + - `MetadataReferenceTestBuilder.cs` 已新增,可把内存源码编成元数据引用 + +### 阶段:RP-032 验证 + +- `dotnet test GFramework/GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.SourceGenerators.Tests.Cqrs.CqrsHandlerRegistryGeneratorTests"` + - 结果:通过 + - 明细:`11` 个测试全部通过 + +### 下一步 + +1. 继续盘点当前仍必须依赖 `RegisterRemainingReflectedHandlerInterfaces(...)` 的真实类型形态,确认是否已经收敛到少量低价值边角路径 +2. 若该判断成立,下一步优先评估“生成器直接输出 closed contract 表”的更强静态化方案 +3. 若继续参考 `Mediator`,重点比较其对“多接口 implementation + 少量未知 contract”的生成期建模是否还比当前更细 + +## 2026-04-16 + +### 阶段:review 小修(RP-033) + +- 接收四条 follow-up 建议后,先确认都命中当前仓库: + - dispatcher 缓存清理遗漏了 `RequestPipelineInvokerCache` + - generator 可访问性判断默认分支缺少显式假设说明 + - `MetadataReferenceTestBuilder` 每次都会重建运行时元数据引用集合 + - `RunGenerator(...)` 的编译错误断言消息上下文偏少 +- 已完成对应实现: + - 在 `ClearDispatcherCaches()` 中补齐 `string` pipeline invoker cache 清理 + - 为 `CanReferenceFromGeneratedRegistry(...)` 默认分支补上“暂按可引用处理,后续可收紧”的注释 + - 用 `Lazy>` 缓存 `TRUSTED_PLATFORM_ASSEMBLIES` 解析结果 + - 让生成器测试失败消息输出完整 `Diagnostic.ToString()` 列表 +- 本轮属于 review follow-up 小修,没有改动公开 API 或运行时主路径语义;核心目标是减少测试间静态状态泄漏风险,并提升 source-generator 测试的调试效率 +- 定向验证已完成: + - `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` 个测试全部通过 + +### 下一步 + +1. 回到 generator 主线,继续盘点当前仍必须依赖 `RegisterRemainingReflectedHandlerInterfaces(...)` 的真实类型形态 +2. 若该尾部分支价值确实很低,优先评估“生成器直接输出 closed contract 表”的更强静态化方案 + +## 2026-04-16 + +### 阶段:review 小修(RP-034) + +- 建立 `CQRS-REWRITE-RP-034` 恢复点 +- 为了把“继续盘点 residual runtime interface discovery 形态”从人工读代码改成可观察输出, + `GFramework.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs` 现在会在每个仍需 + `RegisterRemainingReflectedHandlerInterfaces(...)` 的 implementation 注册块前,生成显式注释: + `// Remaining runtime interface discovery target: ...` +- 注释内容直接复用对应 closed handler interface 的日志显示名,因此不会引入额外 runtime 逻辑,也能和现有 debug 日志口径保持一致 +- `GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs` 已补齐断言,锁定当前已知尾部分支案例 + `IRequestHandler` + 会出现在生成注释中,避免后续又退回“只知道走了 fallback,但不知道具体是哪条 contract”的状态 +- 本轮没有改动公开 API,也没有扩大 runtime 反射面;目标仅是为下一步 closed-contract 评估提供稳定盘点入口 +- 补充验证已完成: + - `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.SourceGenerators.Tests.Cqrs.CqrsHandlerRegistryGeneratorTests"` + - 结果:通过,`11` 个测试全部通过 + - `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Core.Tests.Ioc.MicrosoftDiContainerTests|FullyQualifiedName~GFramework.Core.Tests.Architectures.ArchitectureModulesBehaviorTests|FullyQualifiedName~GFramework.Core.Tests.Architectures.ArchitectureAdditionalCqrsHandlersTests|FullyQualifiedName~GFramework.Core.Tests.Architectures.RegistryInitializationHookBaseTests"` + - 结果:通过,`52` 个测试全部通过 + +### 下一步 + +1. 基于生成注释清单,继续归类当前 residual contract 是否都集中在“外部程序集不可直引类型”这类低频边角 +2. 若判断成立,下一步直接设计 closed-contract 表最小原型,优先验证能否覆盖外部程序集 `protected internal` 嵌套类型,而不是继续扩大 `GetInterfaces()` 补洞 + +## 2026-04-16 + +### 阶段:主线推进(RP-035) + +- 建立 `CQRS-REWRITE-RP-035` 恢复点 +- 已将原 `GFramework.SourceGenerators` 继续按目标模块拆分为: + - `GFramework.Core.SourceGenerators` + - `GFramework.Core.SourceGenerators.Abstractions` + - `GFramework.Cqrs.SourceGenerators` + - `GFramework.Game.SourceGenerators` +- `GFramework.SourceGenerators.Tests` 已改为同时引用新的 `Core/Cqrs/Game` 生成器项目; + `GFramework.Core.Tests` 与 `GFramework.Game.Tests` 也已切到新的本地 analyzer 引用链, + 其中 `Game.Tests` 额外改为导入 `GFramework.Game.SourceGenerators.targets` +- `GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs` 已继续收敛 residual tail: + - `RuntimeTypeReferenceSpec` 现可表达“外部程序集 + metadata name”的定向 lookup + - 对外部程序集中的不可直引 `protected nested` 类型,不再只能退回 `RegisterRemainingReflectedHandlerInterfaces(...)` + - 当前已验证 `IRequestHandler` + 这类案例会生成 `ResolveReferencedAssemblyType(...)` 精确查找,而不是保留 residual 注释 + `GetInterfaces()` 补洞 +- 已删除不再保留的公开 `Mediator` 兼容层: + - `RegisterMediatorBehavior()` + - `ContextAwareMediatorExtensions` + - `ContextAwareMediatorCommandExtensions` + - `ContextAwareMediatorQueryExtensions` + - `MediatorCoroutineExtensions` +- 相关兼容测试 `MediatorCompatibilityDeprecationTests` 已删除; + `ArchitectureModulesBehaviorTests` 与 `RegistryInitializationHookBaseTests` 已同步移除对旧别名的断言/测试替身实现 +- 文档已做最小同步: + - `docs/zh-CN/core/cqrs.md` 不再声明旧 `Mediator` 兼容别名 + - `CLAUDE.md` 已改为统一使用 `Cqrs` 命名入口 + +### 阶段:RP-035 验证 + +- `dotnet build GFramework.Cqrs.SourceGenerators/GFramework.Cqrs.SourceGenerators.csproj -c Release` + - 结果:通过 +- `dotnet build GFramework.Core.SourceGenerators/GFramework.Core.SourceGenerators.csproj -c Release` + - 结果:通过 +- `dotnet build GFramework.Game.SourceGenerators/GFramework.Game.SourceGenerators.csproj -c Release` + - 结果:通过 +- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.SourceGenerators.Tests.Cqrs.CqrsHandlerRegistryGeneratorTests"` + - 结果:通过 + - 明细:`11` 个测试全部通过 +- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Core.Tests.Architectures.ArchitectureModulesBehaviorTests|FullyQualifiedName~GFramework.Core.Tests.Architectures.RegistryInitializationHookBaseTests"` + - 结果:通过 + - 明细:`8` 个测试全部通过 +- `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` + - 结果:通过 + - 备注:仍有一条既有 `GF_ContextRegistration_003` warning,属于测试项目里的静态注册分析提示,不是本轮拆分或 CQRS 主线回归 + +### 下一步 + +1. 继续盘点当前 residual `RegisterRemainingReflectedHandlerInterfaces(...)` 是否还剩下“外部程序集定向 lookup”无法覆盖的真实类型形态 +2. 若只剩极少数低价值边角,直接评估 closed-contract 表最小原型,优先彻底消掉 generator 里的 implementation 级 `GetInterfaces()` 尾分支 +3. 视需要再决定是否把现仍保留旧 namespace 的 source-generator abstractions/public docs 一并统一改名到 `GFramework.Core.SourceGenerators*` + +## 2026-04-17 + +### 阶段:主线推进(RP-036) + +- 建立 `CQRS-REWRITE-RP-036` 恢复点 +- `GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs` 已删除 generated registry 内部的 implementation 级 `GetInterfaces()` 尾分支: + - 不再生成 `RegisterRemainingReflectedHandlerInterfaces(...)` + - 不再生成 `knownServiceTypes` / `Remaining runtime interface discovery target` 注释 / `GetRuntimeTypeDisplayName(...)` 等仅服务于该尾分支的辅助代码 + - 当前合法 C# closed handler contract 统一收敛到 direct / reflected-implementation / precise-reflected 三类注册路径 +- 为保持未来未知 Roslyn 类型形态下的保守正确性,生成器现改为: + - 若仍有无法编码进 `RuntimeTypeReferenceSpec` 的 handler contract,则保留已知静态注册 + - 同时通过程序集级 `CqrsReflectionFallbackAttribute` 对具体 handler implementation 输出 targeted fallback metadata + - 若当前 runtime 合同不提供该 marker,则直接放弃生成 registry,避免静默漏注册 +- `GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs` 已为多程序集 `protected internal` 嵌套类型场景补齐显式断言: + - 锁定不会重新生成 `RegisterRemainingReflectedHandlerInterfaces(...)` + - 锁定不会重新出现 `Remaining runtime interface discovery target` 注释 + - 继续保持 `ResolveReferencedAssemblyType(...)` 精确 lookup 输出 + +### 阶段:RP-036 验证 + +- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.SourceGenerators.Tests.Cqrs.CqrsHandlerRegistryGeneratorTests"` + - 结果:通过 + - 明细:`11` 个测试全部通过 + +### 下一步 + +1. 继续收敛 CQRS runtime 热路径里剩余的反射绑定点,优先盘点 dispatcher / registrar 是否还有可转成静态缓存或强类型委托的分支 +2. 视需要补做 source-generator 命名空间 / 文档收口,决定是否把仍残留旧 `GFramework.SourceGenerators*` 表述的公开文档一并统一到新的模块名 + +## 2026-04-17 + +### 阶段:主线推进(RP-037) + +- 建立 `CQRS-REWRITE-RP-037` 恢复点 +- `GFramework.Cqrs/Internal/CqrsDispatcher.cs` 已继续收敛 dispatcher 热路径里的首次反射绑定与重复缓存命中: + - notification 路径由分散的 `NotificationHandlerServiceTypes + NotificationInvokers` 合并为 `NotificationDispatchBindings` + - stream 路径由分散的 `StreamHandlerServiceTypes + StreamInvokers` 合并为 `StreamDispatchBindings` + - request 路径由 `RequestServiceTypes + RequestInvokerCache + RequestPipelineInvokerCache` 收敛为 `RequestDispatchBindingCache` +- 新的 dispatch binding 会把服务类型与强类型 invoker 委托绑定到同一缓存项: + - 首次命中仍只做一次必要的 `MakeGenericType(...)` / `MakeGenericMethod(...)` / `CreateDelegate(...)` + - 后续 `SendAsync(...)` / `PublishAsync(...)` / `CreateStream(...)` 会减少热路径上的重复字典查询 + - request 绑定继续按 `TResponse` 分层,不回退到 `object` 结果桥接 +- `GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherCacheTests.cs` 已同步更新: + - 断言从旧的 service-type / invoker 分散缓存切换为新的 dispatch binding 缓存结构 + - 继续锁定 request/no-pipeline、request/with-pipeline、notification、stream 四条路径都是“一次建绑定,多次复用” + - 继续锁定 request 绑定按 `int` / `string` 等不同响应类型分层缓存 + +### 阶段:RP-037 验证 + +- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsDispatcherCacheTests"` + - 结果:通过 + - 明细:`2` 个测试全部通过 +- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --no-restore --filter "FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsDispatcherCacheTests|FullyQualifiedName~GFramework.Cqrs.Tests.Mediator.MediatorComprehensiveTests|FullyQualifiedName~GFramework.Cqrs.Tests.Mediator.MediatorArchitectureIntegrationTests|FullyQualifiedName~GFramework.Cqrs.Tests.Mediator.MediatorAdvancedFeaturesTests"` + - 结果:通过 + - 明细:`47` 个测试全部通过 + +### 下一步 + +1. 继续盘点 `CqrsHandlerRegistrar` 初始化路径里剩余的 attribute 读取、registry 实例化与 fallback metadata 解析反射,评估是否值得再做进程级静态缓存 +2. 若 registrar 冷路径收益有限,再回到 source-generator 命名空间 / 文档收口,把仍残留旧 `GFramework.SourceGenerators*` 表述的公开文档统一到新模块名 + +## 2026-04-17 + +### 阶段:主线推进(RP-038) + +- 建立 `CQRS-REWRITE-RP-038` 恢复点 +- `GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs` 已把 registrar 冷路径里重复出现的程序集反射前置解析收敛为进程级缓存: + - `AssemblyMetadataCache` 复用 generated registry attribute 与 reflection fallback metadata 的分析结果 + - `RegistryActivationMetadataCache` 复用 registry 类型是否实现 `ICqrsHandlerRegistry`、是否抽象、是否存在可用无参构造等激活分析 + - `LoadableTypesCache` 复用未命中 generated registry 时的 `GetTypes()` / `ReflectionTypeLoadException` 恢复结果 +- 为避免 `Assembly` 在 mock/proxy 场景下的自定义相等语义干扰缓存命中,上述程序集级缓存统一改为引用相等键 +- 运行时行为保持不变: + - generated registry 仍优先于整程序集扫描 + - fallback metadata 仍先消费显式 `Type`,再消费 type-name lookup + - 没有 generated registry 时仍按稳定排序扫描可加载 handlers +- `GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs` 已新增两条缓存回归: + - 同一程序集对象跨两个容器重复接入时,程序集级 registry/fallback metadata 与 `Assembly.GetType(...)` 只解析一次 + - 同一程序集对象在 full-scan 路径下跨两个容器重复接入时,`GetTypes()` 只执行一次,且两个容器都能复用首次恢复出的可加载 handler 集合 + +### 阶段:RP-038 验证 + +- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsHandlerRegistrarTests"` + - 结果:通过 + - 明细:`8` 个测试全部通过 +- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --no-restore --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"` + - 结果:通过 + - 明细:`53` 个测试全部通过 +- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Core.Tests.Architectures.ArchitectureAdditionalCqrsHandlersTests"` + - 结果:通过 + - 明细:`2` 个测试全部通过 + +### 下一步 + +1. 评估 registrar 冷路径里剩余的 registry 实例创建本身是否值得继续压缩;若收益有限,主线可转回 source-generator 命名空间 / 文档收口 +2. 盘点公开文档和说明文件中仍残留的旧 `GFramework.SourceGenerators*` / 旧 CQRS 表述,决定是否在下一恢复点统一收口 + +## 2026-04-17 + +### 阶段:主线推进(RP-039) + +- 建立 `CQRS-REWRITE-RP-039` 恢复点 +- 结合 RP-038 后的冷路径现状,判断 registrar 继续下压的边际收益已经明显变小,主线转向公开入口文档收口 +- 已完成以下公开入口文档的最小同步: + - `README.md` + - `CLAUDE.md` + - `docs/zh-CN/core/cqrs.md` + - `docs/zh-CN/source-generators/index.md` +- 本轮收口重点: + - 根 README 的模块表、仓库结构与包选择说明已补入 `GFramework.Cqrs` / `GFramework.Cqrs.Abstractions` + - 根 README 与 `CLAUDE.md` 已从旧的单体 `GFramework.SourceGenerators` 模块表述切到当前的 `Core/Game/Godot/Cqrs SourceGenerators` 家族 + - `docs/zh-CN/core/cqrs.md` 的代码示例命名空间已切到当前 `GFramework.Cqrs*` 路径,不再继续展示旧 `GFramework.Core.CQRS*` / `GFramework.Core.Abstractions.CQRS*` + - `docs/zh-CN/core/cqrs.md` 的迁移说明已收敛为“直接使用 `RegisterCqrsPipelineBehavior()`”,不再把已删除的 `RegisterMediatorBehavior()` 写成仍可替换的并存入口 + - `docs/zh-CN/source-generators/index.md` 已从“单一 `GFramework.SourceGenerators` 包”口径改写为“按模块拆分的 Source Generators 家族”口径,同时保留“旧聚合包不存在”的说明 +- 期间尝试把 `README.md` / `CLAUDE.md` 分派给 worker 并行处理,但 subagent 上游接口报错,最终由主 agent 本地完成,不影响结果正确性 + +### 阶段:RP-039 验证 + +- 对以下文件执行定向全文扫描,确认已不存在旧公开入口表述: + - `README.md` + - `CLAUDE.md` + - `docs/zh-CN/core/cqrs.md` + - `docs/zh-CN/source-generators/index.md` +- 扫描结果: + - 已无旧 `GFramework.Core.CQRS*` / `GFramework.Core.Abstractions.CQRS*` 示例命名空间残留 + - 已无旧 `GFramework.SourceGenerators` 聚合模块图或聚合包定位残留 + - 保留的旧名仅存在于迁移说明和“不存在旧聚合包”的显式说明中,属于有意保留 + +### 下一步 + +1. 若继续文档主线,扩大扫描范围到 `docs/zh-CN/**` 与根 README 之外的说明文件,逐步清理更多历史 `GFramework.SourceGenerators*` / `Mediator` 表述 +2. 若回到实现主线,可再次评估 registrar 冷路径是否还值得继续压缩,重点看 registry 实例创建本身的收益是否足以覆盖复杂度 + +## 2026-04-18 + +### 阶段:规则与参考源收口(RP-040) + +- 建立 `CQRS-REWRITE-RP-040` 恢复点 +- 已确认用户将当前 CQRS 主线对照用的 `Mediator` 源码放入仓库内 `ai-libs/Mediator` +- `AGENTS.md` 已新增仓库规则: + - `ai-libs/` 为第三方源码参考区 + - `ai-libs/**` 默认只读,不允许作为常规实现改动目标 + - 后续计划、trace、评审与设计说明引用第三方实现时,应优先写明仓库内路径 +- CQRS 迁移跟踪文档已同步把“参考 Mediator 成熟实现”的当前执行语义收口为“优先参考 `ai-libs/Mediator`” + +### 验证 + +- `dotnet build GFramework.Cqrs/GFramework.Cqrs.csproj -c Release` + - 结果:通过 + - 备注:存在既有 `MA0051` 与 `MA0158` analyzer warnings,无新增构建错误 +- `rg -n "ai-libs/|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` + - 结果:通过 + - 备注:三处文档都已命中 `ai-libs` 只读规则与 `ai-libs/Mediator` 参考路径 + +### 下一步 + +1. 继续推进 CQRS 子系统增强时,把 `ai-libs/Mediator` 作为唯一仓库内参考源使用 +2. 若后续需要变更 `ai-libs/Mediator` 内容,单独建“第三方快照同步”任务,不与 GFramework 实现改动共用一个提交 + +## 2026-04-18 + +### 阶段:文档主线推进(RP-041) + +- 建立 `CQRS-REWRITE-RP-041` 恢复点 +- 结合 `feat/cqrs-optimization` 当前主线与 RP-039 的文档收口结果,继续扩大扫描范围到 `docs/zh-CN/**` +- 盘点确认: + - 旧公开示例主要残留在大量文档代码块中的 `using GFramework.SourceGenerators.Abstractions.*;` + - 当前真实公开命名空间已经是 `GFramework.Core.SourceGenerators.Abstractions.*` + - `GFramework.SourceGenerators.Common` 等命名仍对应仓库内部项目,不应作为本轮批量替换目标 +- 已批量把文档示例命名空间收口到当前公开路径,并额外手工修正三处叙述性旧口径: + - `docs/zh-CN/source-generators/logging-generator.md` + - `docs/zh-CN/source-generators/context-get-generator.md` + - `docs/zh-CN/api-reference/index.md` + +### 验证 + +- 执行: + - `rg -n "using GFramework\\.SourceGenerators\\.Abstractions\\.|### GFramework\\.SourceGenerators|GFramework\\.SourceGenerators 自动生成|`GFramework\\.SourceGenerators` 现在还会分析" docs/zh-CN` +- 结果: + - 通过 + - `docs/zh-CN/**` 已不再残留上述旧公开命名空间与旧聚合说明 + +### 下一步 + +1. 若继续文档主线,优先再扫 `docs/zh-CN/api-reference` 与教程入口页,补齐仍过时的 API / 命名空间表述 +2. 若切回实现主线,重新盘点 `GFramework.Cqrs` 当前剩余的反射绑定点,并只选择收益明确的优化项推进 + +## 2026-04-19 + +### 阶段:PR review 技能接入与 PR-253 follow-up(RP-042) + +- 建立 `CQRS-REWRITE-RP-042` 恢复点 +- 新增项目级 skill `.codex/skills/gframework-pr-review/`: + - 暗号为 `$gframework-pr-review` + - 使用 Windows Git 解析当前分支,并通过 GitHub PR API 定位当前分支对应的 PR + - 通过 GitHub issue comments / reviews / review comments API 提取 `Summary by CodeRabbit`、最新 head + commit review threads、`Failed checks` 与 CTRF 测试结果 + - 不再把重型 PR HTML 页面作为主数据源,只在调试或兼容场景下保留为兜底思路 + - 不依赖 `gh` CLI,也不要求登录态;脚本会显式绕过当前 shell 中失效的代理变量 +- 用新脚本验证了 PR `#253` 的当前状态: + - latest head commit review threads 已可直接从 API 提取;在远端最新提交未更新前,当前仍显示 4 条 open + threads,其中 2 条落在 `fetch_current_pr_review.py`、2 条落在 `ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md` + - PR 页面当前无 `Failed Tests`,CTRF 测试报告显示 `2103 passed / 0 failed` + - `Failed checks` 当前可稳定提取到 `Docstring Coverage` warning;该项属于 PR 级文档注释覆盖率问题,不是 FPR + skill 解析链路故障 +- 已按 PR `#253` 的公开建议完成本地修正: + - `gframework-boot` 的恢复 heuristics 改为“先检索 `ai-plan/`,再判定 `resume` 或 `recovery`” + - `AGENTS.md` 将 `ai-libs/**` 观察写入 active plan/trace 的要求收窄到“多步/复杂任务或已有 active tracking document” + - `Godot` 模板与 `IController` 文档注释中的旧 + `GFramework.SourceGenerators.Abstractions.Rule` 引用已收口到 + `GFramework.Core.SourceGenerators.Abstractions.Rule` + +### 验证 + +- `python3 - <<'PY' ... ast.parse(...) ... PY` + - 结果:通过 + - 备注:`fetch_current_pr_review.py` 语法正确,且避免了只读文件系统下写 `__pycache__` 的问题 +- `python3 .codex/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --pr 253` + - 结果:通过 + - 备注:成功通过 API-first 路径解析当前 PR 元数据、latest head commit review threads、`Docstring Coverage` + warning 和 CTRF 测试报告 +- `python3 .codex/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --branch feat/cqrs-optimization` + - 结果:通过 + - 备注:验证 branch -> PR 解析也已摆脱 HTML 搜索 +- `dotnet build GFramework.Core.Abstractions/GFramework.Core.Abstractions.csproj -c Release` + - 结果:通过 + - 备注:`GFramework.Cqrs.Abstractions` 与 `GFramework.Core.Abstractions` 均成功构建,0 warning / 0 error +- `rg -n "GFramework\\.SourceGenerators\\.Abstractions\\.Rule" Godot GFramework.Core.Abstractions docs/zh-CN -g '*.cs' -g '*.md'` + - 结果:通过 + - 备注:本轮目标范围内已无旧 `Rule` 命名空间残留 + +### 下一步 + +1. 若继续沿用当前 PR 驱动修复流程,可直接用 `$gframework-pr-review` 复查后续 PR 的 CodeRabbit 评论与测试状态 +2. 若要验证本轮本地修正已经消除远端 latest head review threads,需要在提交并推送当前分支后重新执行 `$gframework-pr-review` + +## 2026-04-19 + +### 阶段:PR review 复核与 FPR 输出澄清(RP-043) + +- 建立 `CQRS-REWRITE-RP-043` 恢复点 +- 复核 PR `#253` 时确认: + - `ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md` 的“下次恢复建议”仍把主线写成 `Phase 7` + - 这与 tracking 顶部当前阶段 `Phase 8` 不一致,恢复时会造成执行顺序歧义 +- 进一步检查 `$gframework-pr-review` 脚本后确认: + - 该评论并非“未覆盖”,而是已出现在 latest head commit 的 open review threads 中 + - 我先前误把评论正文里可见的 `Addressed in commit ...` 文案当成了“已经本地验证修复” + - 实际脚本只会在 CodeRabbit thread 带有隐藏 marker `` 时才将状态归类为 `addressed` +- 已据此完成两项修正: + - 将 tracking 中的恢复主线更新为 `Phase 8(当前主线)` + - 调整 `fetch_current_pr_review.py` 输出:当 open thread 里出现 `Addressed in commit ...` 文案但线程并未真正 closed/addressed 时,显式提示“仍需本地验证,不要视为已解决” + +### 验证 + +- `sed -n '469,475p' ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md` + - 结果:通过 + - 备注:恢复建议已切到 `Phase 8(当前主线)` +- `python3 -m py_compile .codex/skills/gframework-pr-review/scripts/fetch_current_pr_review.py` + - 结果:通过 + - 备注:脚本语法正确 + +### 下一步 + +1. 推送当前分支后重新执行 `$gframework-pr-review`,确认 PR `#253` 的 latest head review threads 是否已收敛 +2. 若后续仍保留 CodeRabbit 的 `Addressed in commit ...` 文案但 thread 继续 open,优先以 latest thread 状态和本地文件事实为准,不再按文案字面判定 diff --git a/docs/zh-CN/api-reference/index.md b/docs/zh-CN/api-reference/index.md index 6ecbafe8..394ed10a 100644 --- a/docs/zh-CN/api-reference/index.md +++ b/docs/zh-CN/api-reference/index.md @@ -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 包。 #### 支持的生成器 diff --git a/docs/zh-CN/best-practices/architecture-patterns.md b/docs/zh-CN/best-practices/architecture-patterns.md index c60930e9..726d1d6a 100644 --- a/docs/zh-CN/best-practices/architecture-patterns.md +++ b/docs/zh-CN/best-practices/architecture-patterns.md @@ -657,7 +657,7 @@ public partial class UIController : IController ### 事件组合 ```csharp -using GFramework.SourceGenerators.Abstractions.Rule; +using GFramework.Core.SourceGenerators.Abstractions.Rule; // 使用 OrEvent 组合多个事件 [ContextAware] diff --git a/docs/zh-CN/best-practices/index.md b/docs/zh-CN/best-practices/index.md index c11f8fb7..3379dab3 100644 --- a/docs/zh-CN/best-practices/index.md +++ b/docs/zh-CN/best-practices/index.md @@ -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 diff --git a/docs/zh-CN/best-practices/performance.md b/docs/zh-CN/best-practices/performance.md index 9aac68b3..975e8d7f 100644 --- a/docs/zh-CN/best-practices/performance.md +++ b/docs/zh-CN/best-practices/performance.md @@ -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] diff --git a/docs/zh-CN/core/command.md b/docs/zh-CN/core/command.md index 50303cde..d18b5513 100644 --- a/docs/zh-CN/core/command.md +++ b/docs/zh-CN/core/command.md @@ -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 // 使用命令 using GFramework.Core.Abstractions.Controller; -using GFramework.SourceGenerators.Abstractions.Rule; +using GFramework.Core.SourceGenerators.Abstractions.Rule; [ContextAware] public partial class GameController : IController diff --git a/docs/zh-CN/core/configuration.md b/docs/zh-CN/core/configuration.md index d71d3193..9e74e480 100644 --- a/docs/zh-CN/core/configuration.md +++ b/docs/zh-CN/core/configuration.md @@ -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 diff --git a/docs/zh-CN/core/events.md b/docs/zh-CN/core/events.md index 71228702..8edac1ec 100644 --- a/docs/zh-CN/core/events.md +++ b/docs/zh-CN/core/events.md @@ -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 diff --git a/docs/zh-CN/core/pause.md b/docs/zh-CN/core/pause.md index 344a4584..bb3e2dca 100644 --- a/docs/zh-CN/core/pause.md +++ b/docs/zh-CN/core/pause.md @@ -109,7 +109,7 @@ event EventHandler? 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 diff --git a/docs/zh-CN/core/property.md b/docs/zh-CN/core/property.md index b8c6c678..caf17976 100644 --- a/docs/zh-CN/core/property.md +++ b/docs/zh-CN/core/property.md @@ -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 diff --git a/docs/zh-CN/core/query.md b/docs/zh-CN/core/query.md index 566de072..f618bcf0 100644 --- a/docs/zh-CN/core/query.md +++ b/docs/zh-CN/core/query.md @@ -145,7 +145,7 @@ public class LoadPlayerDataQuery : AbstractAsyncQuery ```csharp using GFramework.Core.Abstractions.bases; -using GFramework.SourceGenerators.Abstractions.bases; +using GFramework.Core.SourceGenerators.Abstractions.Bases; // 使用 Priority 特性设置优先级 [Priority(10)] // 高优先级,先执行 diff --git a/docs/zh-CN/game/data.md b/docs/zh-CN/game/data.md index cd4b96e8..df65b706 100644 --- a/docs/zh-CN/game/data.md +++ b/docs/zh-CN/game/data.md @@ -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 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 diff --git a/docs/zh-CN/game/scene.md b/docs/zh-CN/game/scene.md index 60ccef44..9f79c2af 100644 --- a/docs/zh-CN/game/scene.md +++ b/docs/zh-CN/game/scene.md @@ -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 diff --git a/docs/zh-CN/game/serialization.md b/docs/zh-CN/game/serialization.md index b7f1af23..b1a827bb 100644 --- a/docs/zh-CN/game/serialization.md +++ b/docs/zh-CN/game/serialization.md @@ -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 diff --git a/docs/zh-CN/game/ui.md b/docs/zh-CN/game/ui.md index 5e08b189..0d9df87c 100644 --- a/docs/zh-CN/game/ui.md +++ b/docs/zh-CN/game/ui.md @@ -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 diff --git a/docs/zh-CN/godot/architecture.md b/docs/zh-CN/godot/architecture.md index 7574353d..b5efb31d 100644 --- a/docs/zh-CN/godot/architecture.md +++ b/docs/zh-CN/godot/architecture.md @@ -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] diff --git a/docs/zh-CN/source-generators/auto-register-module-generator.md b/docs/zh-CN/source-generators/auto-register-module-generator.md index 241e6ed2..d216f4e4 100644 --- a/docs/zh-CN/source-generators/auto-register-module-generator.md +++ b/docs/zh-CN/source-generators/auto-register-module-generator.md @@ -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 { diff --git a/docs/zh-CN/source-generators/auto-ui-page-generator.md b/docs/zh-CN/source-generators/auto-ui-page-generator.md index 1c1ecd48..3b1f6a41 100644 --- a/docs/zh-CN/source-generators/auto-ui-page-generator.md +++ b/docs/zh-CN/source-generators/auto-ui-page-generator.md @@ -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] diff --git a/docs/zh-CN/source-generators/bind-node-signal-generator.md b/docs/zh-CN/source-generators/bind-node-signal-generator.md index 9a106318..bdec6290 100644 --- a/docs/zh-CN/source-generators/bind-node-signal-generator.md +++ b/docs/zh-CN/source-generators/bind-node-signal-generator.md @@ -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] diff --git a/docs/zh-CN/source-generators/context-aware-generator.md b/docs/zh-CN/source-generators/context-aware-generator.md index ed00c9b8..9574f603 100644 --- a/docs/zh-CN/source-generators/context-aware-generator.md +++ b/docs/zh-CN/source-generators/context-aware-generator.md @@ -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 diff --git a/docs/zh-CN/source-generators/context-get-generator.md b/docs/zh-CN/source-generators/context-get-generator.md index 831d20c6..7d9f2b0b 100644 --- a/docs/zh-CN/source-generators/context-get-generator.md +++ b/docs/zh-CN/source-generators/context-get-generator.md @@ -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` 的使用点是否存在静态可见注册。 当前支持的使用点: diff --git a/docs/zh-CN/source-generators/enum-generator.md b/docs/zh-CN/source-generators/enum-generator.md index 26c0f955..46450112 100644 --- a/docs/zh-CN/source-generators/enum-generator.md +++ b/docs/zh-CN/source-generators/enum-generator.md @@ -12,7 +12,7 @@ ## 基础使用 ```csharp -using GFramework.SourceGenerators.Abstractions.Enums; +using GFramework.Core.SourceGenerators.Abstractions.Enums; [GenerateEnumExtensions] public enum GameState diff --git a/docs/zh-CN/source-generators/get-node-generator.md b/docs/zh-CN/source-generators/get-node-generator.md index 9c7c9e64..cbbd5834 100644 --- a/docs/zh-CN/source-generators/get-node-generator.md +++ b/docs/zh-CN/source-generators/get-node-generator.md @@ -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] diff --git a/docs/zh-CN/source-generators/logging-generator.md b/docs/zh-CN/source-generators/logging-generator.md index 8a905be1..4e8c1425 100644 --- a/docs/zh-CN/source-generators/logging-generator.md +++ b/docs/zh-CN/source-generators/logging-generator.md @@ -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] diff --git a/docs/zh-CN/source-generators/priority-generator.md b/docs/zh-CN/source-generators/priority-generator.md index 6a4980a2..ef450225 100644 --- a/docs/zh-CN/source-generators/priority-generator.md +++ b/docs/zh-CN/source-generators/priority-generator.md @@ -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 : 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; // 输入系统(最先初始化) diff --git a/docs/zh-CN/tutorials/basic/04-model-refactor.md b/docs/zh-CN/tutorials/basic/04-model-refactor.md index 10dc4074..ff80ea1f 100644 --- a/docs/zh-CN/tutorials/basic/04-model-refactor.md +++ b/docs/zh-CN/tutorials/basic/04-model-refactor.md @@ -187,7 +187,7 @@ architecture.RegisterModel(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; diff --git a/docs/zh-CN/tutorials/basic/05-command-system.md b/docs/zh-CN/tutorials/basic/05-command-system.md index 5f320f62..0930c90b 100644 --- a/docs/zh-CN/tutorials/basic/05-command-system.md +++ b/docs/zh-CN/tutorials/basic/05-command-system.md @@ -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; diff --git a/docs/zh-CN/tutorials/godot-complete-project.md b/docs/zh-CN/tutorials/godot-complete-project.md index f033d350..5c0594ac 100644 --- a/docs/zh-CN/tutorials/godot-complete-project.md +++ b/docs/zh-CN/tutorials/godot-complete-project.md @@ -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; diff --git a/docs/zh-CN/tutorials/pause-system.md b/docs/zh-CN/tutorials/pause-system.md index 46277d19..35dc47bd 100644 --- a/docs/zh-CN/tutorials/pause-system.md +++ b/docs/zh-CN/tutorials/pause-system.md @@ -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 diff --git a/docs/zh-CN/tutorials/state-machine-tutorial.md b/docs/zh-CN/tutorials/state-machine-tutorial.md index 45d0dd24..bbea6b5a 100644 --- a/docs/zh-CN/tutorials/state-machine-tutorial.md +++ b/docs/zh-CN/tutorials/state-machine-tutorial.md @@ -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 { diff --git a/third-party-licenses/Mediator/LICENSE b/third-party-licenses/Mediator/LICENSE new file mode 100644 index 00000000..6d202f94 --- /dev/null +++ b/third-party-licenses/Mediator/LICENSE @@ -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.