@hupan56/wlkj 3.3.5 → 3.3.7
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/package.json +1 -1
- package/templates/qoder/commands/wl-prd.md +23 -0
- package/templates/qoder/commands/wl-search.md +19 -0
- package/templates/qoder/scripts/domain/kg/kg.py +14 -0
- package/templates/qoder/scripts/domain/task/wlkj_panel.py +74 -13
- package/templates/qoder/scripts/orchestration/wlkj.py +11 -0
package/package.json
CHANGED
|
@@ -23,6 +23,29 @@ User input: $ARGUMENTS
|
|
|
23
23
|
| 生成 PRD 正文 | **Qoder AI** | 宿主 AI 比我们调 qwen-plus 更强 |
|
|
24
24
|
| 写文件 | **Qoder Write** | 原生文件操作 |
|
|
25
25
|
|
|
26
|
+
## 🚨 工具接口铁律(防思考链跑飞,先读)
|
|
27
|
+
|
|
28
|
+
**① 一个门面,名字随便叫都对。** 知识查询统一走 `wlkj.py` 或 `kg.py`,**三套名字已互通**(引擎层别名):
|
|
29
|
+
| 你想干 | wlkj.py | kg.py | MCP 工具名 |
|
|
30
|
+
|---|---|---|---|
|
|
31
|
+
| 搜代码 | `wlkj.py search <词>` | `kg.py search <词>` | search_code |
|
|
32
|
+
| 搜 API | `wlkj.py search <词>` | `kg.py api <词>` | search_api |
|
|
33
|
+
| 语义检索 | `wlkj.py semantic <词>` | `kg.py semantic <词>` | rag_search |
|
|
34
|
+
| 上下文打包 | `wlkj.py context <词>` | `kg.py context <词>` | context_pack |
|
|
35
|
+
> 用哪个名都行(`kg.py search_code` 也认)。**别再因"未知子命令"慌张**。
|
|
36
|
+
|
|
37
|
+
**② 中文歧义词先提炼再查**(否则 context "保险" 命中的是社保不是车险):
|
|
38
|
+
- 保险 → 车辆保险 / 商业险 / 交强险 / 保险异常
|
|
39
|
+
- 异常 → 告警 alarmType / 报错 / 校验失败
|
|
40
|
+
- 计划 → 作业计划 / 养护计划 / 计划任务
|
|
41
|
+
- 列表/记录 → 带具体业务对象(车辆保险记录、考勤记录)
|
|
42
|
+
> 拿不准时先 `kg.py search <具体词>` 看返回的控制器/实体名,再用全名 `kg.py context <全名>`。
|
|
43
|
+
|
|
44
|
+
**③ 禁止跌入手动 grep/find 读源码。** 知识层有就拿知识层;**只有**以下情况才用 Grep/Read 读源码:
|
|
45
|
+
- 知识层确实没建(search 返 0 条,且换词仍 0)
|
|
46
|
+
- 要看某个具体文件的实现细节(已定位到文件)
|
|
47
|
+
> ❌ 不要 `find data/code` / `grep -r` 找模块——那是知识层 search 的活,手动做=思考链跑飞。
|
|
48
|
+
|
|
26
49
|
## 🧠 Planning Agent(完整模式强制前置 · 非建议)
|
|
27
50
|
|
|
28
51
|
> ⚠️ **完整模式必须先 Planning 再生成**(不再是"建议")。
|
|
@@ -20,6 +20,25 @@ result = cap.mcp.call("rag_search", {"query": "车辆保养", "top_k": 8})
|
|
|
20
20
|
|
|
21
21
|
自动路由:有 MCP 走 MCP(直连平台知识层,快),无 MCP 走 wlkj.py(CLI 降级)。零宿主感知。
|
|
22
22
|
|
|
23
|
+
## 🚨 接口铁律(防混名/防歧义,先读)
|
|
24
|
+
|
|
25
|
+
**① 三套名字已互通**(引擎层别名,AI 猜哪个都对,别因"未知子命令"慌):
|
|
26
|
+
| 干啥 | wlkj.py / kg.py 子命令 | MCP 工具名 |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| 搜代码 | `search <词>` | search_code |
|
|
29
|
+
| 搜 API | `api <词>`(kg.py)/ `search <词>`(wlkj.py)| search_api |
|
|
30
|
+
| 语义检索 | `semantic <词>` | rag_search |
|
|
31
|
+
| 上下文 | `context <词>` | context_pack |
|
|
32
|
+
| 影响分析 | `impact <实体>` | get_impact |
|
|
33
|
+
> `kg.py search_code 保险` = `kg.py search 保险`,都认。
|
|
34
|
+
|
|
35
|
+
**② 中文歧义词先提炼**(否则"保险"命中社保不是车险):
|
|
36
|
+
保险→车辆保险/商业险/交强险 · 异常→告警alarmType/报错 · 计划→作业/养护计划 · 列表→带业务对象(车辆保险记录)。
|
|
37
|
+
拿不准先 `search <具体词>` 看返回的控制器名,再用全名 `context <全名>`。
|
|
38
|
+
|
|
39
|
+
**③ 禁止 find/grep 手动找模块**——那是 search 的活。只有 search 换词仍返 0 或要看具体文件实现时,才用 Grep/Read。
|
|
40
|
+
|
|
41
|
+
|
|
23
42
|
## ⚡ 第一步:先判断问题类型,选对检索通道(3 路检索)
|
|
24
43
|
|
|
25
44
|
**核心升级:本命令有 3 路检索,按问题类型选最合适的一路(不是无脑 search_code)。**
|
|
@@ -872,6 +872,20 @@ def main(argv=None):
|
|
|
872
872
|
return 0
|
|
873
873
|
cmd = argv[0]
|
|
874
874
|
rest = argv[1:]
|
|
875
|
+
# ★ MCP 名兼容:AI 常把 MCP 工具名(search_code)当 kg.py 子命令调 → "未知子命令"。
|
|
876
|
+
# 在此统一归一化,AI 猜哪个名(kg.py 子命令 / MCP 工具名)都能命中(治工作流混名)。
|
|
877
|
+
_MCP_ALIASES = {
|
|
878
|
+
'search_code': 'search', 'search_api': 'api', 'search_prds': 'prd',
|
|
879
|
+
'search_field': 'search', 'search_style': 'search', 'search_wiki': 'wiki',
|
|
880
|
+
'rag_search': 'semantic', 'ask_corpus': 'semantic',
|
|
881
|
+
'context_pack': 'context', 'context_360': 'context360',
|
|
882
|
+
'get_impact': 'impact', 'multi_hop': 'hop',
|
|
883
|
+
'coverage_matrix': 'coverage', 'feature_overview': 'feature',
|
|
884
|
+
'get_workflow': 'workflow', 'get_design_system': 'design-system',
|
|
885
|
+
'fill_prototype': 'fill-prototype',
|
|
886
|
+
}
|
|
887
|
+
if cmd in _MCP_ALIASES:
|
|
888
|
+
cmd = _MCP_ALIASES[cmd]
|
|
875
889
|
# 查找子命令
|
|
876
890
|
handler = None
|
|
877
891
|
for name, fn, _ in COMMANDS:
|
|
@@ -51,9 +51,21 @@ from foundation.integrations.zentao_client import ZentaoClient
|
|
|
51
51
|
# ============================================================
|
|
52
52
|
|
|
53
53
|
def _zentao_creds():
|
|
54
|
-
"""
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
"""构建禅道客户端。返回 (client, preset_account)。
|
|
55
|
+
|
|
56
|
+
凭据源优先级:
|
|
57
|
+
① 云平台 per-user 凭据(Option A:get_zentao_creds)—— account 已知(preset)
|
|
58
|
+
② 本地 .qoder/config.yaml + secrets/zentao.env —— account 走 member.json
|
|
59
|
+
云凭据优先:demo 机器在内网可达禅道,平台凭据比本地文件更可能是最新配的。
|
|
60
|
+
凭据全无 → (None, '')。
|
|
61
|
+
"""
|
|
62
|
+
c = _cloud_zentao_creds()
|
|
63
|
+
if c and c.get('ok') and c.get('url') and c.get('account') and c.get('password'):
|
|
64
|
+
try:
|
|
65
|
+
return ZentaoClient(c['url'], c['account'], c['password']), c['account']
|
|
66
|
+
except Exception:
|
|
67
|
+
pass
|
|
68
|
+
return ZentaoClient.from_config(), ''
|
|
57
69
|
|
|
58
70
|
|
|
59
71
|
def _cloud_my_panel():
|
|
@@ -107,6 +119,52 @@ def _cloud_my_panel():
|
|
|
107
119
|
return data # {ok:True/False, ...}
|
|
108
120
|
|
|
109
121
|
|
|
122
|
+
def _cloud_zentao_creds():
|
|
123
|
+
"""Option A:从平台取禅道凭据(url/account/password 解密)。
|
|
124
|
+
|
|
125
|
+
用途:云到禅道被防火墙挡(my-panel 登录失败)时,本机(demo 机器,内网可达)
|
|
126
|
+
用平台凭据直连禅道拉真实数据。凭据随绑定 token 取本人配置(同信任级)。
|
|
127
|
+
Returns: {ok:True, url, account, password} 或 None(云不可达/未配置)。
|
|
128
|
+
"""
|
|
129
|
+
import json as _json
|
|
130
|
+
import urllib.request as _u
|
|
131
|
+
cfg_candidates = []
|
|
132
|
+
try:
|
|
133
|
+
from foundation.core.paths import get_repo_root
|
|
134
|
+
cfg_candidates.append(os.path.join(str(get_repo_root()), '.qoder', 'mcp_config.json'))
|
|
135
|
+
except Exception:
|
|
136
|
+
pass
|
|
137
|
+
cfg_candidates.append(os.path.join(_SCRIPTS, '..', 'mcp_config.json'))
|
|
138
|
+
token, url = '', ''
|
|
139
|
+
for cp in cfg_candidates:
|
|
140
|
+
try:
|
|
141
|
+
with open(cp, encoding='utf-8') as f:
|
|
142
|
+
cfg = _json.load(f)
|
|
143
|
+
srv = (cfg.get('mcpServers') or {}).get('wlinkj-knowledge') or {}
|
|
144
|
+
token = srv.get('token', '')
|
|
145
|
+
url = (srv.get('url') or '').rstrip('/')
|
|
146
|
+
if token and url:
|
|
147
|
+
break
|
|
148
|
+
except Exception:
|
|
149
|
+
continue
|
|
150
|
+
if not token or not url:
|
|
151
|
+
return None
|
|
152
|
+
endpoint = url if url.endswith('/mcp') else url + '/mcp'
|
|
153
|
+
body = _json.dumps({"jsonrpc": "2.0", "id": 1, "method": "tools/call",
|
|
154
|
+
"params": {"name": "get_zentao_creds", "arguments": {}}}).encode('utf-8')
|
|
155
|
+
req = _u.Request(endpoint, data=body, method='POST')
|
|
156
|
+
req.add_header('Content-Type', 'application/json')
|
|
157
|
+
req.add_header('X-MCP-Token', token)
|
|
158
|
+
try:
|
|
159
|
+
with _u.urlopen(req, timeout=15) as resp:
|
|
160
|
+
r = _json.loads(resp.read().decode('utf-8', errors='replace'))
|
|
161
|
+
text = r.get('result', {}).get('content', [{}])[0].get('text', '')
|
|
162
|
+
data = _json.loads(text) if text else {}
|
|
163
|
+
return data if data.get('ok') else None
|
|
164
|
+
except Exception:
|
|
165
|
+
return None
|
|
166
|
+
|
|
167
|
+
|
|
110
168
|
def _resolve_my_account(developer):
|
|
111
169
|
"""解析当前 developer → 禅道 account。返回 (account, 错误原因)。"""
|
|
112
170
|
mem = get_member(developer)
|
|
@@ -451,28 +509,31 @@ def fetch_workbench(include_closed=False, kind=None):
|
|
|
451
509
|
else:
|
|
452
510
|
cloud_error = None # 云不可达(switch_project 没跑/平台宕机)→ 静默回退本地
|
|
453
511
|
|
|
454
|
-
# ①
|
|
455
|
-
client = _zentao_creds()
|
|
512
|
+
# ① 凭据(云平台优先 Option A,回退本地文件)
|
|
513
|
+
client, preset_account = _zentao_creds()
|
|
456
514
|
if client is None:
|
|
457
|
-
base_msg = ('禅道凭据未配置。推荐在【平台设置 → 禅道】配置(panel
|
|
515
|
+
base_msg = ('禅道凭据未配置。推荐在【平台设置 → 禅道】配置(panel 自动走云取凭据);'
|
|
458
516
|
'或本地 .qoder/config.yaml 的 zentao.url + secrets/zentao.env 账号密码。')
|
|
459
517
|
if cloud_error:
|
|
460
|
-
base_msg += '
|
|
518
|
+
base_msg += '(云 my-panel 失败:%s)' % cloud_error
|
|
461
519
|
return {'ok': False, 'error': base_msg}, 3
|
|
462
520
|
|
|
463
521
|
ok, msg = client.credentials_ok()
|
|
464
522
|
if not ok:
|
|
465
523
|
return {'ok': False, 'error': '禅道凭据不完整: %s' % msg}, 3
|
|
466
524
|
|
|
467
|
-
# ②
|
|
525
|
+
# ② 内网连通(本机能否直连禅道——demo 机器在内网可达)
|
|
468
526
|
ok, msg = client.check_intranet()
|
|
469
527
|
if not ok:
|
|
470
|
-
return {'ok': False, 'error': '
|
|
528
|
+
return {'ok': False, 'error': '本机连不上禅道内网: %s' % msg}, 5
|
|
471
529
|
|
|
472
|
-
# ③
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
530
|
+
# ③ 归属(云凭据带的 account 优先;否则走 member.json)
|
|
531
|
+
if preset_account:
|
|
532
|
+
my_account = preset_account
|
|
533
|
+
else:
|
|
534
|
+
my_account, err = _resolve_my_account(developer) if developer else (None, '未识别当前开发者身份。')
|
|
535
|
+
if not my_account:
|
|
536
|
+
return {'ok': False, 'error': err}, 3
|
|
476
537
|
|
|
477
538
|
# ④ 收集 (三域并行, 按 kind 决定取哪几域)
|
|
478
539
|
from concurrent.futures import ThreadPoolExecutor
|
|
@@ -47,6 +47,17 @@ DISPATCH = {
|
|
|
47
47
|
'kg-incremental': 'domain/kg/build/kg_incremental.py',
|
|
48
48
|
'kg-semantic': 'domain/kg/graph/kg_semantic.py',
|
|
49
49
|
'semantic': 'domain/kg/graph/kg_semantic.py',
|
|
50
|
+
# ★ MCP 名别名(AI 常把 MCP 工具名当 wlkj.py 命令调 → 未知命令。归一化后两边名都认)
|
|
51
|
+
'search_code': 'domain/kg/search/search_index.py',
|
|
52
|
+
'search_api': 'domain/kg/search/search_index.py',
|
|
53
|
+
'search_prds': 'domain/kg/search/search_index.py',
|
|
54
|
+
'search_field': 'domain/kg/search/search_index.py',
|
|
55
|
+
'search_style': 'domain/kg/search/search_index.py',
|
|
56
|
+
'rag_search': 'domain/kg/graph/kg_semantic.py',
|
|
57
|
+
'ask_corpus': 'domain/kg/graph/kg_semantic.py',
|
|
58
|
+
'context_pack': 'domain/kg/search/context_pack.py',
|
|
59
|
+
'context_360': 'domain/kg/search/context_pack.py',
|
|
60
|
+
'get_impact': 'domain/kg/search/search_index.py',
|
|
50
61
|
'style': 'domain/kg/build/build_style_index.py',
|
|
51
62
|
# ── 任务 / 同步 ──
|
|
52
63
|
'task': 'domain/task/task.py',
|