From 3bc5078d95cd5da98a4a188e8512af0570917dad Mon Sep 17 00:00:00 2001 From: mambo-wang Date: Wed, 26 Aug 2026 20:28:51 +0800 Subject: [PATCH 1/3] chore: remove stray one-off diagnostic script scripts/_tmp2.py Accidentally committed in 99e4c44. It also triggered a ruff formatter panic (Annotation range beyond end of buffer) that forced PR #17 to drop the ruff format CI step. --- scripts/_tmp2.py | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 scripts/_tmp2.py diff --git a/scripts/_tmp2.py b/scripts/_tmp2.py deleted file mode 100644 index c5fc20e..0000000 --- a/scripts/_tmp2.py +++ /dev/null @@ -1,13 +0,0 @@ -import pathlib, re, subprocess -out = subprocess.run(["git","-c","core.quotepath=false","status","--porcelain"], capture_output=True, text=True, encoding="utf-8").stdout -deleted = [l[3:].strip() for l in out.splitlines() if len(l)>3 and "D" in l[:2] and "notes/" in l] -arc_names = {p.name for p in pathlib.Path(".trash/notes-archive").glob("*.md")} -missing = [] -for p in deleted: - name = pathlib.Path(p).name - if name not in arc_names: - missing.append(p) -print("deleted total:", len(deleted)) -print("not in archive (truly deleted):", len(missing)) -for m in missing: - print(" -", m) From 9c9fe58d53404d3cc46c490eb664e13a3ab62032 Mon Sep 17 00:00:00 2001 From: mambo-wang Date: Wed, 26 Aug 2026 20:29:06 +0800 Subject: [PATCH 2/3] style(lint): re-tighten E741/E731 and drop tests F401 exemption (follow-up to #17) PR #17 relaxed the ruff config broadly to unblock CI. This narrows the relaxation to what the codebase actually needs: - pyproject: ignore only E701/E702 (250+ legacy one-line multiple statements, cosmetic); remove E741/E731/E501 from ignore (E501 was never active under the pinned select anyway) and drop the tests/* F401 per-file exemption - fix all 22 E741 sites (rename ambiguous `l` to line/link/node) - fix the single E731 site (lambda -> def in test_existence_filter) - clean 24 unused imports across codewiki/ and tests/ (F401) - drop 2 unused local variables (F841) in analysis.py / smoke_test_mcp.py Full suite: 430 passed, 2 skipped. Touched files are clean under the tightened config; remaining repo-wide violations (E701/E702/E402/etc.) are untouched legacy counts. --- codewiki/mcp/cache.py | 8 ++++---- codewiki/mcp/tools/analysis.py | 19 +++++++++--------- codewiki/mcp/tools/cbm_integration.py | 4 ++-- codewiki/mcp/tools/cross_service.py | 20 +++++++++---------- codewiki/mcp/tools/telemetry.py | 4 ++-- codewiki/mcp/tools/watch.py | 10 +++++----- codewiki/mcp/tools/wiki_search.py | 8 ++++---- .../analysis/topology_visualizer.py | 2 +- pyproject.toml | 10 +++++----- tests/okf_regression_test.py | 1 - tests/smoke_test_mcp.py | 4 +--- tests/test_adoption.py | 9 +++------ tests/test_change_analysis.py | 2 +- tests/test_distill_cleanup.py | 1 - tests/test_freshness.py | 1 - tests/test_friction.py | 1 - tests/test_index_freshness.py | 1 - tests/test_low_adoption.py | 4 ++-- tests/test_openviking_borrowings.py | 3 +-- tests/test_team_telemetry.py | 12 ++++------- tests/test_usage_ranking.py | 1 - 21 files changed, 55 insertions(+), 70 deletions(-) diff --git a/codewiki/mcp/cache.py b/codewiki/mcp/cache.py index eb4725e..031ed88 100644 --- a/codewiki/mcp/cache.py +++ b/codewiki/mcp/cache.py @@ -1894,8 +1894,8 @@ def _parse_row(r: sqlite3.Row) -> Tuple[Set[str], Optional[List], Optional[List] return deps, bc, params def _extract_title(content: str) -> Optional[str]: - for l in content.splitlines()[:30]: - s = l.strip() + for line in content.splitlines()[:30]: + s = line.strip() if s.startswith("# "): return s[2:].strip() return None @@ -1903,7 +1903,7 @@ def _extract_frontmatter(content: str, key: str) -> Optional[str]: if not content.startswith("---"): return None try: end = content.index("---", 3) - for l in content[3:end].splitlines(): - if l.startswith(f"{key}:"): return l[len(key)+1:].strip().strip('"').strip("'") + for line in content[3:end].splitlines(): + if line.startswith(f"{key}:"): return line[len(key)+1:].strip().strip('"').strip("'") except ValueError: pass return None diff --git a/codewiki/mcp/tools/analysis.py b/codewiki/mcp/tools/analysis.py index 94638fc..ea7d8de 100644 --- a/codewiki/mcp/tools/analysis.py +++ b/codewiki/mcp/tools/analysis.py @@ -11,12 +11,14 @@ from __future__ import annotations -import json, logging, os +import json +import logging +import os from pathlib import Path from typing import Any, Dict, List, Optional, Set from codewiki.mcp.cache import AnalysisCache, ComponentMeta, LazyComponentStore -from codewiki.mcp.session import SessionState, SessionStore +from codewiki.mcp.session import SessionStore from codewiki.mcp.workspace import SessionWorkspace logger = logging.getLogger(__name__) @@ -92,7 +94,6 @@ def handle_analyze_repo(arguments: Dict[str, Any], store: SessionStore) -> str: logger.warning("Route removal for %s failed (non-fatal): %s", cf, e) # Compute set of unchanged files to skip during parsing - changed_set = set(changed) all_cached_paths = cache.get_cached_file_paths() # These are the relative paths of files that are still cached and unchanged unchanged_rel_paths = all_cached_paths # After remove_by_file, only unchanged remain @@ -148,16 +149,16 @@ def handle_analyze_repo(arguments: Dict[str, Any], store: SessionStore) -> str: if not available_types & valid_types: valid_types.add("function") leaf_nodes = [ - l for l in raw_leafs - if isinstance(l, str) and l in components - and components[l].component_type in valid_types + n for n in raw_leafs + if isinstance(n, str) and n in components + and components[n].component_type in valid_types ] logger.info("Recomputed %d leaf nodes on merged graph", len(leaf_nodes)) except Exception as e: logger.warning("Leaf-node recompute failed, merging with cached list: %s", e) old_leafs = cache.get_leaf_nodes() - merged = [l for l in old_leafs if l in components] - merged.extend(l for l in leaf_nodes if l not in merged) + merged = [n for n in old_leafs if n in components] + merged.extend(n for n in leaf_nodes if n not in merged) leaf_nodes = merged # Write to SQLite cache (incremental mode if we had cached components) @@ -868,7 +869,7 @@ def _extract_overview_refs(output_dir: Path) -> Set[str]: def _save_overview_refs(output_dir: Path, refs: Set[str]): """Save overview refs to .meta/overview_refs.json.""" - from codewiki.src.config import meta_join, META_DIR + from codewiki.src.config import meta_join meta_dir = Path(meta_join(output_dir, "")) meta_dir.mkdir(parents=True, exist_ok=True) refs_path = meta_dir / "overview_refs.json" diff --git a/codewiki/mcp/tools/cbm_integration.py b/codewiki/mcp/tools/cbm_integration.py index d0b4744..5fd8019 100644 --- a/codewiki/mcp/tools/cbm_integration.py +++ b/codewiki/mcp/tools/cbm_integration.py @@ -193,8 +193,8 @@ def merge_cbm_and_local_results( local_links = local_topology.get("links", []) existing_keys = { - f"{l.get('client_repo')}:{l.get('server_repo')}:{l.get('route_key')}" - for l in local_links + f"{link.get('client_repo')}:{link.get('server_repo')}:{link.get('route_key')}" + for link in local_links } for path_entry in cbm_paths: diff --git a/codewiki/mcp/tools/cross_service.py b/codewiki/mcp/tools/cross_service.py index 65ffeab..bdf953c 100644 --- a/codewiki/mcp/tools/cross_service.py +++ b/codewiki/mcp/tools/cross_service.py @@ -8,7 +8,7 @@ import json import logging from pathlib import Path -from typing import Any, Dict, List, Optional +from typing import Any, Dict, List logger = logging.getLogger(__name__) @@ -113,7 +113,7 @@ def _format_all(links: List[Dict], routes: List[Dict]) -> Dict: "unmatched_routes": [ r for r in routes if not any( - l.get("route_key") == r.get("route_key") for l in links + link.get("route_key") == r.get("route_key") for link in links ) ], } @@ -121,33 +121,33 @@ def _format_all(links: List[Dict], routes: List[Dict]) -> Dict: def _filter_by_service(links: List[Dict], service: str) -> Dict: matching = [ - l for l in links - if service.lower() in l.get("client_repo", "").lower() - or service.lower() in l.get("server_repo", "").lower() + link for link in links + if service.lower() in link.get("client_repo", "").lower() + or service.lower() in link.get("server_repo", "").lower() ] return { "service": service, "count": len(matching), - "as_client": [l for l in matching if service.lower() in l.get("client_repo", "").lower()], - "as_server": [l for l in matching if service.lower() in l.get("server_repo", "").lower()], + "as_client": [link for link in matching if service.lower() in link.get("client_repo", "").lower()], + "as_server": [link for link in matching if service.lower() in link.get("server_repo", "").lower()], } def _filter_by_method(links: List[Dict], method: str) -> Dict: # MQ links serialize method as null — guard against None before .upper() - matching = [l for l in links if (l.get("method") or "").upper() == method.upper()] + matching = [link for link in links if (link.get("method") or "").upper() == method.upper()] return {"method": method.upper(), "count": len(matching), "links": matching} def _filter_by_path(links: List[Dict], path_prefix: str) -> Dict: prefix_lower = path_prefix.lower() - matching = [l for l in links if (l.get("path") or "").lower().startswith(prefix_lower)] + matching = [link for link in links if (link.get("path") or "").lower().startswith(prefix_lower)] return {"path_prefix": path_prefix, "count": len(matching), "links": matching} def _trace_route(links: List[Dict], routes: List[Dict], route_key: str) -> Dict: """Trace a specific route: find all clients and servers involved.""" - matching_links = [l for l in links if l.get("route_key") == route_key] + matching_links = [link for link in links if link.get("route_key") == route_key] matching_routes = [r for r in routes if r.get("route_key") == route_key] clients = [] diff --git a/codewiki/mcp/tools/telemetry.py b/codewiki/mcp/tools/telemetry.py index 5596364..2d2a5b3 100644 --- a/codewiki/mcp/tools/telemetry.py +++ b/codewiki/mcp/tools/telemetry.py @@ -39,7 +39,7 @@ import os from datetime import date, datetime from pathlib import Path -from typing import Dict, List, Optional, Set, Tuple +from typing import Dict, List, Set, Tuple logger = logging.getLogger(__name__) @@ -127,7 +127,7 @@ def _atomic_write_lines(path: Path, lines: List[str]) -> None: def _read_lines(path: Path) -> List[str]: """Non-empty lines of a jsonl file; missing file → [] (never raises).""" try: - return [l for l in path.read_text(encoding="utf-8", errors="replace").splitlines() if l.strip()] + return [line for line in path.read_text(encoding="utf-8", errors="replace").splitlines() if line.strip()] except OSError: return [] diff --git a/codewiki/mcp/tools/watch.py b/codewiki/mcp/tools/watch.py index 1a90188..886b57a 100644 --- a/codewiki/mcp/tools/watch.py +++ b/codewiki/mcp/tools/watch.py @@ -230,11 +230,11 @@ def _incremental_refresh( if not available_types & valid_types: valid_types.add("function") leaf_nodes = [ - l - for l in raw_leafs - if isinstance(l, str) - and l in components - and components[l].component_type in valid_types + n + for n in raw_leafs + if isinstance(n, str) + and n in components + and components[n].component_type in valid_types ] except Exception as exc: logger.warning("watch: leaf-node recompute failed, keeping builder list: %s", exc) diff --git a/codewiki/mcp/tools/wiki_search.py b/codewiki/mcp/tools/wiki_search.py index fb3cc08..346947c 100644 --- a/codewiki/mcp/tools/wiki_search.py +++ b/codewiki/mcp/tools/wiki_search.py @@ -171,8 +171,8 @@ def _extract_fm(ct, key): if not ct.startswith("---"): return None try: end = ct.index("---", 3) - for l in ct[3:end].splitlines(): - if l.startswith(f"{key}:"): return l[len(key)+1:].strip().strip('"').strip("'") + for line in ct[3:end].splitlines(): + if line.startswith(f"{key}:"): return line[len(key)+1:].strip().strip('"').strip("'") except ValueError: pass return None @@ -180,8 +180,8 @@ def _extract_fm(ct, key): _MD_LINK_RE = re.compile(r"\[([^\]]*)\]\([^)]*\)") def _extract_title(ct): - for l in ct.splitlines()[:30]: - s = l.strip() + for line in ct.splitlines()[:30]: + s = line.strip() if s.startswith("# "): title = _MD_LINK_RE.sub(lambda m: m.group(1), s[2:]).strip() return title or None diff --git a/codewiki/src/be/dependency_analyzer/analysis/topology_visualizer.py b/codewiki/src/be/dependency_analyzer/analysis/topology_visualizer.py index f4d0fb8..3b085d6 100644 --- a/codewiki/src/be/dependency_analyzer/analysis/topology_visualizer.py +++ b/codewiki/src/be/dependency_analyzer/analysis/topology_visualizer.py @@ -141,7 +141,7 @@ def generate_route_table(self, topology: WorkspaceTopology) -> str: "| Method | Path | Client Service | Client Function | Server Service | Server Function |", "|--------|------|----------------|-----------------|----------------|-----------------|", ] - for link in sorted(topology.links, key=lambda l: (l.client_repo, l.path)): + for link in sorted(topology.links, key=lambda link: (link.client_repo, link.path)): method = link.method or "—" path = link.path or "—" client_func = link.client_function or "—" diff --git a/pyproject.toml b/pyproject.toml index 63ce276..a3f4b4b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -126,11 +126,11 @@ target-version = "py312" # which most of the existing codebase does not satisfy — see CI failures on # c22fc26/99e4c44). Widen deliberately, not by upgrade accident. select = ["E4", "E7", "E9", "F"] -# Relaxed: cosmetic E7 sub-rules and E501 are style-only; keep F/E9/E4 for bugs -ignore = ["E741", "E731", "E701", "E702", "E501"] - -[tool.ruff.lint.per-file-ignores] -"tests/*" = ["F401"] +# E701/E702 (multiple statements on one line) are style-only with 250+ legacy +# occurrences — relaxed in #17 until a dedicated cleanup pass. +# E741/E731 were re-tightened after fixing all violations (post-#17 cleanup); +# E501 is never active under this select, so it needs no ignore. +ignore = ["E701", "E702"] [tool.pytest.ini_options] testpaths = ["tests"] diff --git a/tests/okf_regression_test.py b/tests/okf_regression_test.py index 0216a9a..c789e79 100644 --- a/tests/okf_regression_test.py +++ b/tests/okf_regression_test.py @@ -33,7 +33,6 @@ handle_edit_doc_file, handle_write_doc_file, _inject_lightweight_frontmatter, - _resync_source_refs, ) from codewiki.mcp.tools.knowledge_loop import ( # noqa: E402 handle_confirm_note, diff --git a/tests/smoke_test_mcp.py b/tests/smoke_test_mcp.py index 88e828e..a22484a 100644 --- a/tests/smoke_test_mcp.py +++ b/tests/smoke_test_mcp.py @@ -7,7 +7,6 @@ import asyncio import json -import os import sys import tempfile from pathlib import Path @@ -15,7 +14,7 @@ # Ensure codewiki is importable sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) -from codewiki.mcp.session import SessionStore, SessionState +from codewiki.mcp.session import SessionStore from codewiki.mcp.tools.analysis import handle_analyze_repo from codewiki.mcp.tools.code_reader import handle_read_code_components from codewiki.mcp.tools.doc_writer import handle_write_doc_file, handle_edit_doc_file @@ -89,7 +88,6 @@ def main(): check("summary has total_components", "total_components" in summary, str(summary.keys())) check("summary has total_leaf_nodes", "total_leaf_nodes" in summary, str(summary.keys())) check("summary has languages", "languages" in summary, str(summary.keys())) - total_leaf = summary["total_leaf_nodes"] # Use list_components tool to get component index lc_result = json.loads(handle_list_components({ diff --git a/tests/test_adoption.py b/tests/test_adoption.py index ebfd740..3aea116 100644 --- a/tests/test_adoption.py +++ b/tests/test_adoption.py @@ -13,17 +13,13 @@ from __future__ import annotations import json -import math -from pathlib import Path -import pytest from codewiki.mcp.cache import USAGE_RANKING_DEFAULTS, compute_usage_heat from codewiki.mcp.session import SessionStore from codewiki.mcp.tools.adoption import ( extract_adopted_docs, load_adoption_counts, - looks_like_search_happened, record_adoption_events, ) from codewiki.mcp.tools.capture_conversation import handle_capture_conversation @@ -90,8 +86,9 @@ def test_existence_filter(self): turns = _turns( '', ) - exists = lambda p: p == "exists.md" - assert extract_adopted_docs(turns, existing=exists) == ["exists.md"] + def _exists(p): + return p == "exists.md" + assert extract_adopted_docs(turns, existing=_exists) == ["exists.md"] def test_prose_mention_does_not_match(self): turns = _turns( diff --git a/tests/test_change_analysis.py b/tests/test_change_analysis.py index 6624c50..720b466 100644 --- a/tests/test_change_analysis.py +++ b/tests/test_change_analysis.py @@ -11,7 +11,7 @@ from __future__ import annotations import json -from typing import Any, Dict, List +from typing import Any, Dict import git import pytest diff --git a/tests/test_distill_cleanup.py b/tests/test_distill_cleanup.py index 48dc9e4..bef80a7 100644 --- a/tests/test_distill_cleanup.py +++ b/tests/test_distill_cleanup.py @@ -7,7 +7,6 @@ import json from pathlib import Path -import pytest from codewiki.mcp.session import SessionStore from codewiki.mcp.tools import distill_conversation as distill diff --git a/tests/test_freshness.py b/tests/test_freshness.py index 120b38b..6ea37dc 100644 --- a/tests/test_freshness.py +++ b/tests/test_freshness.py @@ -15,7 +15,6 @@ from datetime import datetime, timedelta from pathlib import Path -import pytest import yaml from codewiki.mcp.session import SessionStore diff --git a/tests/test_friction.py b/tests/test_friction.py index 1ee1362..08819f4 100644 --- a/tests/test_friction.py +++ b/tests/test_friction.py @@ -18,7 +18,6 @@ import sys from pathlib import Path -import pytest from codewiki.mcp.session import SessionStore from codewiki.mcp.tools import capture_conversation as capture diff --git a/tests/test_index_freshness.py b/tests/test_index_freshness.py index 89b1a5d..5125a29 100644 --- a/tests/test_index_freshness.py +++ b/tests/test_index_freshness.py @@ -18,7 +18,6 @@ import time from pathlib import Path -import pytest from codewiki.mcp.session import SessionStore from codewiki.mcp.tools import index_freshness as fr diff --git a/tests/test_low_adoption.py b/tests/test_low_adoption.py index 13aed67..d0cca6b 100644 --- a/tests/test_low_adoption.py +++ b/tests/test_low_adoption.py @@ -63,8 +63,8 @@ def _append_events(od: Path, events: list) -> None: if p.exists(): import json as _json existing = [ - _json.loads(l) for l in p.read_text(encoding="utf-8").splitlines() - if l.strip() + _json.loads(line) for line in p.read_text(encoding="utf-8").splitlines() + if line.strip() ] write_telemetry(od, "tester", existing + events) diff --git a/tests/test_openviking_borrowings.py b/tests/test_openviking_borrowings.py index 834a66a..c03c8a3 100644 --- a/tests/test_openviking_borrowings.py +++ b/tests/test_openviking_borrowings.py @@ -11,7 +11,6 @@ import sys from pathlib import Path -import pytest sys.path.insert(0, str(Path(__file__).resolve().parents[1])) @@ -20,7 +19,7 @@ def test_v4_table_single_source(): from codewiki.mcp.tools.note_types import ( - DEFAULT_NOTE_TYPES, valid_note_types, promotion_targets, + DEFAULT_NOTE_TYPES, promotion_targets, freshness_windows, merge_fields_for, validate_note_types, ) from codewiki.mcp.tools.distill_conversation import _VALID_NOTE_TYPES diff --git a/tests/test_team_telemetry.py b/tests/test_team_telemetry.py index 83d5bee..41037d4 100644 --- a/tests/test_team_telemetry.py +++ b/tests/test_team_telemetry.py @@ -12,18 +12,14 @@ from __future__ import annotations import json -import os from datetime import datetime, timedelta from pathlib import Path -import pytest from codewiki.mcp.session import SessionStore -from codewiki.mcp.tools import telemetry from codewiki.mcp.tools.capture_conversation import handle_capture_conversation from codewiki.mcp.tools.telemetry import ( aggregate_usage, - record_adopted, record_hit, telemetry_enabled, ) @@ -37,8 +33,8 @@ def _all_events(od: Path) -> list: out = [] for p in sorted(d.glob("*.jsonl")): out.extend( - json.loads(l) - for l in p.read_text(encoding="utf-8").splitlines() if l.strip() + json.loads(line) + for line in p.read_text(encoding="utf-8").splitlines() if line.strip() ) return out @@ -111,7 +107,7 @@ def test_adoption_key_dedup(self, tmp_path): {"t": "adopted", "doc": "notes/a.md", "at": "z", "key": "u/s2"}, ] (p / "u.jsonl").write_text( - "\n".join(json.dumps(l) for l in lines) + "\n", encoding="utf-8") + "\n".join(json.dumps(e) for e in lines) + "\n", encoding="utf-8") agg = aggregate_usage(tmp_path) assert agg["notes/a.md"]["adopted"] == 2 # distinct keys only @@ -198,5 +194,5 @@ def test_adoption_declared_in_conversation_recorded(self, tmp_path): # event landed in the CURRENT user's stream (user-dependent name) files = list((od / ".meta" / "telemetry").glob("*.jsonl")) assert len(files) == 1 - events = [json.loads(l) for l in files[0].read_text(encoding="utf-8").splitlines() if l.strip()] + events = [json.loads(line) for line in files[0].read_text(encoding="utf-8").splitlines() if line.strip()] assert any(e["t"] == "adopted" and e["doc"] == "notes/pit.md" for e in events) diff --git a/tests/test_usage_ranking.py b/tests/test_usage_ranking.py index 013bb09..4c8167b 100644 --- a/tests/test_usage_ranking.py +++ b/tests/test_usage_ranking.py @@ -16,7 +16,6 @@ import inspect import math -import sqlite3 from datetime import date, datetime, timedelta from pathlib import Path From bca885cf833e0916be8d785f90e0e57fb2d1970b Mon Sep 17 00:00:00 2001 From: mambo-wang Date: Wed, 26 Aug 2026 21:06:16 +0800 Subject: [PATCH 3/3] =?UTF-8?q?docs:=20README=20=E8=A1=A5=E5=85=85=20CodeW?= =?UTF-8?q?iki-Plus=20=E7=B3=BB=E5=88=97=E7=AC=AC=207/8=20=E7=AF=87?= =?UTF-8?q?=E6=96=87=E7=AB=A0=E9=93=BE=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 2cde55f..990dd0a 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,9 @@ - [第 4 篇:知识写入方式全景](https://mp.weixin.qq.com/s/V90mghqB5wttKd25eXA-Pw)(2026-08-09) - [第 5 篇:OKF 0.2 规范介绍和实战](https://mp.weixin.qq.com/s/Dt748cHQCa7mfz1PEvgS6g)(2026-08) - [第 6 篇:借助 HOOKS 机制实现跨会话记忆和任务管理](https://mp.weixin.qq.com/s/flsqORauNo0Th1v8G4Ceng)(2026-08) +- [第 7 篇:记忆/经验分层提取——自生长的团队知识库](https://mp.weixin.qq.com/s/s253xe5LiUmgdfDo3XxAbg)(2026-08) +- [第 8 篇:四维代码评审——让踩过的坑自动变成 CHECKLIST](https://mp.weixin.qq.com/s/wH_mjG5IL-0qo_qDFpODuw)(2026-08) + ### 这个项目是什么? @@ -766,6 +769,10 @@ CodeWiki-Plus 的核心工具链(Tree-sitter AST 解析、依赖图构建、 } ``` +

+ Thank You +

+ ---