@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hupan56/wlkj",
3
- "version": "3.3.5",
3
+ "version": "3.3.7",
4
4
  "description": "AI Product R&D Workflow - PRD/Prototype/Search/Task/Report",
5
5
  "bin": {
6
6
  "wlkj": "bin/cli.js"
@@ -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
- """构建禅道客户端。凭据不全返回 None
55
- url 来自 .qoder/config.yaml (团队共享); user/password 来自 secrets/zentao.env (个人)。"""
56
- return ZentaoClient.from_config()
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 += '(注:平台已配凭据但云端连不上禅道:%s;本机若在内网可配本地凭据直连。)' % cloud_error
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': '连不上禅道内网: %s' % msg}, 5
528
+ return {'ok': False, 'error': '本机连不上禅道内网: %s' % msg}, 5
471
529
 
472
- # ③ 归属
473
- my_account, err = _resolve_my_account(developer) if developer else (None, '未识别当前开发者身份。')
474
- if not my_account:
475
- return {'ok': False, 'error': err}, 3
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',