@hupan56/wlkj 2.7.11 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/bin/cli.js +375 -78
  2. package/package.json +29 -29
  3. package/templates/.qoder/.runtime/ctx-cache-5660152f1d6dd819.md +23 -0
  4. package/templates/.qoder/.runtime/ctx-cache-afdce0dac06b25b0.md +23 -0
  5. package/templates/.qoder/.runtime/search-cache-eae7644e7b122f35.txt +1 -0
  6. package/templates/.qoder/learning/eval-history.jsonl +28 -0
  7. package/templates/data/index/wiki-index.json +8 -0
  8. package/templates/qoder/agents/insight-planning.md +1 -1
  9. package/templates/qoder/agents/insight-research.md +28 -15
  10. package/templates/qoder/commands/optional/wl-insight.md +159 -161
  11. package/templates/qoder/commands/optional/wl-report.md +4 -4
  12. package/templates/qoder/commands/optional/wl-status.md +2 -2
  13. package/templates/qoder/commands/wl-code.md +2 -2
  14. package/templates/qoder/commands/wl-design.md +2 -2
  15. package/templates/qoder/commands/wl-init.md +3 -3
  16. package/templates/qoder/commands/wl-prd.md +2 -2
  17. package/templates/qoder/commands/wl-req.md +43 -0
  18. package/templates/qoder/commands/wl-search.md +8 -8
  19. package/templates/qoder/commands/wl-task.md +17 -17
  20. package/templates/qoder/commands/wl-test.md +41 -15
  21. package/templates/qoder/config.yaml +17 -1
  22. package/templates/qoder/hooks/session-start.py +12 -22
  23. package/templates/qoder/nul +4 -0
  24. package/templates/qoder/rules/wl-pipeline.md +22 -48
  25. package/templates/qoder/scripts/README.md +139 -0
  26. package/templates/qoder/scripts/common/autotest_auth.py +109 -0
  27. package/templates/qoder/scripts/common/bootstrap.py +145 -0
  28. package/templates/qoder/scripts/common/check_publish.py +98 -0
  29. package/templates/qoder/scripts/common/cmd_registry.py +112 -0
  30. package/templates/qoder/scripts/common/config.py +187 -0
  31. package/templates/qoder/scripts/common/contract.py +317 -0
  32. package/templates/qoder/scripts/common/developer.py +2 -1
  33. package/templates/qoder/scripts/common/feishu.py +10 -9
  34. package/templates/qoder/scripts/common/guard.py +159 -0
  35. package/templates/qoder/scripts/common/identity.py +121 -2
  36. package/templates/qoder/scripts/common/kg_capabilities.py +182 -0
  37. package/templates/qoder/scripts/common/mcp_base.py +268 -0
  38. package/templates/qoder/scripts/common/paths.py +187 -1
  39. package/templates/qoder/scripts/common/result.py +223 -0
  40. package/templates/qoder/scripts/common/roles.py +60 -0
  41. package/templates/qoder/scripts/common/task_utils.py +21 -9
  42. package/templates/qoder/scripts/common/test_extract.py +115 -0
  43. package/templates/qoder/scripts/kg/__init__.py +11 -0
  44. package/templates/qoder/scripts/kg/build_entity_registry.py +196 -0
  45. package/templates/qoder/scripts/kg/build_relations.py +127 -0
  46. package/templates/qoder/scripts/{build_style_index.py → kg/build_style_index.py} +39 -10
  47. package/templates/qoder/scripts/kg/build_workflows.py +144 -0
  48. package/templates/qoder/scripts/{context_pack.py → kg/context_pack.py} +18 -11
  49. package/templates/qoder/scripts/{enrich_prompt.py → kg/enrich_prompt.py} +232 -226
  50. package/templates/qoder/scripts/{extract_api_params.py → kg/extract.py} +398 -246
  51. package/templates/qoder/scripts/{kg.py → kg/kg.py} +638 -708
  52. package/templates/qoder/scripts/{kg_build.py → kg/kg_build.py} +618 -612
  53. package/templates/qoder/scripts/{kg_build_db.py → kg/kg_build_db.py} +333 -327
  54. package/templates/qoder/scripts/{kg_duckdb.py → kg/kg_duckdb.py} +38 -37
  55. package/templates/qoder/scripts/{kg_incremental.py → kg/kg_incremental.py} +420 -393
  56. package/templates/qoder/scripts/{kg_link_db.py → kg/kg_link_db.py} +230 -224
  57. package/templates/qoder/scripts/{kg_semantic.py → kg/kg_semantic.py} +156 -150
  58. package/templates/qoder/scripts/kg/prefetch.py +359 -0
  59. package/templates/qoder/scripts/{search_index.py → kg/search_index.py} +70 -14
  60. package/templates/qoder/scripts/mcp/__init__.py +11 -0
  61. package/templates/qoder/scripts/{kg_mcp_server.py → mcp/kg_mcp_server.py} +77 -272
  62. package/templates/qoder/scripts/{lanhu_stdio_wrapper.py → mcp/lanhu_stdio_wrapper.py} +125 -119
  63. package/templates/qoder/scripts/{check_mcp.py → mcp/mcp_doctor.py} +515 -298
  64. package/templates/qoder/scripts/{mcp_launcher.py → mcp/mcp_launcher.py} +442 -414
  65. package/templates/qoder/scripts/{mysql_mcp_server.py → mcp/mysql_mcp_server.py} +347 -396
  66. package/templates/qoder/scripts/{zentao_mcp_server.py → mcp/zentao_mcp_server.py} +384 -424
  67. package/templates/qoder/scripts/report/__init__.py +11 -0
  68. package/templates/qoder/scripts/{add_session.py → report/add_session.py} +250 -244
  69. package/templates/qoder/scripts/{archive_prd.py → report/archive_prd.py} +383 -377
  70. package/templates/qoder/scripts/{eval_prd.py → report/eval_prd.py} +73 -11
  71. package/templates/qoder/scripts/{export.py → report/export.py} +63 -0
  72. package/templates/qoder/scripts/{fill_prototype.py → report/fill_prototype.py} +6 -0
  73. package/templates/qoder/scripts/{gen_design_doc.py → report/gen_design_doc.py} +400 -394
  74. package/templates/qoder/scripts/{learn.py → report/learn.py} +152 -146
  75. package/templates/qoder/scripts/{learn_aggregate.py → report/learn_aggregate.py} +207 -201
  76. package/templates/qoder/scripts/{report.py → report/report.py} +287 -281
  77. package/templates/qoder/scripts/report/req.py +222 -0
  78. package/templates/qoder/scripts/report/role.py +33 -0
  79. package/templates/qoder/scripts/{status.py → report/status.py} +634 -628
  80. package/templates/qoder/scripts/setup/__init__.py +11 -0
  81. package/templates/qoder/scripts/setup/carriers.py +662 -0
  82. package/templates/qoder/scripts/{init_doctor.py → setup/init_doctor.py} +63 -26
  83. package/templates/qoder/scripts/{install_qoderwork.py → setup/install_qoderwork.py} +36 -26
  84. package/templates/qoder/scripts/{platform_doctor.py → setup/platform_doctor.py} +265 -259
  85. package/templates/qoder/scripts/{repo_root.py → setup/repo_root.py} +112 -106
  86. package/templates/qoder/scripts/{setup.py → setup/setup.py} +147 -0
  87. package/templates/qoder/scripts/{setup_lanhu.py → setup/setup_lanhu.py} +973 -963
  88. package/templates/qoder/scripts/task/__init__.py +11 -0
  89. package/templates/qoder/scripts/{git_sync.py → task/git_sync.py} +52 -31
  90. package/templates/qoder/scripts/{syncgate.py → task/syncgate.py} +6 -0
  91. package/templates/qoder/scripts/task/task.py +221 -0
  92. package/templates/qoder/scripts/task/task_lifecycle.py +596 -0
  93. package/templates/qoder/scripts/task/task_query.py +161 -0
  94. package/templates/qoder/scripts/task/task_relations.py +424 -0
  95. package/templates/qoder/scripts/{team_sync.py → task/team_sync.py} +93 -20
  96. package/templates/qoder/scripts/test/__init__.py +11 -0
  97. package/templates/qoder/scripts/{autotest.py → test/autotest.py} +1174 -1751
  98. package/templates/qoder/scripts/{autotest_batch.py → test/autotest_batch.py} +242 -224
  99. package/templates/qoder/scripts/test/autotest_data.py +675 -0
  100. package/templates/qoder/scripts/{autotest_run.py → test/autotest_run.py} +309 -297
  101. package/templates/qoder/scripts/{benchmark.py → test/benchmark.py} +6 -0
  102. package/templates/qoder/scripts/{kg_auto_login.py → test/kg_auto_login.py} +202 -196
  103. package/templates/qoder/scripts/{kg_test_runner.py → test/kg_test_runner.py} +7 -1
  104. package/templates/qoder/scripts/{page_probe.py → test/page_probe.py} +465 -459
  105. package/templates/qoder/scripts/wlkj.py +116 -0
  106. package/templates/qoder/settings.json +1 -10
  107. package/templates/qoder/skills/design-import/SKILL.md +226 -226
  108. package/templates/qoder/skills/design-review/SKILL.md +82 -82
  109. package/templates/qoder/skills/prd-generator/SKILL.md +26 -16
  110. package/templates/qoder/skills/prd-review/SKILL.md +5 -5
  111. package/templates/qoder/skills/prototype-generator/SKILL.md +256 -256
  112. package/templates/qoder/skills/spec-coder/SKILL.md +4 -4
  113. package/templates/qoder/skills/spec-generator/SKILL.md +4 -4
  114. package/templates/qoder/skills/test-generator/SKILL.md +5 -5
  115. package/templates/qoder/skills/wl-code/SKILL.md +4 -4
  116. package/templates/qoder/skills/wl-commit/SKILL.md +4 -4
  117. package/templates/qoder/skills/wl-design/SKILL.md +3 -3
  118. package/templates/qoder/skills/wl-init/SKILL.md +8 -8
  119. package/templates/qoder/skills/wl-insight/SKILL.md +5 -5
  120. package/templates/qoder/skills/wl-prd-full/SKILL.md +6 -6
  121. package/templates/qoder/skills/wl-prd-quick/SKILL.md +6 -6
  122. package/templates/qoder/skills/wl-prd-review/SKILL.md +4 -4
  123. package/templates/qoder/skills/wl-report/SKILL.md +7 -7
  124. package/templates/qoder/skills/wl-search/SKILL.md +13 -13
  125. package/templates/qoder/skills/wl-spec/SKILL.md +5 -5
  126. package/templates/qoder/skills/wl-status/SKILL.md +5 -5
  127. package/templates/qoder/skills/wl-task/SKILL.md +6 -6
  128. package/templates/qoder/skills/wl-test/SKILL.md +102 -39
  129. package/templates/root/AGENTS.md +39 -40
  130. package/templates/qoder/hooks/inject-workflow-state.py +0 -169
  131. package/templates/qoder/scripts/__pycache__/check_mcp_launch.cpython-39.pyc +0 -0
  132. package/templates/qoder/scripts/__pycache__/install_qoderwork.cpython-39.pyc +0 -0
  133. package/templates/qoder/scripts/__pycache__/mcp_launcher.cpython-39.pyc +0 -0
  134. package/templates/qoder/scripts/__pycache__/platform_doctor.cpython-39.pyc +0 -0
  135. package/templates/qoder/scripts/check_carriers.py +0 -238
  136. package/templates/qoder/scripts/check_mcp_launch.py +0 -183
  137. package/templates/qoder/scripts/check_qoderwork_consistency.py +0 -166
  138. package/templates/qoder/scripts/collect_prds.py +0 -31
  139. package/templates/qoder/scripts/common/mentions.py +0 -134
  140. package/templates/qoder/scripts/common/utf8.py +0 -38
  141. package/templates/qoder/scripts/extract_routes.py +0 -54
  142. package/templates/qoder/scripts/extract_routes_tree.py +0 -78
  143. package/templates/qoder/scripts/handoff.py +0 -22
  144. package/templates/qoder/scripts/init_developer.py +0 -76
  145. package/templates/qoder/scripts/parse_prds.py +0 -33
  146. package/templates/qoder/scripts/role.py +0 -51
  147. package/templates/qoder/scripts/sync_carriers.py +0 -259
  148. package/templates/qoder/scripts/task.py +0 -1261
  149. package/templates/qoder/scripts/workspace_init.py +0 -102
  150. package/templates/qoder/skills/prompt-enrich/SKILL.md +0 -90
  151. package/templates/qoder/skills/prototype-generator/SKILL.md.zcode-79180-2af4721f-f9a6-412c-88db-c0af680d211b.tmp +0 -0
  152. /package/templates/qoder/scripts/{secure-ls.js → test/secure-ls.js} +0 -0
