@hupan56/wlkj 2.7.12 → 3.1.2
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.
- package/bin/cli.js +1435 -1107
- package/package.json +29 -29
- package/templates/qoder/agents/insight-planning.md +1 -1
- package/templates/qoder/agents/insight-research.md +28 -15
- package/templates/qoder/commands/optional/wl-insight.md +161 -161
- package/templates/qoder/commands/optional/wl-report.md +20 -39
- package/templates/qoder/commands/optional/wl-status.md +4 -4
- package/templates/qoder/commands/wl-code.md +2 -2
- package/templates/qoder/commands/wl-design.md +6 -6
- package/templates/qoder/commands/wl-init.md +3 -3
- package/templates/qoder/commands/wl-prd.md +26 -13
- package/templates/qoder/commands/wl-req.md +43 -0
- package/templates/qoder/commands/wl-search.md +78 -47
- package/templates/qoder/commands/wl-task.md +343 -40
- package/templates/qoder/commands/wl-test.md +43 -17
- package/templates/qoder/config.yaml +52 -14
- package/templates/qoder/hooks/post-tool-use.py +181 -0
- package/templates/qoder/hooks/session-start.py +94 -23
- package/templates/qoder/hooks/stop-eval.py +207 -0
- package/templates/qoder/hooks/user-prompt-submit.py +139 -0
- package/templates/qoder/rules/wl-pipeline.md +85 -49
- package/templates/qoder/scripts/README.md +102 -0
- package/templates/qoder/scripts/capability/__init__.py +26 -0
- package/templates/qoder/scripts/capability/__main__.py +72 -0
- package/templates/qoder/scripts/capability/adapters/__init__.py +29 -0
- package/templates/qoder/scripts/capability/adapters/cli.py +316 -0
- package/templates/qoder/scripts/capability/adapters/mcp.py +334 -0
- package/templates/qoder/scripts/capability/adapters/qw.py +271 -0
- package/templates/qoder/scripts/capability/caps/__init__.py +27 -0
- package/templates/qoder/scripts/capability/caps/context.py +36 -0
- package/templates/qoder/scripts/capability/caps/cron.py +50 -0
- package/templates/qoder/scripts/capability/caps/identity.py +43 -0
- package/templates/qoder/scripts/capability/caps/memory.py +71 -0
- package/templates/qoder/scripts/capability/caps/notify.py +41 -0
- package/templates/qoder/scripts/capability/caps/present.py +48 -0
- package/templates/qoder/scripts/capability/caps/repo.py +31 -0
- package/templates/qoder/scripts/capability/caps/sandbox.py +43 -0
- package/templates/qoder/scripts/capability/chain.py +92 -0
- package/templates/qoder/scripts/capability/memory_chain.py +41 -0
- package/templates/qoder/scripts/capability/registry.py +246 -0
- package/templates/qoder/scripts/capability/registry_mcp.py +250 -0
- package/templates/qoder/scripts/capability/smoke_test.py +211 -0
- package/templates/qoder/scripts/capability/smoke_test_report.json +94 -0
- package/templates/qoder/scripts/deployment/setup/__init__.py +11 -0
- package/templates/qoder/scripts/deployment/setup/carriers.py +669 -0
- package/templates/qoder/scripts/deployment/setup/carriers_verify.py +148 -0
- package/templates/qoder/scripts/{init_doctor.py → deployment/setup/init_doctor.py} +100 -39
- package/templates/qoder/scripts/{install_qoderwork.py → deployment/setup/install_qoderwork.py} +48 -30
- package/templates/qoder/scripts/{platform_doctor.py → deployment/setup/platform_doctor.py} +271 -259
- package/templates/qoder/scripts/{repo_root.py → deployment/setup/repo_root.py} +9 -2
- package/templates/qoder/scripts/{setup.py → deployment/setup/setup.py} +143 -14
- package/templates/qoder/scripts/{setup_lanhu.py → deployment/setup/setup_lanhu.py} +979 -963
- package/templates/qoder/scripts/domain/design/__init__.py +0 -0
- package/templates/qoder/scripts/{fill_prototype.py → domain/design/fill_prototype.py} +17 -5
- package/templates/qoder/scripts/{gen_design_doc.py → domain/design/gen_design_doc.py} +406 -394
- package/templates/qoder/scripts/domain/kg/__init__.py +11 -0
- package/templates/qoder/scripts/domain/kg/build/__init__.py +0 -0
- package/templates/qoder/scripts/domain/kg/build/build_entity_registry.py +201 -0
- package/templates/qoder/scripts/domain/kg/build/build_relations.py +132 -0
- package/templates/qoder/scripts/{build_style_index.py → domain/kg/build/build_style_index.py} +50 -15
- package/templates/qoder/scripts/domain/kg/build/build_workflows.py +149 -0
- package/templates/qoder/scripts/{kg_build.py → domain/kg/build/kg_build.py} +685 -612
- package/templates/qoder/scripts/{kg_build_db.py → domain/kg/build/kg_build_db.py} +338 -327
- package/templates/qoder/scripts/{kg_incremental.py → domain/kg/build/kg_incremental.py} +425 -393
- package/templates/qoder/scripts/{learn_aggregate.py → domain/kg/build/learn_aggregate.py} +212 -201
- package/templates/qoder/scripts/domain/kg/extract/__init__.py +0 -0
- package/templates/qoder/scripts/{common → domain/kg/extract}/extract.py +430 -419
- package/templates/qoder/scripts/domain/kg/extract/test_extract.py +115 -0
- package/templates/qoder/scripts/{common → domain/kg/extract}/ts_extract.py +614 -536
- package/templates/qoder/scripts/domain/kg/graph/__init__.py +0 -0
- package/templates/qoder/scripts/{kg_link_db.py → domain/kg/graph/kg_link_db.py} +235 -224
- package/templates/qoder/scripts/domain/kg/graph/kg_semantic.py +683 -0
- package/templates/qoder/scripts/{kg.py → domain/kg/kg.py} +871 -708
- package/templates/qoder/scripts/domain/kg/kg_capabilities.py +182 -0
- package/templates/qoder/scripts/domain/kg/search/__init__.py +0 -0
- package/templates/qoder/scripts/{context_pack.py → domain/kg/search/context_pack.py} +45 -17
- package/templates/qoder/scripts/{enrich_prompt.py → domain/kg/search/enrich_prompt.py} +238 -226
- package/templates/qoder/scripts/domain/kg/search/prefetch.py +384 -0
- package/templates/qoder/scripts/{common → domain/kg/search}/search_engine.py +207 -205
- package/templates/qoder/scripts/{search_index.py → domain/kg/search/search_index.py} +210 -88
- package/templates/qoder/scripts/domain/kg/server/__init__.py +0 -0
- package/templates/qoder/scripts/domain/kg/server/kgd.py +549 -0
- package/templates/qoder/scripts/domain/kg/server/perf_bench.py +197 -0
- package/templates/qoder/scripts/domain/kg/storage/__init__.py +0 -0
- package/templates/qoder/scripts/{kg_duckdb.py → domain/kg/storage/kg_duckdb.py} +70 -45
- package/templates/qoder/scripts/domain/learning/__init__.py +0 -0
- package/templates/qoder/scripts/{learn.py → domain/learning/learn.py} +157 -146
- package/templates/qoder/scripts/domain/report/__init__.py +11 -0
- package/templates/qoder/scripts/{add_session.py → domain/report/add_session.py} +256 -244
- package/templates/qoder/scripts/{export.py → domain/report/export.py} +72 -4
- package/templates/qoder/scripts/{report.py → domain/report/report.py} +292 -281
- package/templates/qoder/scripts/domain/report/report_snapshot.py +261 -0
- package/templates/qoder/scripts/domain/report/role.py +39 -0
- package/templates/qoder/scripts/{status.py → domain/report/status.py} +737 -628
- package/templates/qoder/scripts/domain/report/status_snapshot.py +191 -0
- package/templates/qoder/scripts/domain/requirement/__init__.py +0 -0
- package/templates/qoder/scripts/{archive_prd.py → domain/requirement/archive_prd.py} +389 -377
- package/templates/qoder/scripts/domain/requirement/req.py +228 -0
- package/templates/qoder/scripts/domain/task/__init__.py +11 -0
- package/templates/qoder/scripts/{git_sync.py → domain/task/git_sync.py} +90 -50
- package/templates/qoder/scripts/{syncgate.py → domain/task/syncgate.py} +18 -7
- package/templates/qoder/scripts/domain/task/task.py +229 -0
- package/templates/qoder/scripts/domain/task/task_lifecycle.py +606 -0
- package/templates/qoder/scripts/domain/task/task_query.py +162 -0
- package/templates/qoder/scripts/domain/task/task_relations.py +425 -0
- package/templates/qoder/scripts/{team_sync.py → domain/task/team_sync.py} +101 -23
- package/templates/qoder/scripts/domain/task/zentao_sync.py +370 -0
- package/templates/qoder/scripts/foundation/__init__.py +0 -0
- package/templates/qoder/scripts/foundation/bootstrap.py +145 -0
- package/templates/qoder/scripts/foundation/core/__init__.py +0 -0
- package/templates/qoder/scripts/foundation/core/cmd_registry.py +112 -0
- package/templates/qoder/scripts/foundation/core/config.py +187 -0
- package/templates/qoder/scripts/{common → foundation/core}/paths.py +706 -400
- package/templates/qoder/scripts/foundation/data/__init__.py +0 -0
- package/templates/qoder/scripts/foundation/data/contract.py +317 -0
- package/templates/qoder/scripts/{common → foundation/data}/events.py +2 -2
- package/templates/qoder/scripts/foundation/data/result.py +223 -0
- package/templates/qoder/scripts/{common → foundation/data}/task_utils.py +427 -392
- package/templates/qoder/scripts/foundation/identity/__init__.py +0 -0
- package/templates/qoder/scripts/foundation/identity/check_publish.py +103 -0
- package/templates/qoder/scripts/{common → foundation/identity}/developer.py +239 -238
- package/templates/qoder/scripts/foundation/identity/guard.py +159 -0
- package/templates/qoder/scripts/{common → foundation/identity}/identity.py +132 -9
- package/templates/qoder/scripts/foundation/identity/roles.py +60 -0
- package/templates/qoder/scripts/foundation/integrations/__init__.py +0 -0
- package/templates/qoder/scripts/{common → foundation/integrations}/active_task.py +30 -15
- package/templates/qoder/scripts/{common → foundation/integrations}/feishu.py +11 -10
- package/templates/qoder/scripts/{common → foundation/integrations}/terms.py +3 -3
- package/templates/qoder/scripts/foundation/integrations/zentao_client.py +220 -0
- package/templates/qoder/scripts/foundation/io/__init__.py +0 -0
- package/templates/qoder/scripts/{common → foundation/io}/filelock.py +1 -1
- package/templates/qoder/scripts/{common → foundation/io}/reqid.py +1 -1
- package/templates/qoder/scripts/foundation/protocol/__init__.py +0 -0
- package/templates/qoder/scripts/foundation/protocol/duckdb_conn.py +39 -0
- package/templates/qoder/scripts/foundation/protocol/mcp_base.py +268 -0
- package/templates/qoder/scripts/foundation/utils/__init__.py +0 -0
- package/templates/qoder/scripts/{common → foundation/utils}/eval_api.py +12 -1
- package/templates/qoder/scripts/{common → foundation/utils}/pip_install.py +23 -6
- package/templates/qoder/scripts/{common → foundation/utils}/platform_guard.py +1 -1
- package/templates/qoder/scripts/orchestration/__init__.py +0 -0
- package/templates/qoder/scripts/orchestration/wlkj.py +185 -0
- package/templates/qoder/scripts/protocol/__init__.py +0 -0
- package/templates/qoder/scripts/protocol/browser/README.md +207 -0
- package/templates/qoder/scripts/protocol/browser/SKILL.md +265 -0
- package/templates/qoder/scripts/protocol/browser/config.env +9 -0
- package/templates/qoder/scripts/protocol/browser/references/cdp-api.md +110 -0
- package/templates/qoder/scripts/protocol/browser/references/migration-2.5.3.md +72 -0
- package/templates/qoder/scripts/protocol/browser/references/site-patterns/.gitkeep +0 -0
- package/templates/qoder/scripts/protocol/browser/scripts/browser-discovery.mjs +138 -0
- package/templates/qoder/scripts/protocol/browser/scripts/cdp-proxy.mjs +672 -0
- package/templates/qoder/scripts/protocol/browser/scripts/check-deps.mjs +206 -0
- package/templates/qoder/scripts/protocol/browser/scripts/find-url.mjs +253 -0
- package/templates/qoder/scripts/protocol/browser/scripts/match-site.mjs +46 -0
- package/templates/qoder/scripts/protocol/browser/templates/config.env.template +9 -0
- package/templates/qoder/scripts/protocol/mcp/__init__.py +11 -0
- package/templates/qoder/scripts/protocol/mcp/kg_mcp_server.py +478 -0
- package/templates/qoder/scripts/{lanhu_stdio_wrapper.py → protocol/mcp/lanhu_stdio_wrapper.py} +131 -119
- package/templates/qoder/scripts/protocol/mcp/mcp_doctor.py +548 -0
- package/templates/qoder/scripts/protocol/mcp/mcp_launcher.py +600 -0
- package/templates/qoder/scripts/protocol/mcp/mysql_mcp_server.py +461 -0
- package/templates/qoder/scripts/protocol/mcp/zentao_mcp_server.py +1920 -0
- package/templates/qoder/scripts/protocol/transports/__init__.py +56 -0
- package/templates/qoder/scripts/protocol/transports/base.py +87 -0
- package/templates/qoder/scripts/protocol/transports/cli.py +132 -0
- package/templates/qoder/scripts/protocol/transports/http.py +141 -0
- package/templates/qoder/scripts/protocol/transports/stdio.py +293 -0
- package/templates/qoder/scripts/run_weekly_update.bat +27 -18
- package/templates/qoder/scripts/run_weekly_update.sh +22 -13
- package/templates/qoder/scripts/validation/__init__.py +0 -0
- package/templates/qoder/scripts/validation/eval/__init__.py +0 -0
- package/templates/qoder/scripts/validation/eval/perf-report-2026-06-24.md +139 -0
- package/templates/qoder/scripts/validation/eval/qwork_harness.py +617 -0
- package/templates/qoder/scripts/validation/eval/report-commands.md +220 -0
- package/templates/qoder/scripts/validation/eval/transcript_timing.py +386 -0
- package/templates/qoder/scripts/validation/metrics/__init__.py +0 -0
- package/templates/qoder/scripts/{eval_prd.py → validation/metrics/eval_prd.py} +84 -15
- package/templates/qoder/scripts/validation/metrics/usability_score.py +750 -0
- package/templates/qoder/scripts/validation/test/__init__.py +11 -0
- package/templates/qoder/scripts/validation/test/assertion_gen.py +551 -0
- package/templates/qoder/scripts/{autotest.py → validation/test/autotest.py} +1234 -1751
- package/templates/qoder/scripts/validation/test/autotest_auth.py +109 -0
- package/templates/qoder/scripts/{autotest_batch.py → validation/test/autotest_batch.py} +255 -224
- package/templates/qoder/scripts/validation/test/autotest_data.py +680 -0
- package/templates/qoder/scripts/validation/test/autotest_opencli.py +646 -0
- package/templates/qoder/scripts/{autotest_run.py → validation/test/autotest_run.py} +323 -297
- package/templates/qoder/scripts/validation/test/autotest_webaccess.py +571 -0
- package/templates/qoder/scripts/{benchmark.py → validation/test/benchmark.py} +17 -5
- package/templates/qoder/scripts/{kg_auto_login.py → validation/test/kg_auto_login.py} +208 -196
- package/templates/qoder/scripts/{kg_test_runner.py → validation/test/kg_test_runner.py} +13 -2
- package/templates/qoder/scripts/{page_probe.py → validation/test/page_probe.py} +470 -459
- package/templates/qoder/scripts/validation/test/smoke_all_commands.py +170 -0
- package/templates/qoder/settings.json +23 -2
- package/templates/qoder/skills/design-import/SKILL.md +235 -226
- package/templates/qoder/skills/design-review/SKILL.md +91 -82
- package/templates/qoder/skills/prd-generator/SKILL.md +38 -24
- package/templates/qoder/skills/prd-review/SKILL.md +8 -6
- package/templates/qoder/skills/prototype-generator/SKILL.md +296 -247
- package/templates/qoder/skills/spec-coder/SKILL.md +24 -15
- package/templates/qoder/skills/spec-generator/SKILL.md +24 -12
- package/templates/qoder/skills/test-generator/SKILL.md +9 -7
- package/templates/qoder/skills/wl-code/SKILL.md +25 -13
- package/templates/qoder/skills/wl-commit/SKILL.md +10 -9
- package/templates/qoder/skills/wl-design/SKILL.md +7 -5
- package/templates/qoder/skills/wl-init/SKILL.md +8 -8
- package/templates/qoder/skills/wl-insight/SKILL.md +25 -12
- package/templates/qoder/skills/wl-prd-full/SKILL.md +59 -8
- package/templates/qoder/skills/wl-prd-quick/SKILL.md +7 -7
- package/templates/qoder/skills/wl-prd-review/SKILL.md +18 -6
- package/templates/qoder/skills/wl-report/SKILL.md +27 -25
- package/templates/qoder/skills/wl-search/SKILL.md +151 -80
- package/templates/qoder/skills/wl-spec/SKILL.md +9 -7
- package/templates/qoder/skills/wl-status/SKILL.md +50 -18
- package/templates/qoder/skills/wl-task/SKILL.md +94 -12
- package/templates/qoder/skills/wl-test/SKILL.md +154 -45
- package/templates/qoder/templates/prd-full-template.md +7 -0
- package/templates/root/AGENTS.md +275 -237
- package/templates/root/requirements.txt +3 -0
- package/templates/root//344/275/277/347/224/250/350/257/264/346/230/216.md +3 -3
- package/templates/root//346/226/260/346/211/213/346/214/207/345/215/227.md +2 -2
- package/templates/qoder/hooks/inject-workflow-state.py +0 -169
- package/templates/qoder/scripts/__pycache__/check_mcp_launch.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/__pycache__/install_qoderwork.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/__pycache__/mcp_launcher.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/__pycache__/platform_doctor.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/check_carriers.py +0 -238
- package/templates/qoder/scripts/check_mcp.py +0 -298
- package/templates/qoder/scripts/check_mcp_launch.py +0 -183
- package/templates/qoder/scripts/check_qoderwork_consistency.py +0 -166
- package/templates/qoder/scripts/collect_prds.py +0 -31
- package/templates/qoder/scripts/common/mentions.py +0 -134
- package/templates/qoder/scripts/common/utf8.py +0 -38
- package/templates/qoder/scripts/extract_api_params.py +0 -246
- package/templates/qoder/scripts/extract_routes.py +0 -54
- package/templates/qoder/scripts/extract_routes_tree.py +0 -78
- package/templates/qoder/scripts/handoff.py +0 -22
- package/templates/qoder/scripts/init_developer.py +0 -76
- package/templates/qoder/scripts/kg_mcp_server.py +0 -801
- package/templates/qoder/scripts/kg_semantic.py +0 -150
- package/templates/qoder/scripts/mcp_launcher.py +0 -414
- package/templates/qoder/scripts/mysql_mcp_server.py +0 -396
- package/templates/qoder/scripts/parse_prds.py +0 -33
- package/templates/qoder/scripts/role.py +0 -51
- package/templates/qoder/scripts/sync_carriers.py +0 -259
- package/templates/qoder/scripts/task.py +0 -1261
- package/templates/qoder/scripts/workspace_init.py +0 -102
- package/templates/qoder/scripts/zentao_mcp_server.py +0 -424
- package/templates/qoder/skills/prompt-enrich/SKILL.md +0 -90
- /package/templates/qoder/scripts/{common → deployment}/__init__.py +0 -0
- /package/templates/qoder/{skills/prototype-generator/SKILL.md.zcode-79180-2af4721f-f9a6-412c-88db-c0af680d211b.tmp → scripts/domain/__init__.py} +0 -0
- /package/templates/qoder/scripts/{common → domain/kg/graph}/graph_traverse.py +0 -0
- /package/templates/qoder/scripts/{common → domain/kg/server}/repowiki.py +0 -0
- /package/templates/qoder/scripts/{common → foundation/io}/atomicio.py +0 -0
- /package/templates/qoder/scripts/{secure-ls.js → validation/test/secure-ls.js} +0 -0
|
@@ -1,801 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
"""
|
|
4
|
-
Knowledge Graph MCP Server for QoderWork (pure Python, no SDK dependency).
|
|
5
|
-
|
|
6
|
-
Exposes the QODER knowledge graph as MCP tools that QoderWork's AI agent
|
|
7
|
-
can call directly. Uses the MCP stdio protocol (JSON-RPC 2.0 over stdin/stdout)
|
|
8
|
-
implemented from scratch — no external dependencies beyond the standard library.
|
|
9
|
-
|
|
10
|
-
Registered in ~/.qoderwork/mcp.json as "qoder-knowledge-graph".
|
|
11
|
-
Tools surface as mcp__qoder-knowledge-graph__<tool> to the AI.
|
|
12
|
-
|
|
13
|
-
Each tool captures the stdout of the existing search_index/context_pack
|
|
14
|
-
functions, so no logic is duplicated — this is a thin stdio→MCP wrapper.
|
|
15
|
-
"""
|
|
16
|
-
import io
|
|
17
|
-
import os
|
|
18
|
-
import sys
|
|
19
|
-
import json
|
|
20
|
-
import asyncio
|
|
21
|
-
|
|
22
|
-
# Ensure UTF-8 output
|
|
23
|
-
try:
|
|
24
|
-
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
|
|
25
|
-
sys.stderr.reconfigure(encoding='utf-8', errors='replace')
|
|
26
|
-
except Exception:
|
|
27
|
-
pass
|
|
28
|
-
|
|
29
|
-
# 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 的上级)
|
|
32
|
-
sys.path.insert(0, SCRIPTS_DIR)
|
|
33
|
-
|
|
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
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def _capture(func, *args, **kwargs):
|
|
41
|
-
"""Run a function, capture its stdout (which the scripts print to)."""
|
|
42
|
-
old = sys.stdout
|
|
43
|
-
buf = io.StringIO()
|
|
44
|
-
sys.stdout = buf
|
|
45
|
-
try:
|
|
46
|
-
func(*args, **kwargs)
|
|
47
|
-
except Exception as e:
|
|
48
|
-
buf.write('\n[error] %s' % str(e))
|
|
49
|
-
finally:
|
|
50
|
-
sys.stdout = old
|
|
51
|
-
return buf.getvalue().strip()
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
# Lazy imports
|
|
55
|
-
_search = None
|
|
56
|
-
_context = None
|
|
57
|
-
|
|
58
|
-
def _ensure_imports():
|
|
59
|
-
global _search, _context
|
|
60
|
-
if _search is None:
|
|
61
|
-
import search_index as _search
|
|
62
|
-
if _context is None:
|
|
63
|
-
import context_pack as _context
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
# ── MCP Tool Definitions ─────────────────────────────────────────────
|
|
67
|
-
|
|
68
|
-
TOOLS = [
|
|
69
|
-
{
|
|
70
|
-
"name": "search_code",
|
|
71
|
-
"description": "搜索代码: 按关键词查找代码文件在哪。例: search_code(keyword='考勤') 或 search_code(keyword='车辆', platform='web')",
|
|
72
|
-
"inputSchema": {
|
|
73
|
-
"type": "object",
|
|
74
|
-
"properties": {
|
|
75
|
-
"keyword": {"type": "string", "description": "搜索关键词(中文或英文)"},
|
|
76
|
-
"platform": {"type": "string", "enum": ["web", "app"],
|
|
77
|
-
"description": "平台: web=PC管理端, app=移动端。可选"},
|
|
78
|
-
},
|
|
79
|
-
"required": ["keyword"],
|
|
80
|
-
},
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
"name": "search_api",
|
|
84
|
-
"description": "搜索API端点: 按关键词查找后端接口。例: search_api(keyword='salary')",
|
|
85
|
-
"inputSchema": {
|
|
86
|
-
"type": "object",
|
|
87
|
-
"properties": {"keyword": {"type": "string", "description": "API关键词"}},
|
|
88
|
-
"required": ["keyword"],
|
|
89
|
-
},
|
|
90
|
-
},
|
|
91
|
-
{
|
|
92
|
-
"name": "search_prd",
|
|
93
|
-
"description": "搜索PRD: 查已有需求文档,防止重复造轮子。例: search_prd(keyword='考勤')",
|
|
94
|
-
"inputSchema": {
|
|
95
|
-
"type": "object",
|
|
96
|
-
"properties": {"keyword": {"type": "string", "description": "PRD关键词"}},
|
|
97
|
-
"required": ["keyword"],
|
|
98
|
-
},
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
"name": "get_impact",
|
|
102
|
-
"description": "影响分析: 改某接口/路径会影响哪些前端页面和按钮?评估改动风险。例: get_impact(endpoint='/asset') 或 get_impact(endpoint='export')",
|
|
103
|
-
"inputSchema": {
|
|
104
|
-
"type": "object",
|
|
105
|
-
"properties": {"endpoint": {"type": "string",
|
|
106
|
-
"description": "API端点路径或关键词, 如 /asset/list 或 export"}},
|
|
107
|
-
"required": ["endpoint"],
|
|
108
|
-
},
|
|
109
|
-
},
|
|
110
|
-
{
|
|
111
|
-
"name": "context_360",
|
|
112
|
-
"description": "360度视图: 查看符号(端点/函数/处理器)的全部关联信息。例: context_360(symbol='handleExport') 或 context_360(symbol='/system/role')",
|
|
113
|
-
"inputSchema": {
|
|
114
|
-
"type": "object",
|
|
115
|
-
"properties": {"symbol": {"type": "string", "description": "符号名: 端点/函数名/处理器名"}},
|
|
116
|
-
"required": ["symbol"],
|
|
117
|
-
},
|
|
118
|
-
},
|
|
119
|
-
{
|
|
120
|
-
"name": "context_pack",
|
|
121
|
-
"description": "上下文打包: 一次返回关键词相关的全部上下文(代码+页面+字段+PRD+API+布局指纹)。避免来回查。例: context_pack(keyword='车辆', platform='web')",
|
|
122
|
-
"inputSchema": {
|
|
123
|
-
"type": "object",
|
|
124
|
-
"properties": {
|
|
125
|
-
"keyword": {"type": "string", "description": "业务关键词"},
|
|
126
|
-
"platform": {"type": "string", "enum": ["web", "app"],
|
|
127
|
-
"description": "平台: web或app。可选"},
|
|
128
|
-
"page_type": {"type": "string",
|
|
129
|
-
"description": "页面类型: table-page/form-page/detail-page。可选"},
|
|
130
|
-
"role": {"type": "string", "enum": ["pm", "design", "dev", "test", "admin"],
|
|
131
|
-
"description": "角色裁剪: pm=字段+PRD+API, design=页面+布局+风格, dev=代码+API, test=代码+PRD+API。可选"},
|
|
132
|
-
},
|
|
133
|
-
"required": ["keyword"],
|
|
134
|
-
},
|
|
135
|
-
},
|
|
136
|
-
{
|
|
137
|
-
"name": "table_impact",
|
|
138
|
-
"description": "改表影响分析: 改某数据库表会影响哪些 Entity 类和接口? 跨层遍历(数据层→代码层)。例: table_impact(table='quality_case') → 找到 QualityCase 类 + 影响的 Controller/接口",
|
|
139
|
-
"inputSchema": {
|
|
140
|
-
"type": "object",
|
|
141
|
-
"properties": {
|
|
142
|
-
"table": {"type": "string", "description": "数据库表名 (如 quality_case)"},
|
|
143
|
-
},
|
|
144
|
-
"required": ["table"],
|
|
145
|
-
},
|
|
146
|
-
},
|
|
147
|
-
{
|
|
148
|
-
"name": "coverage_matrix",
|
|
149
|
-
"description": "功能×测试覆盖矩阵: 哪些功能有测试覆盖, 哪些是空白? 标出回归测试盲区。例: coverage_matrix()",
|
|
150
|
-
"inputSchema": {
|
|
151
|
-
"type": "object",
|
|
152
|
-
"properties": {},
|
|
153
|
-
},
|
|
154
|
-
},
|
|
155
|
-
{
|
|
156
|
-
"name": "feature_overview",
|
|
157
|
-
"description": "功能画像: 一个功能的完整画像(端点+按钮+测试+页面)。例: feature_overview(feature='资产管理') 或 feature_overview(feature='asset')",
|
|
158
|
-
"inputSchema": {
|
|
159
|
-
"type": "object",
|
|
160
|
-
"properties": {
|
|
161
|
-
"feature": {"type": "string", "description": "功能名(中/英文都行): 资产管理/asset/hr"},
|
|
162
|
-
},
|
|
163
|
-
"required": ["feature"],
|
|
164
|
-
},
|
|
165
|
-
},
|
|
166
|
-
{
|
|
167
|
-
"name": "get_workflow",
|
|
168
|
-
"description": "业务流程: 查看一个模块的完整操作链(query→create→audit→archive→export→delete)。数据源: DuckDB workflows 表(168条/13模块)。优先匹配英文模块名(assets/cost/hr/maintenance等), 中文输入会返回候选模块列表。例: get_workflow(module='assets') 或 get_workflow(module='cost')",
|
|
169
|
-
"inputSchema": {
|
|
170
|
-
"type": "object",
|
|
171
|
-
"properties": {
|
|
172
|
-
"module": {"type": "string", "description": "模块名(英文优先): assets/cost/hr/maintenance/operation/qualityAndSafety/veh/settings/dashboard/flow/systems/monitor/quasafe"},
|
|
173
|
-
},
|
|
174
|
-
"required": ["module"],
|
|
175
|
-
},
|
|
176
|
-
},
|
|
177
|
-
{
|
|
178
|
-
"name": "search_wiki",
|
|
179
|
-
"description": "搜索Repo Wiki: 查Qoder IDE生成的模块级语义文档(架构/职责/依赖), 比读源码省token。写PRD前优先查。数据源: wiki-index.json(730关键词)。注意: 不会自动构建索引, 索引缺失会提示手动跑。例: search_wiki(keyword='考勤') 或 search_wiki(keyword='权限')",
|
|
180
|
-
"inputSchema": {
|
|
181
|
-
"type": "object",
|
|
182
|
-
"properties": {"keyword": {"type": "string", "description": "Wiki关键词(中/英文)"}},
|
|
183
|
-
"required": ["keyword"],
|
|
184
|
-
},
|
|
185
|
-
},
|
|
186
|
-
{
|
|
187
|
-
"name": "multi_hop",
|
|
188
|
-
"description": "知识图谱多跳遍历: 从一个符号出发, 沿edges递归N跳, 找出全部可达实体(页面→接口→控制器→测试)。知识图谱核心能力。数据源: DuckDB edges表(8251边)。支持别名解析(传中文/端点/函数名自动转entity_id)。例: multi_hop(symbol='资产管理') 或 multi_hop(symbol='/asset/list', max_depth=3)",
|
|
189
|
-
"inputSchema": {
|
|
190
|
-
"type": "object",
|
|
191
|
-
"properties": {
|
|
192
|
-
"symbol": {"type": "string", "description": "起点符号: 可传entity_id(E:/asset/list)、别名(资产管理)、端点(/asset/list)或函数名"},
|
|
193
|
-
"max_depth": {"type": "integer", "default": 3, "description": "最大跳数(1-5), 默认3。越大越全但越慢"},
|
|
194
|
-
},
|
|
195
|
-
"required": ["symbol"],
|
|
196
|
-
},
|
|
197
|
-
},
|
|
198
|
-
{
|
|
199
|
-
"name": "search_field",
|
|
200
|
-
"description": "搜索字段: 查某个字段在哪些页面用过、中文标题是什么。例: search_field(field='plateNo') 或 search_field(field='车牌号')",
|
|
201
|
-
"inputSchema": {
|
|
202
|
-
"type": "object",
|
|
203
|
-
"properties": {
|
|
204
|
-
"field": {"type": "string", "description": "字段名(英文field或中文标题)"},
|
|
205
|
-
},
|
|
206
|
-
"required": ["field"],
|
|
207
|
-
},
|
|
208
|
-
},
|
|
209
|
-
{
|
|
210
|
-
"name": "list_modules",
|
|
211
|
-
"description": "模块清单: 列出项目所有业务模块及其中文名。例: list_modules() 或 list_modules(platform='web')",
|
|
212
|
-
"inputSchema": {
|
|
213
|
-
"type": "object",
|
|
214
|
-
"properties": {
|
|
215
|
-
"platform": {"type": "string", "enum": ["web", "app"], "description": "平台过滤。可选"},
|
|
216
|
-
},
|
|
217
|
-
},
|
|
218
|
-
},
|
|
219
|
-
{
|
|
220
|
-
"name": "fill_prototype",
|
|
221
|
-
"description": "原型预填: 用真实代码数据(表格列/表单字段/布局参数)自动填模板,输出80%完成的HTML草稿+数据清单。AI只需微调20%。出原型前必须调。例: fill_prototype(keyword='营业外合同', platform='web')",
|
|
222
|
-
"inputSchema": {
|
|
223
|
-
"type": "object",
|
|
224
|
-
"properties": {
|
|
225
|
-
"keyword": {"type": "string", "description": "业务关键词"},
|
|
226
|
-
"platform": {"type": "string", "enum": ["web", "app"],
|
|
227
|
-
"description": "平台: web或app。默认web"},
|
|
228
|
-
},
|
|
229
|
-
"required": ["keyword"],
|
|
230
|
-
},
|
|
231
|
-
},
|
|
232
|
-
{
|
|
233
|
-
"name": "get_design_system",
|
|
234
|
-
"description": "设计系统查询: 返回项目完整设计规范(布局指纹/颜色token/页面模式/组件表/图标规则/behaviors)。出任何原型前必须先调。例: get_design_system() 或 get_design_system(platform='web')",
|
|
235
|
-
"inputSchema": {
|
|
236
|
-
"type": "object",
|
|
237
|
-
"properties": {
|
|
238
|
-
"platform": {"type": "string", "enum": ["web", "app"],
|
|
239
|
-
"description": "平台: 只返回指定平台的指纹。不传则返回全部"},
|
|
240
|
-
},
|
|
241
|
-
},
|
|
242
|
-
},
|
|
243
|
-
]
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
# 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
|
-
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()
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
# ── Tool implementations (added 2026-06-17) ──────────────────────────
|
|
280
|
-
# 三个工具的底层能力均已实测: get_workflow/multi_hop 走 DuckDB(有数据),
|
|
281
|
-
# search_wiki 走 wiki-index.json(730词)。均不依赖已失效的旧 JSON 索引。
|
|
282
|
-
|
|
283
|
-
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
|
|
291
|
-
q = (module or '').strip()
|
|
292
|
-
if not q:
|
|
293
|
-
return "请提供模块名。可选: assets/cost/hr/maintenance/operation/qualityAndSafety/veh/settings/dashboard/flow/systems/monitor/quasafe"
|
|
294
|
-
con = K.get_db()
|
|
295
|
-
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
|
|
335
|
-
finally:
|
|
336
|
-
try: con.close()
|
|
337
|
-
except Exception: pass
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
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
|
-
"""
|
|
347
|
-
kw = (keyword or '').strip()
|
|
348
|
-
if not kw:
|
|
349
|
-
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)
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
def _tool_multi_hop(symbol, max_depth=3):
|
|
388
|
-
"""知识图谱多跳遍历。先别名解析, 再递归 CTE 遍历。
|
|
389
|
-
|
|
390
|
-
审核: kg_duckdb.multi_hop 实测从 E:/code 3 跳到 C:SysSmsCodeController。
|
|
391
|
-
edges 表 8251 边。用户可能传中文名/端点/函数名, 需先走 aliases 解析成
|
|
392
|
-
entity_id。
|
|
393
|
-
"""
|
|
394
|
-
import kg_duckdb as K
|
|
395
|
-
sym = (symbol or '').strip()
|
|
396
|
-
if not sym:
|
|
397
|
-
return "请提供起点符号 (entity_id / 端点 / 函数名 / 中文别名)"
|
|
398
|
-
try:
|
|
399
|
-
depth = int(max_depth)
|
|
400
|
-
except (TypeError, ValueError):
|
|
401
|
-
depth = 3
|
|
402
|
-
depth = max(1, min(5, depth)) # 钳制 1-5
|
|
403
|
-
con = K.get_db()
|
|
404
|
-
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()
|
|
469
|
-
finally:
|
|
470
|
-
try: con.close()
|
|
471
|
-
except Exception: pass
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
def _tool_search_field(field):
|
|
475
|
-
from common.paths import DATA_INDEX_DIR
|
|
476
|
-
from kg_duckdb import get_db
|
|
477
|
-
con = get_db()
|
|
478
|
-
rows = con.execute(
|
|
479
|
-
"SELECT field, title, count, sample_files FROM fields WHERE LOWER(field) LIKE ? OR LOWER(title) LIKE ? LIMIT 20",
|
|
480
|
-
['%' + field.lower() + '%', '%' + field.lower() + '%']
|
|
481
|
-
).fetchall()
|
|
482
|
-
con.close()
|
|
483
|
-
if not rows:
|
|
484
|
-
return "未找到字段 '%s'" % field
|
|
485
|
-
lines = ["字段搜索 '%s' (%d个匹配)" % (field, len(rows))]
|
|
486
|
-
for f, t, c, s in rows:
|
|
487
|
-
lines.append(" %s -> %s (用了%d次)" % (f, t, c))
|
|
488
|
-
return '\n'.join(lines)
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
def _tool_list_modules(platform=''):
|
|
492
|
-
from kg_duckdb import get_db
|
|
493
|
-
con = get_db()
|
|
494
|
-
if platform == 'web':
|
|
495
|
-
rows = con.execute("SELECT dir, cn, project FROM modules WHERE project LIKE '%fywl%' ORDER BY dir").fetchall()
|
|
496
|
-
elif platform == 'app':
|
|
497
|
-
rows = con.execute("SELECT dir, cn, project FROM modules WHERE project LIKE '%Carmg%' OR project LIKE '%H5%' ORDER BY dir").fetchall()
|
|
498
|
-
else:
|
|
499
|
-
rows = con.execute("SELECT dir, cn, project FROM modules ORDER BY project, dir").fetchall()
|
|
500
|
-
con.close()
|
|
501
|
-
if not rows:
|
|
502
|
-
return "无模块数据"
|
|
503
|
-
lines = ["模块清单 (%d个)" % len(rows)]
|
|
504
|
-
cur = ""
|
|
505
|
-
for d, cn, proj in rows:
|
|
506
|
-
if proj != cur:
|
|
507
|
-
cur = proj
|
|
508
|
-
lines.append("[%s]" % proj)
|
|
509
|
-
lines.append(" %s -> %s" % (d, cn))
|
|
510
|
-
return '\n'.join(lines)
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
def _execute_tool(name, arguments):
|
|
514
|
-
"""Execute a tool and return result text."""
|
|
515
|
-
_ensure_imports()
|
|
516
|
-
|
|
517
|
-
if name == "search_code":
|
|
518
|
-
kw = arguments.get("keyword", "")
|
|
519
|
-
plat = arguments.get("platform") or ""
|
|
520
|
-
return _capture(_search.search_keywords, kw, plat) or "无匹配结果"
|
|
521
|
-
|
|
522
|
-
elif name == "search_api":
|
|
523
|
-
return _capture(_search.search_api, arguments.get("keyword", "")) or "无匹配结果"
|
|
524
|
-
|
|
525
|
-
elif name == "search_prd":
|
|
526
|
-
return _capture(_search.search_prds, arguments.get("keyword", "")) or "无匹配PRD"
|
|
527
|
-
|
|
528
|
-
elif name == "get_impact":
|
|
529
|
-
return _capture(_search.search_impact, arguments.get("endpoint", "")) or "未找到匹配端点"
|
|
530
|
-
|
|
531
|
-
elif name == "context_360":
|
|
532
|
-
return _capture(_search.search_context360, arguments.get("symbol", "")) or "未找到匹配符号"
|
|
533
|
-
|
|
534
|
-
elif name == "context_pack":
|
|
535
|
-
kw = arguments.get("keyword", "")
|
|
536
|
-
plat = arguments.get("platform", "")
|
|
537
|
-
pt = arguments.get("page_type", "")
|
|
538
|
-
role = arguments.get("role", "")
|
|
539
|
-
full = _capture(_context._compute_and_print, kw, plat, pt)
|
|
540
|
-
if not full:
|
|
541
|
-
return "无匹配上下文"
|
|
542
|
-
if role:
|
|
543
|
-
return _filter_by_role(full, role)
|
|
544
|
-
return full
|
|
545
|
-
|
|
546
|
-
elif name == "fill_prototype":
|
|
547
|
-
# 调用 fill_prototype.py 的 main 逻辑 (捕获 stdout)
|
|
548
|
-
kw = arguments.get("keyword", "")
|
|
549
|
-
plat = arguments.get("platform", "web")
|
|
550
|
-
import subprocess as _sp
|
|
551
|
-
script = os.path.join(SCRIPTS_DIR, 'fill_prototype.py')
|
|
552
|
-
try:
|
|
553
|
-
r = _sp.run([sys.executable, script, kw, '--platform', plat],
|
|
554
|
-
capture_output=True, text=True, encoding='utf-8',
|
|
555
|
-
errors='replace', timeout=30, cwd=SCRIPTS_DIR)
|
|
556
|
-
return r.stdout or "fill_prototype 无输出 (可能索引缺失)"
|
|
557
|
-
except Exception as e:
|
|
558
|
-
return "[error] fill_prototype: %s" % str(e)
|
|
559
|
-
|
|
560
|
-
elif name == "get_design_system":
|
|
561
|
-
# 返回 DESIGN.md 内容 (若不存在则生成)
|
|
562
|
-
import subprocess as _sp
|
|
563
|
-
design_md = os.path.join(BASE_DIR, 'data', 'design', 'DESIGN.md')
|
|
564
|
-
if not os.path.isfile(design_md):
|
|
565
|
-
gen_script = os.path.join(SCRIPTS_DIR, 'gen_design_doc.py')
|
|
566
|
-
if os.path.isfile(gen_script):
|
|
567
|
-
_sp.run([sys.executable, gen_script], capture_output=True, timeout=30)
|
|
568
|
-
if os.path.isfile(design_md):
|
|
569
|
-
content = open(design_md, encoding='utf-8').read()
|
|
570
|
-
# 若指定了 platform, 只返回该平台的布局指纹段
|
|
571
|
-
plat = arguments.get("platform")
|
|
572
|
-
if plat:
|
|
573
|
-
lines = content.split('\n')
|
|
574
|
-
result = []
|
|
575
|
-
in_section = False
|
|
576
|
-
for line in lines:
|
|
577
|
-
if line.startswith('### ') and plat.lower() in line.lower():
|
|
578
|
-
in_section = True
|
|
579
|
-
elif line.startswith('### ') and in_section:
|
|
580
|
-
break
|
|
581
|
-
if in_section:
|
|
582
|
-
result.append(line)
|
|
583
|
-
return '\n'.join(result) if result else content
|
|
584
|
-
return content
|
|
585
|
-
return "DESIGN.md 不存在且生成失败。请手动跑: python gen_design_doc.py"
|
|
586
|
-
|
|
587
|
-
elif name == "table_impact":
|
|
588
|
-
# 改表影响分析: 表 → entity_table_map → Entity → 代码图谱 Controller/接口
|
|
589
|
-
from common.paths import DATA_INDEX_DIR as _idx
|
|
590
|
-
import duckdb as _ddb
|
|
591
|
-
db_path = str(_idx / 'kg_db.duckdb')
|
|
592
|
-
if not os.path.isfile(db_path):
|
|
593
|
-
return "数据层图谱未构建。请先跑: python .qoder/scripts/kg_build_db.py"
|
|
594
|
-
table = arguments.get("table", "").strip()
|
|
595
|
-
try:
|
|
596
|
-
con = _ddb.connect(db_path, read_only=True)
|
|
597
|
-
except Exception as e:
|
|
598
|
-
return "数据层图谱暂时不可用(可能正在重建): %s。稍后重试。" % str(e)[:60]
|
|
599
|
-
try:
|
|
600
|
-
# 1. 表 → Entity
|
|
601
|
-
ents = con.execute(
|
|
602
|
-
"SELECT DISTINCT entity_class FROM entity_table_map WHERE table_name = ?",
|
|
603
|
-
[table]
|
|
604
|
-
).fetchall()
|
|
605
|
-
ent_list = [r[0] for r in ents]
|
|
606
|
-
if not ent_list:
|
|
607
|
-
return "表 '%s' 在 entity_table_map 中无映射 (可能该表无 @TableName 注解的 Entity)" % table
|
|
608
|
-
lines = ["改表影响分析: %s" % table]
|
|
609
|
-
lines.append(" 对应 Entity: %s" % ', '.join(ent_list))
|
|
610
|
-
# 2. 表的真实列 (给个概览)
|
|
611
|
-
cols = con.execute(
|
|
612
|
-
"SELECT column_name, data_type, column_comment FROM db_columns "
|
|
613
|
-
"WHERE table_name = ? LIMIT 10", [table]
|
|
614
|
-
).fetchall()
|
|
615
|
-
if cols:
|
|
616
|
-
lines.append(" 表字段 (%d+):" % len(cols))
|
|
617
|
-
for cn, dt, cc in cols:
|
|
618
|
-
lines.append(" %s (%s) %s" % (cn, dt, (cc or '')[:30]))
|
|
619
|
-
# 3. Entity → 代码图谱 (attach kg.duckdb 查接口/Controller)
|
|
620
|
-
try:
|
|
621
|
-
con.execute("ATTACH '%s' AS code (READ_ONLY)" % str(_idx / 'kg.duckdb'))
|
|
622
|
-
lines.append(" 影响的接口/控制器:")
|
|
623
|
-
found = 0
|
|
624
|
-
for ec in ent_list:
|
|
625
|
-
refs = con.execute(
|
|
626
|
-
"SELECT type, canonical FROM code.entities "
|
|
627
|
-
"WHERE contains(canonical, ?) AND type IN ('CONTROLLER','ENDPOINT') "
|
|
628
|
-
"LIMIT 12", [ec]
|
|
629
|
-
).fetchall()
|
|
630
|
-
for t, c in refs:
|
|
631
|
-
lines.append(" [%s] %s" % (t, c))
|
|
632
|
-
found += 1
|
|
633
|
-
if not found:
|
|
634
|
-
lines.append(" (代码图谱中未找到引用 %s 的接口)" % ent_list[0])
|
|
635
|
-
con.execute("DETACH code")
|
|
636
|
-
except Exception as e:
|
|
637
|
-
lines.append(" (代码图谱查询失败: %s)" % str(e)[:60])
|
|
638
|
-
return '\n'.join(lines)
|
|
639
|
-
finally:
|
|
640
|
-
con.close()
|
|
641
|
-
|
|
642
|
-
elif name == "coverage_matrix":
|
|
643
|
-
from common.graph_traverse import CodeGraph
|
|
644
|
-
from common.paths import DATA_INDEX_DIR as _idx
|
|
645
|
-
g = CodeGraph(str(_idx))
|
|
646
|
-
result = g.coverage_matrix()
|
|
647
|
-
return result or "无法生成覆盖矩阵"
|
|
648
|
-
|
|
649
|
-
elif name == "feature_overview":
|
|
650
|
-
from common.graph_traverse import CodeGraph
|
|
651
|
-
from common.paths import DATA_INDEX_DIR as _idx
|
|
652
|
-
g = CodeGraph(str(_idx))
|
|
653
|
-
result = g.feature_overview(arguments.get("feature", ""))
|
|
654
|
-
if result and '不存在' not in result:
|
|
655
|
-
return result
|
|
656
|
-
# DuckDB 兜底: entity-registry.json 缺失时, 用 DuckDB 直接查
|
|
657
|
-
feat = arguments.get("feature", "")
|
|
658
|
-
fl = feat.lower()
|
|
659
|
-
try:
|
|
660
|
-
import duckdb as _ddb
|
|
661
|
-
con = _ddb.connect(str(_idx / 'kg.duckdb'), read_only=True)
|
|
662
|
-
# 端点
|
|
663
|
-
eps = con.execute(
|
|
664
|
-
"SELECT canonical FROM entities WHERE type='ENDPOINT' AND "
|
|
665
|
-
"(lower(canonical) LIKE ? OR lower(canonical) LIKE ?) LIMIT 15",
|
|
666
|
-
['%'+fl+'%', '%'+feat+'%']).fetchall()
|
|
667
|
-
# 按钮
|
|
668
|
-
btns = con.execute(
|
|
669
|
-
"SELECT canonical FROM entities WHERE type='BUTTON' AND "
|
|
670
|
-
"lower(canonical) LIKE ? LIMIT 10", ['%'+fl+'%']).fetchall()
|
|
671
|
-
con.close()
|
|
672
|
-
lines = ['功能画像: %s (DuckDB兜底)' % feat]
|
|
673
|
-
if eps:
|
|
674
|
-
lines.append('端点(%d):' % len(eps))
|
|
675
|
-
for (e,) in eps[:10]:
|
|
676
|
-
lines.append(' %s' % e)
|
|
677
|
-
if btns:
|
|
678
|
-
lines.append('按钮(%d):' % len(btns))
|
|
679
|
-
for (b,) in btns[:5]:
|
|
680
|
-
lines.append(' %s' % b)
|
|
681
|
-
if not eps and not btns:
|
|
682
|
-
lines.append(' (无匹配。换关键词重试)')
|
|
683
|
-
return chr(10).join(lines)
|
|
684
|
-
except Exception as e:
|
|
685
|
-
return '未找到功能 "%s" (DuckDB兜底也失败: %s)' % (feat, str(e)[:60])
|
|
686
|
-
|
|
687
|
-
elif name == "get_workflow":
|
|
688
|
-
return _tool_get_workflow(arguments.get("module", ""))
|
|
689
|
-
|
|
690
|
-
elif name == "search_wiki":
|
|
691
|
-
return _tool_search_wiki(arguments.get("keyword", ""))
|
|
692
|
-
|
|
693
|
-
elif name == "multi_hop":
|
|
694
|
-
return _tool_multi_hop(arguments.get("symbol", ""),
|
|
695
|
-
arguments.get("max_depth", 3))
|
|
696
|
-
|
|
697
|
-
elif name == "search_field":
|
|
698
|
-
return _tool_search_field(arguments.get("field", ""))
|
|
699
|
-
|
|
700
|
-
elif name == "list_modules":
|
|
701
|
-
return _tool_list_modules(arguments.get("platform", ""))
|
|
702
|
-
|
|
703
|
-
return "未知工具: %s" % name
|
|
704
|
-
|
|
705
|
-
|
|
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
|
-
})
|
|
757
|
-
|
|
758
|
-
elif method == "ping":
|
|
759
|
-
_send({"jsonrpc": "2.0", "id": req_id, "result": {}})
|
|
760
|
-
|
|
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
|
-
|
|
766
|
-
|
|
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.
|
|
772
|
-
"""
|
|
773
|
-
import threading
|
|
774
|
-
|
|
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)
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
if __name__ == "__main__":
|
|
798
|
-
try:
|
|
799
|
-
main()
|
|
800
|
-
except KeyboardInterrupt:
|
|
801
|
-
pass
|