@hupan56/wlkj 3.3.3 → 3.3.5
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 +29 -29
- package/templates/qoder/scripts/capability/adapters/mcp.py +8 -1
- package/templates/qoder/scripts/capability/registry_mcp.py +9 -3
- package/templates/qoder/scripts/domain/kg/graph/kg_semantic.py +41 -5
- package/templates/qoder/scripts/domain/task/wlkj_panel.py +98 -4
- package/templates/qoder/scripts/capability/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/__pycache__/present_html.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/__pycache__/registry.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/__pycache__/registry_mcp.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/adapters/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/adapters/__pycache__/cli.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/adapters/__pycache__/mcp.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/adapters/__pycache__/qw.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/caps/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/caps/__pycache__/context.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/caps/__pycache__/cron.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/caps/__pycache__/identity.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/caps/__pycache__/memory.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/caps/__pycache__/notify.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/caps/__pycache__/present.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/caps/__pycache__/repo.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/caps/__pycache__/sandbox.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/domain/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/domain/integration/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/domain/integration/__pycache__/return_to_platform.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/domain/task/__pycache__/wlkj_panel.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/foundation/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/foundation/core/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/foundation/core/__pycache__/paths.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/protocol/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/protocol/transports/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/protocol/transports/__pycache__/base.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/protocol/transports/__pycache__/cli.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/protocol/transports/__pycache__/http.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/protocol/transports/__pycache__/stdio.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/validation/metrics/__pycache__/present_board.cpython-39.pyc +0 -0
package/package.json
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@hupan56/wlkj",
|
|
3
|
-
"version": "3.3.
|
|
4
|
-
"description": "AI Product R&D Workflow - PRD/Prototype/Search/Task/Report",
|
|
5
|
-
"bin": {
|
|
6
|
-
"wlkj": "bin/cli.js"
|
|
7
|
-
},
|
|
8
|
-
"files": [
|
|
9
|
-
"bin/",
|
|
10
|
-
"templates/",
|
|
11
|
-
"MAC-VERIFY.md"
|
|
12
|
-
],
|
|
13
|
-
"keywords": [
|
|
14
|
-
"workflow",
|
|
15
|
-
"ai",
|
|
16
|
-
"prd",
|
|
17
|
-
"pipeline",
|
|
18
|
-
"qoder",
|
|
19
|
-
"product",
|
|
20
|
-
"team"
|
|
21
|
-
],
|
|
22
|
-
"license": "MIT",
|
|
23
|
-
"publishConfig": {
|
|
24
|
-
"access": "public"
|
|
25
|
-
},
|
|
26
|
-
"engines": {
|
|
27
|
-
"node": ">=16"
|
|
28
|
-
}
|
|
29
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@hupan56/wlkj",
|
|
3
|
+
"version": "3.3.5",
|
|
4
|
+
"description": "AI Product R&D Workflow - PRD/Prototype/Search/Task/Report",
|
|
5
|
+
"bin": {
|
|
6
|
+
"wlkj": "bin/cli.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin/",
|
|
10
|
+
"templates/",
|
|
11
|
+
"MAC-VERIFY.md"
|
|
12
|
+
],
|
|
13
|
+
"keywords": [
|
|
14
|
+
"workflow",
|
|
15
|
+
"ai",
|
|
16
|
+
"prd",
|
|
17
|
+
"pipeline",
|
|
18
|
+
"qoder",
|
|
19
|
+
"product",
|
|
20
|
+
"team"
|
|
21
|
+
],
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=16"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -132,6 +132,11 @@ class McpCap:
|
|
|
132
132
|
|
|
133
133
|
def _cli_fallback(self, tool_name: str, args: dict) -> Optional[str]:
|
|
134
134
|
"""MCP 工具名 → wlkj.py 执行。无映射返回 None。"""
|
|
135
|
+
# ★ 递归守卫:search_code→kg search→search_index→call_remote→call→_cli_fallback 会无限套娃。
|
|
136
|
+
# 已在降级子进程内(_run_wlkj 设了 _WLKJ_IN_CLI_FALLBACK=1)则直接放弃降级,返回原始错误。
|
|
137
|
+
import os as _os
|
|
138
|
+
if _os.environ.get("_WLKJ_IN_CLI_FALLBACK"):
|
|
139
|
+
return None
|
|
135
140
|
entry = self._TOOL_TO_WLKJ.get(tool_name)
|
|
136
141
|
if entry is None:
|
|
137
142
|
return None
|
|
@@ -183,10 +188,12 @@ class McpCap:
|
|
|
183
188
|
repo = os.getcwd()
|
|
184
189
|
wlkj = os.path.join(repo, ".qoder", "scripts", "orchestration", "wlkj.py")
|
|
185
190
|
try:
|
|
191
|
+
# 递归守卫:子进程内 _WLKJ_IN_CLI_FALLBACK=1,其 McpCap 不再降级(否则套娃)
|
|
192
|
+
env = dict(os.environ, _WLKJ_IN_CLI_FALLBACK="1")
|
|
186
193
|
r = subprocess.run(
|
|
187
194
|
[sys.executable, wlkj, cmd] + args,
|
|
188
195
|
capture_output=True, text=True, timeout=60,
|
|
189
|
-
encoding="utf-8", errors="replace",
|
|
196
|
+
encoding="utf-8", errors="replace", env=env,
|
|
190
197
|
)
|
|
191
198
|
if r.returncode == 0:
|
|
192
199
|
return r.stdout.strip()
|
|
@@ -73,16 +73,22 @@ SERVERS: dict[str, dict] = {
|
|
|
73
73
|
|
|
74
74
|
|
|
75
75
|
def _mcp_config_path() -> Path:
|
|
76
|
-
"""定位 mcp_config.json(在仓库根/宿主下)。
|
|
76
|
+
"""定位 mcp_config.json(在仓库根/宿主下)。
|
|
77
|
+
|
|
78
|
+
★ switch_project.py 实际写入位置是 {repo}/.qoder/mcp_config.json(云模式连云 bind 后),
|
|
79
|
+
必须优先查这里——否则读不到 token,transport 用空 token 调云 MCP 鉴权失败(历史 bug:
|
|
80
|
+
引擎 search 全返回"MCP返回非JSON"+递归降级套娃)。老形态 repo/mcp_config.json 兼容。
|
|
81
|
+
"""
|
|
77
82
|
repo = _get_repo_root()
|
|
78
83
|
# registry_mcp.py 在 .../scripts/capability/, mcp_config.json 在宿主根(.qoder)。
|
|
79
84
|
for cand in [
|
|
80
|
-
repo / "mcp_config.json",
|
|
85
|
+
repo / ".qoder" / "mcp_config.json", # ★ 云模式实际写入位置(switch_project)
|
|
86
|
+
repo / "mcp_config.json", # 老形态:仓库根
|
|
81
87
|
repo / "registry_mcp_config.json",
|
|
82
88
|
]:
|
|
83
89
|
if cand.is_file():
|
|
84
90
|
return cand
|
|
85
|
-
return repo / "mcp_config.json"
|
|
91
|
+
return repo / ".qoder" / "mcp_config.json"
|
|
86
92
|
|
|
87
93
|
|
|
88
94
|
def _load_servers() -> dict[str, dict]:
|
|
@@ -278,6 +278,31 @@ def build_embeddings(batch_size=64):
|
|
|
278
278
|
con.close()
|
|
279
279
|
|
|
280
280
|
|
|
281
|
+
def _cloud_rag_search(query, top_k=10):
|
|
282
|
+
"""云优先语义搜索:调工作台 MCP rag_search(本地无 KG/embedding,全迁云)。
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
[(entity_id, text, score), ...] 或 None(云不可用时返回 None,让调用方退本地)。
|
|
286
|
+
"""
|
|
287
|
+
try:
|
|
288
|
+
from domain.kg.search._remote import call_remote
|
|
289
|
+
data = call_remote("rag_search", {"query": query, "top_k": top_k})
|
|
290
|
+
except Exception:
|
|
291
|
+
return None
|
|
292
|
+
items = data.get("items") or []
|
|
293
|
+
out = []
|
|
294
|
+
for it in items[:top_k]:
|
|
295
|
+
eid = it.get("entity_id") or it.get("id") or ""
|
|
296
|
+
text = it.get("text") or it.get("name") or ""
|
|
297
|
+
score = it.get("score")
|
|
298
|
+
try:
|
|
299
|
+
score = float(score) if score is not None else 0.0
|
|
300
|
+
except Exception:
|
|
301
|
+
score = 0.0
|
|
302
|
+
out.append((eid, text, score))
|
|
303
|
+
return out
|
|
304
|
+
|
|
305
|
+
|
|
281
306
|
def semantic_search(query, top_k=10):
|
|
282
307
|
"""语义搜索: query→向量→余弦相似度→top_k 实体。
|
|
283
308
|
|
|
@@ -656,13 +681,24 @@ if __name__ == '__main__':
|
|
|
656
681
|
n = build_embeddings()
|
|
657
682
|
print('Built %d embeddings' % n)
|
|
658
683
|
|
|
659
|
-
elif len(sys.argv) > 2 and sys.argv[1] == 'search'
|
|
660
|
-
|
|
684
|
+
elif (len(sys.argv) > 2 and sys.argv[1] == 'search') or \
|
|
685
|
+
(len(sys.argv) > 1 and sys.argv[1] not in ('--check', '--label', 'build-embeddings', '--status', '--backfill-cn')):
|
|
686
|
+
# 兼容两种姿势: `search <词>` 和裸词 `<词>`(wlkj.py semantic 保险异常 透传过来)。
|
|
687
|
+
# ★ 云优先:本地无 KG/embedding(全迁云),先调云 rag_search;云不可用才退本地 semantic_search。
|
|
688
|
+
query = sys.argv[2] if sys.argv[1] == 'search' else sys.argv[1]
|
|
689
|
+
results = _cloud_rag_search(query)
|
|
690
|
+
if results is None:
|
|
691
|
+
# 云不可用 → 退本地(开发期有本地 embedding 时仍可用)
|
|
692
|
+
results = semantic_search(query)
|
|
661
693
|
if results:
|
|
662
|
-
for
|
|
663
|
-
|
|
694
|
+
for row in results:
|
|
695
|
+
if len(row) == 3:
|
|
696
|
+
eid, text, score = row
|
|
697
|
+
print(' %.3f %s' % (score, text[:70]))
|
|
698
|
+
else:
|
|
699
|
+
print(' %s' % row)
|
|
664
700
|
else:
|
|
665
|
-
print('(
|
|
701
|
+
print('(无结果或云 rag_search 不可用 + 本地 embedding 未构建)')
|
|
666
702
|
|
|
667
703
|
elif len(sys.argv) > 1 and sys.argv[1] == '--status':
|
|
668
704
|
st = embedding_status()
|
|
@@ -56,6 +56,57 @@ def _zentao_creds():
|
|
|
56
56
|
return ZentaoClient.from_config()
|
|
57
57
|
|
|
58
58
|
|
|
59
|
+
def _cloud_my_panel():
|
|
60
|
+
"""云优先:调平台 get_my_zentao_panel(凭据走平台 per-user 配置,不出平台)。
|
|
61
|
+
|
|
62
|
+
直连云 /mcp JSON-RPC(读 .qoder/mcp_config.json 的 token+url)。
|
|
63
|
+
Returns: {ok:True, tasks, bugs, stories, account} 或 None(云不可达/工具失败)。
|
|
64
|
+
云返 {ok:False,error}(未配置/登录失败)→ 返该 dict(调用方据此提示+回退本地)。
|
|
65
|
+
"""
|
|
66
|
+
import json as _json
|
|
67
|
+
import urllib.request as _u
|
|
68
|
+
import urllib.error as _ue
|
|
69
|
+
# 定位 mcp_config.json(switch_project 写在 .qoder/)
|
|
70
|
+
cfg_candidates = []
|
|
71
|
+
try:
|
|
72
|
+
from foundation.core.paths import get_repo_root
|
|
73
|
+
cfg_candidates.append(os.path.join(str(get_repo_root()), '.qoder', 'mcp_config.json'))
|
|
74
|
+
except Exception:
|
|
75
|
+
pass
|
|
76
|
+
cfg_candidates.append(os.path.join(_SCRIPTS, '..', 'mcp_config.json'))
|
|
77
|
+
token, url = '', ''
|
|
78
|
+
for cp in cfg_candidates:
|
|
79
|
+
try:
|
|
80
|
+
with open(cp, encoding='utf-8') as f:
|
|
81
|
+
cfg = _json.load(f)
|
|
82
|
+
srv = (cfg.get('mcpServers') or {}).get('wlinkj-knowledge') or {}
|
|
83
|
+
token = srv.get('token', '')
|
|
84
|
+
url = (srv.get('url') or '').rstrip('/')
|
|
85
|
+
if token and url:
|
|
86
|
+
break
|
|
87
|
+
except Exception:
|
|
88
|
+
continue
|
|
89
|
+
if not token or not url:
|
|
90
|
+
return None # 云未绑定(switch_project 没跑)→ 回退本地
|
|
91
|
+
endpoint = url if url.endswith('/mcp') else url + '/mcp'
|
|
92
|
+
body = _json.dumps({"jsonrpc": "2.0", "id": 1, "method": "tools/call",
|
|
93
|
+
"params": {"name": "get_my_zentao_panel", "arguments": {}}}).encode('utf-8')
|
|
94
|
+
req = _u.Request(endpoint, data=body, method='POST')
|
|
95
|
+
req.add_header('Content-Type', 'application/json')
|
|
96
|
+
req.add_header('X-MCP-Token', token)
|
|
97
|
+
try:
|
|
98
|
+
with _u.urlopen(req, timeout=30) as resp:
|
|
99
|
+
r = _json.loads(resp.read().decode('utf-8', errors='replace'))
|
|
100
|
+
except Exception:
|
|
101
|
+
return None # 网络不通 → 回退本地
|
|
102
|
+
try:
|
|
103
|
+
text = r.get('result', {}).get('content', [{}])[0].get('text', '')
|
|
104
|
+
data = _json.loads(text) if text else {}
|
|
105
|
+
except Exception:
|
|
106
|
+
return None
|
|
107
|
+
return data # {ok:True/False, ...}
|
|
108
|
+
|
|
109
|
+
|
|
59
110
|
def _resolve_my_account(developer):
|
|
60
111
|
"""解析当前 developer → 禅道 account。返回 (account, 错误原因)。"""
|
|
61
112
|
mem = get_member(developer)
|
|
@@ -359,12 +410,55 @@ def fetch_workbench(include_closed=False, kind=None):
|
|
|
359
410
|
"""
|
|
360
411
|
developer = get_developer()
|
|
361
412
|
|
|
362
|
-
#
|
|
413
|
+
# ⓪ 云优先:调平台 get_my_zentao_panel(凭据走平台 per-user,不出平台)。
|
|
414
|
+
# 成功 → 直接用云数据,跳过本地凭据/连通/归属/收集(① ~ ④)。
|
|
415
|
+
# 未配置/登录失败 → 返回 {ok:False} 提示用户去平台配置,并回退本地(本地配了仍可用)。
|
|
416
|
+
# 云不可达(返回 None) → 静默回退本地全流程。
|
|
417
|
+
cloud = _cloud_my_panel()
|
|
418
|
+
if isinstance(cloud, dict):
|
|
419
|
+
# 云可达 → 云的判断权威(平台是凭据源)。
|
|
420
|
+
if cloud.get('ok'):
|
|
421
|
+
tasks = cloud.get('tasks') or []
|
|
422
|
+
bugs = cloud.get('bugs') or []
|
|
423
|
+
stories = cloud.get('stories') or []
|
|
424
|
+
# 按 kind 裁剪(与本地一致)
|
|
425
|
+
if kind == 'tasks':
|
|
426
|
+
bugs, stories = [], []
|
|
427
|
+
elif kind == 'bugs':
|
|
428
|
+
tasks, stories = [], []
|
|
429
|
+
elif kind == 'stories':
|
|
430
|
+
tasks, bugs = [], []
|
|
431
|
+
tasks, bugs, stories = _filter_live(tasks, bugs, stories, include_closed)
|
|
432
|
+
# 云路径:数据已平台聚合,归一化即可(进度检测需本地 client,云模式跳过给默认值)
|
|
433
|
+
norm_tasks = [_norm_task(t) for t in tasks]
|
|
434
|
+
norm_bugs = [_norm_bug(b) for b in bugs]
|
|
435
|
+
norm_stories = [_norm_story(s) for s in stories]
|
|
436
|
+
for it in norm_tasks + norm_bugs + norm_stories:
|
|
437
|
+
it['progress'] = {'prd': False, 'spec': 'none', 'code': False}
|
|
438
|
+
data = {'account': cloud.get('account', ''), 'developer': developer,
|
|
439
|
+
'current_task': None, 'tasks': norm_tasks,
|
|
440
|
+
'bugs': norm_bugs, 'stories': norm_stories}
|
|
441
|
+
meta = {'fetched_at': datetime.now().isoformat(timespec='seconds'),
|
|
442
|
+
'source': 'cloud',
|
|
443
|
+
'counts': {'tasks': len(norm_tasks), 'bugs': len(norm_bugs),
|
|
444
|
+
'stories': len(norm_stories)},
|
|
445
|
+
'include_closed': include_closed}
|
|
446
|
+
return {'ok': True, 'data': data, 'meta': meta}, 0
|
|
447
|
+
# 云可达但失败(未配置/登录失败/防火墙)→ 记下云诊断,回退本地。
|
|
448
|
+
# 回退价值:云到禅道常被防火墙挡,但本机(demo 机器,内网)可能直连可达。
|
|
449
|
+
# 本地也失败时,把云诊断一起带出(平台有凭据但云连不上 + 本地无凭据)。
|
|
450
|
+
cloud_error = cloud.get('error', '禅道不可用')
|
|
451
|
+
else:
|
|
452
|
+
cloud_error = None # 云不可达(switch_project 没跑/平台宕机)→ 静默回退本地
|
|
453
|
+
|
|
454
|
+
# ① 凭据(本地回退)
|
|
363
455
|
client = _zentao_creds()
|
|
364
456
|
if client is None:
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
457
|
+
base_msg = ('禅道凭据未配置。推荐在【平台设置 → 禅道】配置(panel 自动走云);'
|
|
458
|
+
'或本地 .qoder/config.yaml 的 zentao.url + secrets/zentao.env 账号密码。')
|
|
459
|
+
if cloud_error:
|
|
460
|
+
base_msg += '(注:平台已配凭据但云端连不上禅道:%s;本机若在内网可配本地凭据直连。)' % cloud_error
|
|
461
|
+
return {'ok': False, 'error': base_msg}, 3
|
|
368
462
|
|
|
369
463
|
ok, msg = client.credentials_ok()
|
|
370
464
|
if not ok:
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/templates/qoder/scripts/domain/integration/__pycache__/return_to_platform.cpython-39.pyc
DELETED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|