@@ -1,5 +1,11 @@
1
1
  #!/usr/bin/env python
2
2
  # -*- coding: utf-8 -*-
3
+ # v3.0 路径自举: 引导到 common/bootstrap, 统一 sys.path 逻辑
4
+ import os as _o, sys as _s
5
+ _cp = _o.path.join(_o.path.dirname(_o.path.dirname(_o.path.abspath(__file__))), 'common')
6
+ if _cp not in _s.path: _s.path.insert(0, _cp)
7
+ from bootstrap import setup; setup()
8
+
3
9
  """
4
10
  Knowledge Graph MCP Server for QoderWork (pure Python, no SDK dependency).
5
11
 
@@ -27,18 +33,22 @@ except Exception:
27
33
  pass
28
34
 
29
35
  # Locate the scripts dir
30
- SCRIPTS_DIR = os.path.dirname(os.path.abspath(__file__))
31
- BASE_DIR = os.path.dirname(os.path.dirname(SCRIPTS_DIR)) # repo root (.qoder 的上级)
36
+ SCRIPTS_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # 子包→scripts/根
37
+ BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(SCRIPTS_DIR))) # 多一层: 子包→scripts→repo # repo root (.qoder 的上级)
32
38
  sys.path.insert(0, SCRIPTS_DIR)
39
+ _KG_DIR = os.path.dirname(os.path.abspath(__file__))
40
+ if _KG_DIR not in sys.path: sys.path.insert(0, _KG_DIR) # 同包import
33
41
 
34
- # Suppress all non-JSON output on stdout (MCP protocol uses stdout exclusively)
35
- # Redirect any stray prints to stderr
36
- _real_stdout = sys.stdout
37
- sys.stdout = sys.stderr # default: prints go to stderr
42
+ # stdout 重定向 + 协议层由 BaseMCPServer 接管 (v3.0 重构)
43
+ # _real_stdout 不再模块级定义, 改为运行时从基类取 (见 _capture)
38
44
 
39
45
 
40
46
  def _capture(func, *args, **kwargs):
41
- """Run a function, capture its stdout (which the scripts print to)."""
47
+ """Run a function, capture its stdout (which the scripts print to).
48
+
49
+ v3.0: sys.stdout 在 run() 后已是 stderr (基类 _setup_streams 重定向),
50
+ _real_stdout 存在 BaseMCPServer._real_stdout。这里捕获时临时换回 buf。
51
+ """
42
52
  old = sys.stdout
43
53
  buf = io.StringIO()
44
54
  sys.stdout = buf
@@ -246,34 +256,38 @@ TOOLS = [
246
256
 
247
257
 
248
258
  # Role-based section filtering for context_pack output
249
- ROLE_SECTIONS = {
250
- 'pm': {'1', '2', '2b', '3', '4', '5', '7'}, # PM: no code files, no style refs
251
- 'design': {'2', '2b', '3', '6'}, # Design: pages+layout+fields+style
252
- 'dev': {'1', '3', '5', '7'}, # Dev: code+fields+API+wiki
253
- 'test': {'1', '4', '5'}, # Test: code+PRD+API
254
- 'admin': {'4', '5'}, # Admin: PRD+API overview
255
- }
256
-
257
- def _filter_by_role(text, role):
258
- """Filter context_pack output by role: keep only relevant sections."""
259
+ # v3.0: 单一真源移到 common/roles.py, 消除与 kg.py 的逐字重复
260
+ try:
261
+ from common.roles import ROLE_SECTIONS, filter_by_role as _filter_by_role
262
+ except Exception:
263
+ # 回退: 内联定义 (common 不可用时)
259
264
  import re
260
- visible = ROLE_SECTIONS.get(role.lower())
261
- if not visible:
262
- return text
263
- sections = re.split(r'(\n## \d+[ab]?\.)', text)
264
- result = sections[0] # header before first section
265
- i = 1
266
- while i < len(sections):
267
- header = sections[i]
268
- num_m = re.match(r'\n## (\d+[ab]?)\.', header)
269
- if num_m and num_m.group(1) in visible:
270
- result += header
271
- if i + 1 < len(sections):
272
- result += sections[i + 1]
273
- i += 2
274
- else:
275
- i += 2 # skip this section
276
- return result.strip()
265
+ ROLE_SECTIONS = {
266
+ 'pm': {'1', '2', '2b', '3', '4', '5', '7'},
267
+ 'design': {'2', '2b', '3', '6'},
268
+ 'dev': {'1', '3', '5', '7'},
269
+ 'test': {'1', '4', '5'},
270
+ 'admin': {'4', '5'},
271
+ }
272
+
273
+ def _filter_by_role(text, role):
274
+ visible = ROLE_SECTIONS.get((role or '').lower())
275
+ if not visible:
276
+ return text
277
+ sections = re.split(r'(\n## \d+[ab]?\.)', text)
278
+ result = sections[0]
279
+ i = 1
280
+ while i < len(sections):
281
+ header = sections[i]
282
+ num_m = re.match(r'\n## (\d+[ab]?)\.', header)
283
+ if num_m and num_m.group(1) in visible:
284
+ result += header
285
+ if i + 1 < len(sections):
286
+ result += sections[i + 1]
287
+ i += 2
288
+ else:
289
+ i += 2
290
+ return result.strip()
277
291
 
278
292
 
279
293
  # ── Tool implementations (added 2026-06-17) ──────────────────────────
@@ -281,107 +295,27 @@ def _filter_by_role(text, role):
281
295
  # search_wiki 走 wiki-index.json(730词)。均不依赖已失效的旧 JSON 索引。
282
296
 
283
297
  def _tool_get_workflow(module):
284
- """业务操作链查询。直查 DuckDB workflows 表, 带脏数据清洗 + 模糊匹配。
285
-
286
- 审核: workflows 表 168 行/13 模块, 脏数据约 25 行(endpoint='None' 或
287
- button 泄漏模板语法 '{{'), 清洗后链路完整。模块名均为英文, 中文输入会
288
- 模糊查 entities.cn 返回候选。
289
- """
290
- import kg_duckdb as K
298
+ """业务操作链查询 v3.0 f3: 转发到 common.kg_capabilities (与 kg.py 共用单一真源)。"""
291
299
  q = (module or '').strip()
292
300
  if not q:
293
301
  return "请提供模块名。可选: assets/cost/hr/maintenance/operation/qualityAndSafety/veh/settings/dashboard/flow/systems/monitor/quasafe"
302
+ import kg_duckdb as K
303
+ from common.kg_capabilities import workflow_query
294
304
  con = K.get_db()
295
305
  try:
296
- # 1) 精确 + 模糊匹配 workflows.module (英文)
297
- hits = con.execute(
298
- "SELECT DISTINCT module FROM workflows WHERE module = ? OR module LIKE '%' || ? || '%' ORDER BY module",
299
- [q.lower(), q.lower()]
300
- ).fetchall()
301
- if not hits:
302
- # 2) 中文兜底: 查 entities 里 cn 匹配的, 给候选
303
- cn_hits = con.execute(
304
- "SELECT canonical, cn FROM entities WHERE type IN ('FEATURE','MODULE') AND cn <> '' AND (cn LIKE '%' || ? OR cn LIKE '%' || ? || '%')",
305
- [q, q]
306
- ).fetchall()
307
- if cn_hits:
308
- cands = ', '.join('%s(%s)' % (c[0], c[1]) for c in cn_hits[:8])
309
- return "未在 workflows 表找到模块 '%s'。但 entities 表有相近的中文名, 可换这些英文模块名重试:\n %s" % (q, cands)
310
- all_mods = [r[0] for r in con.execute("SELECT DISTINCT module FROM workflows ORDER BY module").fetchall()]
311
- return "未找到模块 '%s'。现有模块: %s" % (q, ', '.join(all_mods))
312
-
313
- out = []
314
- for (mod,) in hits:
315
- # 清洗: 过滤空端点和模板泄漏按钮
316
- rows = con.execute("""
317
- SELECT state, seq, button, endpoint FROM workflows
318
- WHERE module = ?
319
- AND endpoint <> 'None' AND endpoint <> ''
320
- AND button NOT LIKE '{{%'
321
- ORDER BY seq, state
322
- """, [mod]).fetchall()
323
- if not rows:
324
- continue
325
- # 按 seq 分组展示操作链
326
- out.append('=== %s (%d 步, 已清洗脏数据) ===' % (mod, len(rows)))
327
- cur_seq = -1
328
- for st, sq, btn, ep in rows:
329
- if sq != cur_seq:
330
- out.append(' [%d] %s' % (sq, st))
331
- cur_seq = sq
332
- out.append(' %-16s -> %s' % (btn, ep))
333
- out.append('')
334
- return '\n'.join(out).strip() or "模块 '%s' 清洗后无有效操作链" % q
306
+ return workflow_query(con, q)
335
307
  finally:
336
308
  try: con.close()
337
309
  except Exception: pass
338
310
 
339
311
 
340
312
  def _tool_search_wiki(keyword):
341
- """Repo Wiki 搜索。只读 wiki-index.json, 不触发自动构建(慢操作, 不适合 MCP 同步调用)。
342
-
343
- 审核: repowiki.search_wiki 函数在, wiki-index.json 有 730 关键词。
344
- search_index.search_wiki_cli 会在索引缺失时调 build_wiki_index (可能跑很久),
345
- 这里绕开它直接调底层 search_wiki, 索引缺失即报错让用户手动跑。
346
- """
313
+ """Repo Wiki 搜索 — v3.0 f3: 转发到 common.kg_capabilities.wiki_query (CLI/MCP共用)。"""
347
314
  kw = (keyword or '').strip()
348
315
  if not kw:
349
316
  return "请提供搜索关键词"
350
- from common.paths import DATA_INDEX_DIR as _idx, PROJECT_ROOT as _base
351
- wiki_path = os.path.join(str(_idx), 'wiki-index.json')
352
- if not os.path.isfile(wiki_path):
353
- return ("wiki-index.json 不存在。请在终端手动构建(一次性):\n"
354
- " python .qoder/scripts/search_index.py wiki\n"
355
- "(需要先在 Qoder IDE 对项目生成 Repo Wiki)")
356
- try:
357
- from common.repowiki import search_wiki
358
- from common.terms import get_cn_map_with_auto
359
- except ImportError as e:
360
- return "Wiki 搜索模块缺失: %s" % str(e)
361
- code_dir = os.path.join(_base, 'data', 'code')
362
- try:
363
- cn_map = get_cn_map_with_auto()
364
- results = search_wiki(kw, str(_idx), code_dir, cn_map)
365
- except Exception as e:
366
- return "Wiki 搜索出错: %s" % str(e)
367
- if not results:
368
- return "No wiki pages match: %s" % kw
369
- out = ['Wiki pages matching: %s (%d found)' % (kw, len(results)), '']
370
- for i, r in enumerate(results, 1):
371
- out.append(' %d. [%s] %s' % (i, r.get('project', '?'), r.get('title', '')))
372
- deps = r.get('dependent_files') or []
373
- if deps:
374
- out.append(' 依赖: %s%s' % (', '.join(deps[:2]),
375
- (' (共 %d 个文件)' % len(deps)) if len(deps) > 2 else ''))
376
- if r.get('md_path'):
377
- try:
378
- rel = os.path.relpath(r['md_path'], _base)
379
- except ValueError:
380
- rel = r['md_path']
381
- out.append(' 文档: %s' % rel)
382
- out.append('')
383
- out.append(' 提示: 读 wiki 文档 (带行号引用) 比读源码更省 token')
384
- return '\n'.join(out)
317
+ from common.kg_capabilities import wiki_query
318
+ return wiki_query(kw)
385
319
 
386
320
 
387
321
  def _tool_multi_hop(symbol, max_depth=3):
@@ -402,70 +336,9 @@ def _tool_multi_hop(symbol, max_depth=3):
402
336
  depth = max(1, min(5, depth)) # 钳制 1-5
403
337
  con = K.get_db()
404
338
  try:
405
- # 1) 尝试解析为 entity_id: 直接匹配 / 别名表 / 模糊匹配 entity_id
406
- start_id = sym
407
- # 1a) 别名精确匹配 (小写)
408
- resolved = con.execute(
409
- "SELECT entity_id FROM aliases WHERE alias = ?", [sym.lower()]
410
- ).fetchone()
411
- if resolved:
412
- start_id = resolved[0]
413
- else:
414
- # 1b) entity_id 模糊匹配 (用户传端点 /asset/list 或函数名)
415
- fuzzy = con.execute(
416
- "SELECT id FROM entities WHERE id = ? OR id LIKE '%' || ? || '%' LIMIT 1",
417
- [sym, sym]
418
- ).fetchone()
419
- if fuzzy:
420
- start_id = fuzzy[0]
421
- else:
422
- # 1c) edges 里直接含该符号作为端点
423
- edge_hit = con.execute(
424
- "SELECT from_id FROM edges WHERE from_id LIKE '%' || ? || '%' LIMIT 1",
425
- [sym]
426
- ).fetchone()
427
- if edge_hit:
428
- start_id = edge_hit[0]
429
-
430
- # 2) 递归 CTE 遍历
431
- try:
432
- hops = K.multi_hop(con, start_id, max_depth=depth)
433
- except Exception as e:
434
- return "遍历出错 (起点=%s): %s" % (start_id, str(e))
435
-
436
- if not hops or (len(hops) == 1 and hops[0][1] == 0):
437
- return "从 '%s' (解析为 %s) 出发无可达节点。可能该符号不在图谱中, 或无出边。" % (sym, start_id)
438
-
439
- # 3) 按 depth 分组, 取每层的实体详情 (最多展示)
440
- from collections import defaultdict
441
- by_depth = defaultdict(list)
442
- for eid, d in hops:
443
- by_depth[d].append(eid)
444
-
445
- out = ['=== 多跳遍历: %s (解析为 %s) ===' % (sym, start_id)]
446
- out.append('可达 %d 个实体, 最大 %d 跳:' % (len(hops), max(by_depth.keys())))
447
- out.append('')
448
- for d in sorted(by_depth.keys()):
449
- eids = by_depth[d]
450
- out.append('[第 %d 跳] %d 个实体:' % (d, len(eids)))
451
- # 拉每个实体的 canonical/cn 信息
452
- shown = 0
453
- for eid in eids:
454
- if shown >= 12:
455
- out.append(' ... 还有 %d 个' % (len(eids) - 12))
456
- break
457
- info = con.execute(
458
- "SELECT type, canonical, cn FROM entities WHERE id = ?", [eid]
459
- ).fetchone()
460
- if info:
461
- t, c, cn = info
462
- label = '%s / %s' % (c, cn) if cn and cn != c else c
463
- out.append(' %-14s %-12s %s' % ('[%s]' % t, eid[:40], label))
464
- else:
465
- out.append(' %-14s %s' % ('[edge]', eid[:60]))
466
- shown += 1
467
- out.append('')
468
- return '\n'.join(out).strip()
339
+ # v3.0 f3: 核心查询转发到 common.kg_capabilities.hop_query (CLI/MCP共用)
340
+ from common.kg_capabilities import hop_query
341
+ return hop_query(con, sym, depth, multi_hop_fn=K.multi_hop)
469
342
  finally:
470
343
  try: con.close()
471
344
  except Exception: pass
@@ -703,99 +576,31 @@ def _execute_tool(name, arguments):
703
576
  return "未知工具: %s" % name
704
577
 
705
578
 
706
- # ── MCP Stdio Protocol (JSON-RPC 2.0) ─────────────────────────────────
707
-
708
- def _send(msg):
709
- """Send a JSON-RPC message on stdout (MCP transport)."""
710
- _real_stdout.write(json.dumps(msg, ensure_ascii=False) + '\n')
711
- _real_stdout.flush()
712
-
713
-
714
- async def _handle(req):
715
- """Handle a single JSON-RPC request."""
716
- method = req.get("method", "")
717
- req_id = req.get("id")
718
- params = req.get("params", {})
719
-
720
- if method == "initialize":
721
- _send({
722
- "jsonrpc": "2.0", "id": req_id,
723
- "result": {
724
- "protocolVersion": "2024-11-05",
725
- "capabilities": {"tools": {}},
726
- "serverInfo": {"name": "qoder-knowledge-graph", "version": "1.0.0"},
727
- }
728
- })
729
-
730
- elif method == "notifications/initialized":
731
- pass # no response needed for notifications
732
-
733
- elif method == "tools/list":
734
- _send({"jsonrpc": "2.0", "id": req_id, "result": {"tools": TOOLS}})
735
-
736
- elif method == "tools/call":
737
- tool_name = params.get("name", "")
738
- arguments = params.get("arguments", {})
739
- try:
740
- result_text = await asyncio.get_event_loop().run_in_executor(
741
- None, _execute_tool, tool_name, arguments)
742
- _send({
743
- "jsonrpc": "2.0", "id": req_id,
744
- "result": {
745
- "content": [{"type": "text", "text": result_text}],
746
- "isError": False,
747
- }
748
- })
749
- except Exception as e:
750
- _send({
751
- "jsonrpc": "2.0", "id": req_id,
752
- "result": {
753
- "content": [{"type": "text", "text": "[error] %s" % str(e)}],
754
- "isError": True,
755
- }
756
- })
579
+ # ── MCP Server (继承 BaseMCPServer, 协议层由基类接管) ──────────────────
580
+ # v3.0 重构: 删除 _send/_handle/main 三段逐字复制代码 (~96行),
581
+ # 改为继承 BaseMCPServer。kg 的 initialize 不做内网校验 (本地知识图谱)
757
582
 
758
- elif method == "ping":
759
- _send({"jsonrpc": "2.0", "id": req_id, "result": {}})
583
+ def _bootstrap_base():
584
+ """延迟 import BaseMCPServer (scripts/ 在 sys.path)。"""
585
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
586
+ from common.mcp_base import BaseMCPServer
587
+ return BaseMCPServer
760
588
 
761
- else:
762
- if req_id is not None:
763
- _send({"jsonrpc": "2.0", "id": req_id,
764
- "error": {"code": -32601, "message": "Method not found: %s" % method}})
765
589
 
590
+ class KnowledgeGraphMCPServer(_bootstrap_base()):
591
+ """知识图谱 MCP Server。继承基类协议层, 只填工具逻辑。
766
592
 
767
- def main():
768
- """Read JSON-RPC requests from stdin (synchronous readline), dispatch.
769
-
770
- Synchronous is more reliable than asyncio on Windows Proactor event loop.
771
- Tool execution runs in a thread pool to avoid blocking on long queries.
593
+ mysql/zentao 不同: kg 不做内网校验 (本地 DuckDB),
594
+ 所以不覆写 on_initialize_hook (基类默认空)
772
595
  """
773
- import threading
774
596
 
775
- while True:
776
- try:
777
- line = sys.stdin.buffer.readline()
778
- except (EOFError, OSError):
779
- break
780
- if not line:
781
- break
782
- line = line.strip()
783
- if not line:
784
- continue
785
- try:
786
- req = json.loads(line.decode('utf-8'))
787
- # Handle synchronously (tools are fast enough; context_pack ~1-2s)
788
- loop = asyncio.new_event_loop()
789
- loop.run_until_complete(_handle(req))
790
- loop.close()
791
- except json.JSONDecodeError:
792
- pass
793
- except Exception as e:
794
- print('[kg-mcp] error: %s' % str(e), file=sys.stderr)
597
+ SERVER_NAME = "qoder-knowledge-graph"
598
+ SERVER_VERSION = "1.0.0"
599
+ TOOLS = TOOLS # 引用上方模块级 TOOLS (17个)
600
+
601
+ def handle_tool(self, name, args):
602
+ return _execute_tool(name, args)
795
603
 
796
604
 
797
605
  if __name__ == "__main__":
798
- try:
799
- main()
800
- except KeyboardInterrupt:
801
- pass
606
+ KnowledgeGraphMCPServer().run()