@hupan56/wlkj 3.1.32 → 3.3.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 (178) hide show
  1. package/bin/cli.js +117 -0
  2. package/package.json +1 -1
  3. package/templates/qoder/agents/insight-planning.md +67 -67
  4. package/templates/qoder/agents/prd-reference.md +47 -47
  5. package/templates/qoder/commands/optional/wl-insight.md +4 -4
  6. package/templates/qoder/commands/optional/wl-report.md +1 -1
  7. package/templates/qoder/commands/optional/wl-spec.md +23 -3
  8. package/templates/qoder/commands/optional/wl-status.md +12 -1
  9. package/templates/qoder/commands/wl-code.md +138 -7
  10. package/templates/qoder/commands/wl-commit.md +12 -1
  11. package/templates/qoder/commands/wl-design.md +70 -6
  12. package/templates/qoder/commands/wl-init.md +27 -0
  13. package/templates/qoder/commands/wl-prd.md +230 -15
  14. package/templates/qoder/commands/wl-req.md +10 -3
  15. package/templates/qoder/commands/wl-search.md +74 -20
  16. package/templates/qoder/commands/wl-task.md +3 -3
  17. package/templates/qoder/commands/wl-test.md +17 -2
  18. package/templates/qoder/contracts/CHANGELOG.md +418 -0
  19. package/templates/qoder/contracts/README.md +180 -0
  20. package/templates/qoder/contracts/code.md +82 -0
  21. package/templates/qoder/contracts/commit.md +86 -0
  22. package/templates/qoder/contracts/contract-header.md +76 -0
  23. package/templates/qoder/contracts/design.md +106 -0
  24. package/templates/qoder/contracts/fallback.md +126 -0
  25. package/templates/qoder/contracts/isolation.md +119 -0
  26. package/templates/qoder/contracts/prd.md +118 -0
  27. package/templates/qoder/contracts/schemas/design-spec.schema.json +46 -0
  28. package/templates/qoder/contracts/schemas/prd.schema.json +36 -0
  29. package/templates/qoder/contracts/schemas/test-cases.schema.json +40 -0
  30. package/templates/qoder/contracts/spec.md +116 -0
  31. package/templates/qoder/contracts/task.md +125 -0
  32. package/templates/qoder/contracts/test.md +112 -0
  33. package/templates/qoder/hooks/post-tool-use.py +61 -0
  34. package/templates/qoder/hooks/session-start.py +34 -66
  35. package/templates/qoder/hooks/stop-eval.py +47 -0
  36. package/templates/qoder/rules/wl-pipeline.md +37 -0
  37. package/templates/qoder/scripts/capability/__pycache__/__init__.cpython-39.pyc +0 -0
  38. package/templates/qoder/scripts/capability/__pycache__/registry.cpython-39.pyc +0 -0
  39. package/templates/qoder/scripts/capability/__pycache__/registry_mcp.cpython-39.pyc +0 -0
  40. package/templates/qoder/scripts/capability/adapters/__init__.py +1 -1
  41. package/templates/qoder/scripts/capability/adapters/__pycache__/__init__.cpython-39.pyc +0 -0
  42. package/templates/qoder/scripts/capability/adapters/__pycache__/cli.cpython-39.pyc +0 -0
  43. package/templates/qoder/scripts/capability/adapters/__pycache__/mcp.cpython-39.pyc +0 -0
  44. package/templates/qoder/scripts/capability/adapters/__pycache__/qw.cpython-39.pyc +0 -0
  45. package/templates/qoder/scripts/capability/adapters/mcp.py +76 -100
  46. package/templates/qoder/scripts/capability/adapters/qw.py +295 -295
  47. package/templates/qoder/scripts/capability/caps/__init__.py +1 -1
  48. package/templates/qoder/scripts/capability/caps/__pycache__/__init__.cpython-39.pyc +0 -0
  49. package/templates/qoder/scripts/capability/caps/__pycache__/context.cpython-39.pyc +0 -0
  50. package/templates/qoder/scripts/capability/caps/__pycache__/cron.cpython-39.pyc +0 -0
  51. package/templates/qoder/scripts/capability/caps/__pycache__/identity.cpython-39.pyc +0 -0
  52. package/templates/qoder/scripts/capability/caps/__pycache__/memory.cpython-39.pyc +0 -0
  53. package/templates/qoder/scripts/capability/caps/__pycache__/notify.cpython-39.pyc +0 -0
  54. package/templates/qoder/scripts/capability/caps/__pycache__/present.cpython-39.pyc +0 -0
  55. package/templates/qoder/scripts/capability/caps/__pycache__/repo.cpython-39.pyc +0 -0
  56. package/templates/qoder/scripts/capability/caps/__pycache__/sandbox.cpython-39.pyc +0 -0
  57. package/templates/qoder/scripts/capability/caps/memory.py +1 -1
  58. package/templates/qoder/scripts/capability/registry.py +21 -23
  59. package/templates/qoder/scripts/capability/registry_mcp.py +69 -5
  60. package/templates/qoder/scripts/capability/smoke_test_report.json +34 -20
  61. package/templates/qoder/scripts/deployment/setup/carriers.py +3 -1
  62. package/templates/qoder/scripts/deployment/setup/init_doctor.py +10 -3
  63. package/templates/qoder/scripts/deployment/setup/install_qoderwork.py +11 -0
  64. package/templates/qoder/scripts/deployment/setup/wlkj_shim.py +104 -0
  65. package/templates/qoder/scripts/domain/__pycache__/__init__.cpython-39.pyc +0 -0
  66. package/templates/qoder/scripts/domain/deployment/deploy_to_test.py +298 -0
  67. package/templates/qoder/scripts/domain/integration/__init__.py +0 -0
  68. package/templates/qoder/scripts/domain/integration/__pycache__/__init__.cpython-39.pyc +0 -0
  69. package/templates/qoder/scripts/domain/integration/__pycache__/return_to_platform.cpython-39.pyc +0 -0
  70. package/templates/qoder/scripts/domain/integration/return_to_platform.py +392 -0
  71. package/templates/qoder/scripts/domain/integration/spec_upload.py +209 -0
  72. package/templates/qoder/scripts/domain/kg/build/kg_build.py +268 -25
  73. package/templates/qoder/scripts/domain/kg/build/kg_incremental.py +108 -3
  74. package/templates/qoder/scripts/domain/kg/build/kg_signatures.py +169 -0
  75. package/templates/qoder/scripts/domain/kg/extract/asset/__init__.py +10 -0
  76. package/templates/qoder/scripts/domain/kg/extract/asset/asset_tree.py +57 -0
  77. package/templates/qoder/scripts/domain/kg/extract/asset/discussion_importer.py +62 -0
  78. package/templates/qoder/scripts/domain/kg/extract/asset/prd_importer.py +146 -0
  79. package/templates/qoder/scripts/domain/kg/extract/asset/prototype_importer.py +64 -0
  80. package/templates/qoder/scripts/domain/kg/extract/asset/returns_importer.py +52 -0
  81. package/templates/qoder/scripts/domain/kg/extract/build_goal3.py +104 -0
  82. package/templates/qoder/scripts/domain/kg/extract/build_goal4.py +55 -0
  83. package/templates/qoder/scripts/domain/kg/extract/build_goal5.py +95 -0
  84. package/templates/qoder/scripts/domain/kg/extract/db/__init__.py +8 -0
  85. package/templates/qoder/scripts/domain/kg/extract/db/data_profile.py +22 -0
  86. package/templates/qoder/scripts/domain/kg/extract/db/fk_extractor.py +55 -0
  87. package/templates/qoder/scripts/domain/kg/extract/db/schema_extractor.py +90 -0
  88. package/templates/qoder/scripts/domain/kg/extract/extract.py +84 -0
  89. package/templates/qoder/scripts/domain/kg/extract/extract.py.bak +430 -0
  90. package/templates/qoder/scripts/domain/kg/extract/inference/__init__.py +9 -0
  91. package/templates/qoder/scripts/domain/kg/extract/inference/community_summarizer.py +206 -0
  92. package/templates/qoder/scripts/domain/kg/extract/inference/embed_builder.py +132 -0
  93. package/templates/qoder/scripts/domain/kg/extract/inference/naming_matcher.py +80 -0
  94. package/templates/qoder/scripts/domain/kg/extract/inference/promote.py +59 -0
  95. package/templates/qoder/scripts/domain/kg/extract/inference/recompute.py +93 -0
  96. package/templates/qoder/scripts/domain/kg/extract/inference/weak_link.py +421 -0
  97. package/templates/qoder/scripts/domain/kg/extract/java/__init__.py +15 -0
  98. package/templates/qoder/scripts/domain/kg/extract/java/_parser.py +271 -0
  99. package/templates/qoder/scripts/domain/kg/extract/java/all.py +145 -0
  100. package/templates/qoder/scripts/domain/kg/extract/java/build_java_to_pg.py +102 -0
  101. package/templates/qoder/scripts/domain/kg/extract/java/call_chain.py +49 -0
  102. package/templates/qoder/scripts/domain/kg/extract/java/class_extractor.py +141 -0
  103. package/templates/qoder/scripts/domain/kg/extract/java/domain_extractor.py +148 -0
  104. package/templates/qoder/scripts/domain/kg/extract/java/dubbo_extractor.py +33 -0
  105. package/templates/qoder/scripts/domain/kg/extract/java/endpoint_extractor.py +36 -0
  106. package/templates/qoder/scripts/domain/kg/extract/java/javadoc_extractor.py +110 -0
  107. package/templates/qoder/scripts/domain/kg/extract/java/llm_cn_filler.py +150 -0
  108. package/templates/qoder/scripts/domain/kg/extract/java/member_extractor.py +157 -0
  109. package/templates/qoder/scripts/domain/kg/extract/java/mybatisplus_extractor.py +34 -0
  110. package/templates/qoder/scripts/domain/kg/extract/java/pg_upsert.py +165 -0
  111. package/templates/qoder/scripts/domain/kg/extract/java/satoken_extractor.py +30 -0
  112. package/templates/qoder/scripts/domain/kg/extract/java/spring_extractor.py +39 -0
  113. package/templates/qoder/scripts/domain/kg/extract/java/validation_extractor.py +33 -0
  114. package/templates/qoder/scripts/domain/kg/extract/mybatis/__init__.py +9 -0
  115. package/templates/qoder/scripts/domain/kg/extract/mybatis/all.py +79 -0
  116. package/templates/qoder/scripts/domain/kg/extract/mybatis/mapper_parser.py +99 -0
  117. package/templates/qoder/scripts/domain/kg/extract/mybatis/relation_builder.py +69 -0
  118. package/templates/qoder/scripts/domain/kg/extract/mybatis/sql_extractor.py +78 -0
  119. package/templates/qoder/scripts/domain/kg/extract/prd/__init__.py +8 -0
  120. package/templates/qoder/scripts/domain/kg/extract/prd/prd_chunk_embed.py +105 -0
  121. package/templates/qoder/scripts/domain/kg/extract/prd/prd_llm_extract.py +153 -0
  122. package/templates/qoder/scripts/domain/kg/extract/prd/req_anchor.py +120 -0
  123. package/templates/qoder/scripts/domain/kg/extract/ts_extract.py +111 -0
  124. package/templates/qoder/scripts/domain/kg/graph/kg_semantic.py +4 -2
  125. package/templates/qoder/scripts/domain/kg/kg.py +42 -5
  126. package/templates/qoder/scripts/domain/kg/search/_remote.py +187 -0
  127. package/templates/qoder/scripts/domain/kg/search/context_pack.py +32 -2
  128. package/templates/qoder/scripts/domain/kg/search/search_index.py +74 -20
  129. package/templates/qoder/scripts/domain/kg/storage/kg_duckdb.py +43 -0
  130. package/templates/qoder/scripts/domain/kg/switch_project.py +159 -0
  131. package/templates/qoder/scripts/domain/kg/sync_repowiki.py +109 -0
  132. package/templates/qoder/scripts/domain/requirement/req.py +134 -28
  133. package/templates/qoder/scripts/domain/task/__pycache__/wlkj_panel.cpython-39.pyc +0 -0
  134. package/templates/qoder/scripts/domain/task/wlkj_panel.py +1348 -0
  135. package/templates/qoder/scripts/engine/poller.py +219 -0
  136. package/templates/qoder/scripts/foundation/__pycache__/__init__.cpython-39.pyc +0 -0
  137. package/templates/qoder/scripts/foundation/core/__pycache__/__init__.cpython-39.pyc +0 -0
  138. package/templates/qoder/scripts/foundation/core/__pycache__/paths.cpython-39.pyc +0 -0
  139. package/templates/qoder/scripts/foundation/core/paths.py +102 -0
  140. package/templates/qoder/scripts/foundation/integrations/active_task.py +2 -1
  141. package/templates/qoder/scripts/orchestration/wlkj.py +4 -0
  142. package/templates/qoder/scripts/protocol/__pycache__/__init__.cpython-39.pyc +0 -0
  143. package/templates/qoder/scripts/protocol/mcp/zentao_mcp_server.py +24 -11
  144. package/templates/qoder/scripts/protocol/transports/__pycache__/__init__.cpython-39.pyc +0 -0
  145. package/templates/qoder/scripts/protocol/transports/__pycache__/base.cpython-39.pyc +0 -0
  146. package/templates/qoder/scripts/protocol/transports/__pycache__/cli.cpython-39.pyc +0 -0
  147. package/templates/qoder/scripts/protocol/transports/__pycache__/http.cpython-39.pyc +0 -0
  148. package/templates/qoder/scripts/protocol/transports/__pycache__/stdio.cpython-39.pyc +0 -0
  149. package/templates/qoder/scripts/protocol/transports/http.py +7 -1
  150. package/templates/qoder/scripts/validation/eval/qwork_harness.py +1 -1
  151. package/templates/qoder/scripts/validation/eval/report-commands.md +2 -2
  152. package/templates/qoder/settings.json +27 -9
  153. package/templates/qoder/skills/design-import/SKILL.md +3 -3
  154. package/templates/qoder/skills/design-review/SKILL.md +1 -1
  155. package/templates/qoder/skills/prd-generator/SKILL.md +4 -4
  156. package/templates/qoder/skills/prd-review/SKILL.md +1 -1
  157. package/templates/qoder/skills/prototype-generator/SKILL.md +3 -3
  158. package/templates/qoder/skills/spec-coder/SKILL.md +1 -1
  159. package/templates/qoder/skills/spec-generator/SKILL.md +80 -23
  160. package/templates/qoder/skills/test-generator/SKILL.md +1 -1
  161. package/templates/qoder/skills/wl-code/SKILL.md +13 -1
  162. package/templates/qoder/skills/wl-commit/SKILL.md +1 -1
  163. package/templates/qoder/skills/wl-design/SKILL.md +6 -6
  164. package/templates/qoder/skills/wl-init/SKILL.md +2 -2
  165. package/templates/qoder/skills/wl-insight/SKILL.md +5 -5
  166. package/templates/qoder/skills/wl-prd/SKILL.md +60 -0
  167. package/templates/qoder/skills/wl-report/SKILL.md +2 -2
  168. package/templates/qoder/skills/wl-search/SKILL.md +1 -1
  169. package/templates/qoder/skills/wl-spec/SKILL.md +2 -2
  170. package/templates/qoder/skills/wl-status/SKILL.md +2 -2
  171. package/templates/qoder/skills/wl-task/SKILL.md +3 -3
  172. package/templates/qoder/skills/wl-test/SKILL.md +2 -2
  173. package/templates/qoder/templates/spec-template.md +124 -0
  174. package/templates/root/AGENTS.md +41 -14
  175. package/templates/qoder/scripts/domain/task/zentao_panel.py +0 -451
  176. package/templates/qoder/skills/wl-prd-full/SKILL.md +0 -121
  177. package/templates/qoder/skills/wl-prd-quick/SKILL.md +0 -50
  178. package/templates/qoder/skills/wl-prd-review/SKILL.md +0 -47
@@ -0,0 +1,219 @@
1
+ """引擎轮询器:后台轮询平台 pending 触发 → 认领 → 执行 → 回流 → 回填结果。
2
+
3
+ 这是"平台→引擎→平台"真闭环的引擎侧执行器:
4
+ 1. 轮询 GET /trigger/pending(带项目令牌)
5
+ 2. 认领 POST /trigger/{tid}/claim(原子锁)
6
+ 3. 执行:根据 command 调 MCP 取知识 + 生成产出(简化版:rag_search + 模板拼接)
7
+ 4. 回流:create_prd/submit_return
8
+ 5. 回填:PATCH /trigger/{tid}(mcp_tools_called/knowledge_used)
9
+
10
+ 用法:
11
+ python poller.py --config ../mcp_config.json --interval 10
12
+
13
+ 注:完整版需要接 LLM(qwen-plus)生成 PRD/原型。当前是简化版(rag_search + 模板),
14
+ 验证闭环链路。接 LLM 后把 _execute 里的模板拼接换成 cap.mcp.call("ask_corpus",...) 生成。
15
+ """
16
+ import json
17
+ import time
18
+ import urllib.request
19
+ import urllib.error
20
+ import argparse
21
+ import os
22
+ import sys
23
+ from pathlib import Path
24
+
25
+
26
+ def _load_config(config_path: str) -> dict:
27
+ with open(config_path, encoding="utf-8") as f:
28
+ return json.load(f)
29
+
30
+
31
+ def _get_token(cfg: dict) -> str:
32
+ for srv in (cfg.get("mcpServers") or {}).values():
33
+ return srv.get("token", "")
34
+ return ""
35
+
36
+
37
+ def _get_pid(cfg: dict) -> str:
38
+ for srv in (cfg.get("mcpServers") or {}).values():
39
+ env = srv.get("env") or {}
40
+ return env.get("WLKJ_PROJECT_ID", "")
41
+ return ""
42
+
43
+
44
+ def _get_base(cfg: dict) -> str:
45
+ ep = cfg.get("return_endpoint") or {}
46
+ url = ep.get("url", "")
47
+ # 从 return_endpoint.url 提取 base(/api/projects 前面那段)
48
+ if "/api/" in url:
49
+ return url.split("/api/")[0]
50
+ for srv in (cfg.get("mcpServers") or {}).values():
51
+ u = srv.get("url", "")
52
+ if "/mcp" in u:
53
+ return u.split("/mcp")[0]
54
+ return "http://127.0.0.1:10010"
55
+
56
+
57
+ def _api(base, method, path, token, body=None):
58
+ url = base + path
59
+ data = json.dumps(body).encode() if body else None
60
+ req = urllib.request.Request(url, data=data, method=method)
61
+ req.add_header("Content-Type", "application/json")
62
+ if token:
63
+ req.add_header("X-Engine-Token", token)
64
+ try:
65
+ with urllib.request.urlopen(req, timeout=30) as resp:
66
+ return resp.status, json.loads(resp.read().decode())
67
+ except urllib.error.HTTPError as e:
68
+ return e.code, json.loads(e.read().decode() or "{}")
69
+ except Exception as e:
70
+ return 0, {"error": str(e)}
71
+
72
+
73
+ def _mcp_call(base, token, tool, args):
74
+ """通过 MCP JSON-RPC 调工具。"""
75
+ body = {"jsonrpc": "2.0", "id": 1, "method": "tools/call",
76
+ "params": {"name": tool, "arguments": args}}
77
+ data = json.dumps(body).encode()
78
+ req = urllib.request.Request(base + "/mcp", data=data, method="POST")
79
+ req.add_header("Content-Type", "application/json")
80
+ req.add_header("X-MCP-Token", token)
81
+ try:
82
+ with urllib.request.urlopen(req, timeout=60) as resp:
83
+ r = json.loads(resp.read().decode())
84
+ text = r.get("result", {}).get("content", [{}])[0].get("text", "")
85
+ return json.loads(text) if text else {}
86
+ except Exception as e:
87
+ return {"error": str(e)}
88
+
89
+
90
+ def _execute(base, token, pid, command, args):
91
+ """执行触发的工作流(简化版:rag_search 取知识 + 模板拼 PRD + learn 写回)。
92
+ 完整版应接 LLM(ask_corpus)生成自然语言 PRD。"""
93
+ query = (args or {}).get("query", "")
94
+ tools_called = []
95
+ knowledge_used = []
96
+
97
+ # 1) rag_search 取知识
98
+ if query:
99
+ r = _mcp_call(base, token, "rag_search", {"query": query, "top_k": 8})
100
+ tools_called.append("rag_search")
101
+ knowledge_used = [it.get("entity_id", "") for it in r.get("items", [])[:5]]
102
+ knowledge_text = "\n".join(
103
+ "- %s (%s)" % (it.get("text", "")[:60], it.get("entity_id", "")[:30])
104
+ for it in r.get("items", [])[:8]
105
+ )
106
+ # 1b) get_learnings 查之前学到的(反哺)
107
+ lr = _mcp_call(base, token, "get_learnings", {"project_id": pid, "limit": 5})
108
+ tools_called.append("get_learnings")
109
+ learnings_text = "\n".join(
110
+ "- [%s] %s" % (l.get("category",""), (l.get("pattern","")[:60]))
111
+ for l in (lr.get("items") or [])[:5]
112
+ ) or "(暂无学习记录)"
113
+ else:
114
+ knowledge_text = "(无关键词)"
115
+ learnings_text = ""
116
+
117
+ # 2) 根据 command 生成产出
118
+ if "prd" in command.lower():
119
+ prd_md = f"# {query} PRD(引擎自动生成)\n\n## 概述\n基于知识层自动生成的「{query}」需求文档。\n\n## 相关知识\n{knowledge_text}\n\n## 历史学习\n{learnings_text}\n\n## 功能\n(根据上述知识补充)"
120
+ # 回流
121
+ r = _mcp_call(base, token, "create_prd", {
122
+ "project_id": pid, "title": f"{query}PRD(引擎触发)", "content_md": prd_md, "status": "planning"
123
+ })
124
+ tools_called.append("create_prd")
125
+ # 学习写回:把这次的关键认知沉淀
126
+ _mcp_call(base, token, "learn", {"project_id": pid, "category": "rule",
127
+ "pattern": f"{query}: PRD已生成,涉及知识{len(knowledge_used)}条", "source": "wlkj-poller"})
128
+ tools_called.append("learn")
129
+ output = f"生成PRD: {query}PRD(引擎触发) + 学习沉淀"
130
+ elif "design" in command.lower():
131
+ html = f"<html><body><h1>{query}</h1><p>引擎自动生成原型</p></body></html>"
132
+ r = _mcp_call(base, token, "create_prototype", {
133
+ "project_id": pid, "feature": f"{query}(引擎触发)", "platform": "web", "html": html
134
+ })
135
+ tools_called.append("create_prototype")
136
+ output = f"生成原型: {query}(引擎触发)"
137
+ else:
138
+ # learn 写回
139
+ _mcp_call(base, token, "learn", {
140
+ "project_id": pid, "category": "rule", "pattern": f"{query}相关规则(引擎触发)",
141
+ "source": "wlkj-poller"
142
+ })
143
+ tools_called.append("learn")
144
+ output = f"执行命令: {command}"
145
+
146
+ return {"output": output, "tools": tools_called, "knowledge": knowledge_used}
147
+
148
+
149
+ def poll_once(cfg):
150
+ """轮询一次:取 pending → 认领 → 执行 → 回填。"""
151
+ token = _get_token(cfg)
152
+ pid = _get_pid(cfg)
153
+ base = _get_base(cfg)
154
+ if not token or not pid:
155
+ print("[poller] 配置缺 token/project_id,跳过")
156
+ return
157
+
158
+ # 1) 查 pending
159
+ _, pending = _api(base, "GET", f"/api/projects/{pid}/trigger/pending", token)
160
+ items = pending.get("pending", [])
161
+ if not items:
162
+ return # 无待执行
163
+
164
+ for item in items[:1]: # 一次只处理 1 条
165
+ tid = item.get("triggerId")
166
+ cmd = item.get("command", "")
167
+ args = item.get("args", {})
168
+ print(f"[poller] 认领: {tid} {cmd} {args}")
169
+
170
+ # 2) claim
171
+ code, resp = _api(base, "POST", f"/api/projects/{pid}/trigger/workflow/{tid}/claim",
172
+ token, {"engine_id": "poller-001"})
173
+ if code != 200:
174
+ print(f"[poller] 认领失败({code}): {resp}")
175
+ continue
176
+
177
+ # 3) 执行
178
+ t0 = time.time()
179
+ try:
180
+ result = _execute(base, token, pid, cmd, args)
181
+ status = "success"
182
+ except Exception as e:
183
+ result = {"output": str(e), "tools": [], "knowledge": []}
184
+ status = "failed"
185
+
186
+ # 4) patch 回填
187
+ _api(base, "PATCH", f"/api/projects/{pid}/trigger/workflow/{tid}", token, {
188
+ "status": status,
189
+ "output_summary": result["output"],
190
+ "knowledge_used": result["knowledge"],
191
+ "mcp_tools_called": result["tools"],
192
+ "duration_sec": int(time.time() - t0),
193
+ })
194
+ print(f"[poller] 完成: {status} → {result['output']} (tools={result['tools']})")
195
+
196
+
197
+ def main():
198
+ parser = argparse.ArgumentParser(description="wlkj 引擎轮询器")
199
+ parser.add_argument("--config", default=str(Path(__file__).parent.parent.parent / "mcp_config.json"))
200
+ parser.add_argument("--interval", type=int, default=10, help="轮询间隔(秒)")
201
+ parser.add_argument("--once", action="store_true", help="只跑一次")
202
+ args = parser.parse_args()
203
+
204
+ cfg = _load_config(args.config)
205
+ print(f"[poller] 启动: project={_get_pid(cfg)} interval={args.interval}s")
206
+ print(f"[poller] 按 Ctrl+C 停止")
207
+
208
+ while True:
209
+ try:
210
+ poll_once(cfg)
211
+ except Exception as e:
212
+ print(f"[poller] 异常: {e}")
213
+ if args.once:
214
+ break
215
+ time.sleep(args.interval)
216
+
217
+
218
+ if __name__ == "__main__":
219
+ main()
@@ -640,6 +640,108 @@ def get_module_spec_dir(module: Optional[str] = None) -> Path:
640
640
  return spec_dir
641
641
 
642
642
 
643
+ # ── spec 检测 (v3.x wl-code 门禁依据) ──────────────────────────
644
+ # 设计原则: 纯读函数, 不写文件不改状态, 任何调用都安全。
645
+ # spec 只存 2 处: workspace/specs/REQ-{ID}-*.spec.md (全局) + 当前任务目录 spec.md。
646
+ # status 从契约头读 (@contract spec v1 / status: draft|confirmed), 无头视同 draft。
647
+ # ─────────────────────────────────────────────────────────────
648
+
649
+ def get_spec_status(spec_path) -> str:
650
+ """读 spec 文件契约头的 status 字段。
651
+
652
+ 返回: 'draft' | 'confirmed' | None(文件不存在/无法解析)
653
+ - 无 status 字段 或 无契约头 → 视同 'draft' (wl-code 走确认环节, 不直接写码)
654
+ - 文件不存在 → None (wl-code 视为无 spec, 触发生成)
655
+ """
656
+ import re
657
+ try:
658
+ text = open(spec_path, encoding="utf-8").read()
659
+ except (OSError, IOError, TypeError):
660
+ return None
661
+ # 直接正则取 @contract spec 头里的 status (不依赖 contract.py, 避免循环 import)
662
+ m = re.search(r"<!--\s*@contract\s+spec(?:\s+v\d+)?\s*(.*?)-->", text, re.S)
663
+ if not m:
664
+ return "draft" # 无契约头 → 视同 draft (安全: 走确认环节)
665
+ body = m.group(1)
666
+ sm = re.search(r"^\s*status\s*:\s*(\S+)\s*$", body, re.M)
667
+ if not sm:
668
+ return "draft" # 有头但无 status 字段 → draft
669
+ val = sm.group(1).strip().lower()
670
+ return val if val in ("draft", "confirmed") else "draft"
671
+
672
+
673
+ def find_spec(req_id=None, developer_name=None, repo_root=None) -> list:
674
+ """按 REQ-ID 或当前任务找 spec 文件 (wl-code gate 的核心检测)。
675
+
676
+ 查 2 处 (spec 不存时间桶):
677
+ A. workspace/specs/REQ-{ID}-*.spec.md (全局 specs, glob 前缀匹配)
678
+ B. 当前任务目录的 spec.md (无 req_id 时, 读 .current-task)
679
+
680
+ Args:
681
+ req_id: REQ-YYYY-NNN 形式。传了就只查 A; 不传则查 B (当前任务)。
682
+ Returns:
683
+ [{path, status, source-req}, ...] 按优先级 (confirmed 优先)
684
+ status: 'draft' | 'confirmed' (从契约头读, 无头=draft)
685
+ """
686
+ import re
687
+ if repo_root is None:
688
+ repo_root = PROJECT_ROOT
689
+ results = []
690
+
691
+ def _inspect(p):
692
+ """读一个 spec 文件, 返回它的元信息 dict 或 None。"""
693
+ try:
694
+ text = open(p, encoding="utf-8").read()
695
+ except (OSError, IOError):
696
+ return None
697
+ m = re.search(r"<!--\s*@contract\s+spec(?:\s+v\d+)?\s*(.*?)-->", text, re.S)
698
+ body = m.group(1) if m else ""
699
+ status_m = re.search(r"^\s*status\s*:\s*(\S+)\s*$", body, re.M)
700
+ status = status_m.group(1).strip().lower() if status_m else "draft"
701
+ status = status if status in ("draft", "confirmed") else "draft"
702
+ sreq_m = re.search(r"^\s*source-req\s*:\s*(\S+)\s*$", body, re.M)
703
+ source_req = sreq_m.group(1).strip() if sreq_m else None
704
+ return {"path": str(p), "status": status, "source-req": source_req}
705
+
706
+ # A. 时间桶 REQ-ID 容器 (spec-generator 主写入位置, 优先查)
707
+ # 复用 find_req_dirs 跨桶找到容器, 再在容器里找 *.spec.md
708
+ if req_id:
709
+ clean_m = re.match(r"(REQ-\d{4}-\d{3,})", str(req_id))
710
+ clean = clean_m.group(1) if clean_m else str(req_id)
711
+ # A1. 时间桶容器 (members/{dev}/{drafts|outputs}/{桶}/{REQ-ID}/)
712
+ for hit in find_req_dirs(clean, developer_name=developer_name, repo_root=repo_root):
713
+ for f in sorted(Path(hit["path"]).glob("*.spec.md")):
714
+ info = _inspect(f)
715
+ if info:
716
+ results.append(info)
717
+ # A2. 兜底: 全局 .qoder/workspace/specs/ (老 spec 或不在桶里的)
718
+ specs_root = QODER_DIR / "workspace" / "specs"
719
+ if specs_root.is_dir():
720
+ for f in sorted(specs_root.glob("%s-*.spec.md" % clean)):
721
+ info = _inspect(f)
722
+ if info:
723
+ results.append(info)
724
+ for f in sorted(specs_root.glob("*.spec.md")):
725
+ info = _inspect(f)
726
+ if info and info not in results and info.get("source-req") == clean:
727
+ results.append(info)
728
+
729
+ # B. 当前任务目录的 spec.md (无 req_id 时主路径)
730
+ if not results:
731
+ ct = get_current_task()
732
+ if ct:
733
+ # ct 是相对 repo_root 的路径(如 workspace/tasks/xx), 但实际在 .qoder/ 下
734
+ task_spec = QODER_DIR / ct / "spec.md"
735
+ if task_spec.is_file():
736
+ info = _inspect(task_spec)
737
+ if info:
738
+ results.append(info)
739
+
740
+ # 排序: confirmed 优先 (让 wl-code gate 先看到可放行的)
741
+ results.sort(key=lambda x: 0 if x["status"] == "confirmed" else 1)
742
+ return results
743
+
744
+
643
745
  def get_task_dir(slug: str) -> Path:
644
746
  """Get task directory by slug."""
645
747
  from datetime import datetime
@@ -44,7 +44,8 @@ try:
44
44
  )
45
45
  except ImportError:
46
46
  _ENV_SESSION_KEYS = (
47
- ("qoderwork", ("QODER_SESSION_ID",)),
47
+ ("qoder-desktop", ("QODER_DESKTOP", "QODER_IDE")),
48
+ ("qoder-work", ("QODER_SESSION_ID",)),
48
49
  ("codex", ("CODEX_SESSION_ID", "CODEX_THREAD_ID")),
49
50
  ("claude", ("CLAUDE_SESSION_ID", "CLAUDE_CODE_SESSION_ID")),
50
51
  ("cursor", ("CURSOR_SESSION_ID",)),
@@ -57,6 +57,9 @@ DISPATCH = {
57
57
  # ── 需求 / PRD ──
58
58
  'req': 'domain/requirement/req.py',
59
59
  'archive-prd': 'domain/requirement/archive_prd.py',
60
+ # ── 回流平台 (引擎产出 → 平台 AI回流Tab) ──
61
+ # 用法: wlkj.py return prd <路径> <标题> | return prototype <路径> <功能名> [--platform web]
62
+ 'return': 'domain/integration/return_to_platform.py',
60
63
  # ── 设计 ──
61
64
  'fill-prototype': 'domain/design/fill_prototype.py',
62
65
  'design-doc': 'domain/design/gen_design_doc.py',
@@ -118,6 +121,7 @@ def _usage():
118
121
  ' 同步: sync pull|push (team_sync.py)',
119
122
  ' 需求: req <子命令> (req.py, 需求生命周期)',
120
123
  ' archive-prd (PRD 归档)',
124
+ ' 回流平台: return prd|prototype <文件> <标题> [--platform web] (PRD/原型回流到平台 AI回流Tab)',
121
125
  ' 设计: fill-prototype <tpl> (真实数据预填原型)',
122
126
  ' design-doc (生成 DESIGN.md)',
123
127
  ' 评估/学习: eval <prd.md> (eval_prd.py, EVA 评分)',
@@ -190,7 +190,7 @@ class ZentaoMCPServer(BaseMCPServer):
190
190
  # fail-closed: 共享账号下无法判定归属 → 拒绝写, 防改错队友
191
191
  return ('🚫 未识别禅道身份, 无法校验归属, 已拒绝写操作(防改错队友)。\n'
192
192
  ' 修复: /wl-init 配置禅道账号, 或 member.json 加 "zentao_account"。')
193
- r = _api('GET', '/%ss/%s' % (kind, item_id))
193
+ r = _api('GET', '/%s/%s' % ({'task': 'tasks', 'bug': 'bugs', 'story': 'stories'}.get(kind, kind + 's'), item_id))
194
194
  if r.status_code not in (200, 201):
195
195
  return None # 查询失败不阻塞 (禅道权限兜底)
196
196
  data = r.json()
@@ -691,13 +691,23 @@ class ZentaoMCPServer(BaseMCPServer):
691
691
  pid = args.get('product_id', 1)
692
692
  path = '/products/%s/builds' % pid
693
693
  scope = '产品 %s' % pid
694
- return self._safe_get(
695
- path, None,
696
- '%s 的版本 (%%d ):' % scope,
697
- lambda b: ' [%s] %s (date=%s)' % (
698
- b.get('id'), str(b.get('name', ''))[:20], b.get('date', '')),
699
- empty_msg='%s 暂无版本(build)。' % scope,
700
- )
694
+ r = _api('GET', path, None)
695
+ # 禅道部分维度可能不支持 builds 端点(404 not found) → 给明确引导而非裸"查询失败"
696
+ if r.status_code == 404 and not (args.get('project_id') or args.get('execution_id') or args.get('product_id')):
697
+ return ('⚠️ 查版本需要指定维度(禅道无"全量版本"端点)。\n'
698
+ ' 常用: list_builds(project_id=N) 看项目级按周版本\n'
699
+ ' 或: list_builds(product_id=N) / list_builds(execution_id=N)')
700
+ if r.status_code not in (200, 201):
701
+ return '查询失败: %s (该维度可能无版本)' % r.status_code
702
+ data = r.json()
703
+ items = data.get('builds') if isinstance(data, dict) else None
704
+ if not isinstance(items, list) or not items:
705
+ return '%s 暂无版本(build)。' % scope
706
+ lines = ['%s 的版本 (%d 个):' % (scope, len(items))]
707
+ for b in items:
708
+ lines.append(' [%s] %s (date=%s)' % (
709
+ b.get('id'), str(b.get('name', ''))[:20], b.get('date', '')))
710
+ return '\n'.join(lines)
701
711
 
702
712
  @tool("get_build",
703
713
  "查版本/构建详情。例: get_build(build_id=8)",
@@ -1288,7 +1298,9 @@ class ZentaoMCPServer(BaseMCPServer):
1288
1298
  '可能权限不足或字段缺失')
1289
1299
 
1290
1300
  @tool("update_task",
1291
- "修改任务(名称/指派/工时/截止等)。权限不足返回提示不报错。例: update_task(task_id=301, name='导出PDF接口(改)', left=3)",
1301
+ "修改任务(名称/指派/工时/截止/描述等)。权限不足返回提示不报错。"
1302
+ "★spec 回填: 确认后的 spec 摘要可通过 desc 写入任务描述, 让禅道里能看到开发规格要点。"
1303
+ "例: update_task(task_id=301, name='导出PDF接口(改)', left=3, desc='spec摘要...')",
1292
1304
  {"type": "object", "properties": {
1293
1305
  "task_id": {"type": "integer", "description": "任务ID"},
1294
1306
  "name": {"type": "string", "description": "新名称(可选)"},
@@ -1296,6 +1308,7 @@ class ZentaoMCPServer(BaseMCPServer):
1296
1308
  "estimate": {"type": "number", "description": "预计工时(可选)"},
1297
1309
  "left": {"type": "number", "description": "剩余工时(可选)"},
1298
1310
  "deadline": {"type": "string", "description": "截止日期(可选)"},
1311
+ "desc": {"type": "string", "description": "任务描述(可选)。spec 回填用: 写 spec 摘要(接口清单/关键表/异常/AC映射) + 本地 spec 路径"},
1299
1312
  }, "required": ["task_id"]})
1300
1313
  def _t_update_task(self, args):
1301
1314
  tid = args.get('task_id', 0)
@@ -1303,11 +1316,11 @@ class ZentaoMCPServer(BaseMCPServer):
1303
1316
  if guard:
1304
1317
  return guard
1305
1318
  payload = {}
1306
- for k in ('name', 'assignedTo', 'estimate', 'left', 'deadline'):
1319
+ for k in ('name', 'assignedTo', 'estimate', 'left', 'deadline', 'desc'):
1307
1320
  if args.get(k) is not None:
1308
1321
  payload[k] = args[k]
1309
1322
  if not payload:
1310
- return '⚠️ 没有要更新的字段(可选: name/assignedTo/estimate/left/deadline)。'
1323
+ return '⚠️ 没有要更新的字段(可选: name/assignedTo/estimate/left/deadline/desc)。'
1311
1324
  return self._safe_write(
1312
1325
  'PUT', '/tasks/%s' % tid, payload,
1313
1326
  '✅ 任务 #%s 已更新' % tid,
@@ -72,7 +72,11 @@ class RemoteHttpTransport:
72
72
  body["params"] = params
73
73
 
74
74
  data = json.dumps(body, ensure_ascii=False).encode("utf-8")
75
- endpoint = self._url + "/mcp"
75
+ # 修复双重 /mcp:若 url 已含 /mcp 后缀则直接用,否则拼接。
76
+ # 历史问题: registry 配的 url 已是 http://host/mcp, 这里又拼 /mcp → /mcp/mcp → 404。
77
+ # 现在两端都安全: url 配 base 或带 /mcp 都能落到正确的 endpoint。
78
+ low = self._url.lower()
79
+ endpoint = self._url if low.endswith("/mcp") else (self._url + "/mcp")
76
80
 
77
81
  req = urllib.request.Request(
78
82
  endpoint,
@@ -81,6 +85,8 @@ class RemoteHttpTransport:
81
85
  method="POST",
82
86
  )
83
87
  if self._token:
88
+ # 平台 MCP 校验 X-MCP-Token(项目级令牌);同时带 Authorization Bearer 兼容标准 MCP 客户端
89
+ req.add_header("X-MCP-Token", self._token)
84
90
  req.add_header("Authorization", "Bearer %s" % self._token)
85
91
 
86
92
  try:
@@ -15,7 +15,7 @@ qwork_harness.py — QoderWork 命令自动化测试框架 (P2)
15
15
  python qwork_harness.py "/wl-search 考勤"
16
16
  python qwork_harness.py "/wl-search 考勤" --auto-answer=0 # 选第1个
17
17
  python qwork_harness.py "/wl-search 考勤" --no-answer # 只跑不等答
18
- python qwork_harness.py "/wl-prd-full 用户管理" --auto-answer=1
18
+ python qwork_harness.py "/wl-prd 用户管理" --auto-answer=1
19
19
 
20
20
  依赖: websocket-client (pip install websocket-client)
21
21
  """
@@ -143,7 +143,7 @@ AskUserQuestion 在 transcript 里的真实生命周期:
143
143
  python qwork_harness.py "/wl-search 考勤"
144
144
 
145
145
  # 强制选项序号(0-based)
146
- python qwork_harness.py "/wl-prd-full 用户管理" --auto-answer=0
146
+ python qwork_harness.py "/wl-prd 用户管理" --auto-answer=0
147
147
 
148
148
  # 只跑不代答(遇到提问就停)
149
149
  python qwork_harness.py "/wl-search 考勤" --no-answer
@@ -214,7 +214,7 @@ python qwork_harness.py "/wl-search 考勤" --json
214
214
  # 或直接 python qwork_harness.py "/wl-search 考勤" (注意 / 在 bash 下的转义)
215
215
 
216
216
  python qwork_harness.py "/wl-search 考勤"
217
- python qwork_harness.py "/wl-prd-full 用户管理" --auto-answer=0 # 强制选项序号
217
+ python qwork_harness.py "/wl-prd 用户管理" --auto-answer=0 # 强制选项序号
218
218
  python qwork_harness.py "/wl-search 考勤" --no-answer # 不代答
219
219
  python qwork_harness.py "/wl-search 考勤" --json # JSON 输出
220
220
  ```
@@ -1,15 +1,33 @@
1
1
  {
2
2
  "permissions": {
3
+ "defaultMode": "acceptEdits",
3
4
  "allow": [
4
- "Bash(*)",
5
- "Read(*)",
6
- "Write(*)",
7
- "Edit(*)",
8
- "Glob(*)",
9
- "Grep(*)",
10
- "WebFetch(*)",
11
- "WebSearch(*)",
12
- "mcp__*"
5
+ "Read",
6
+ "Write",
7
+ "Edit",
8
+ "Glob",
9
+ "Grep",
10
+ "WebFetch",
11
+ "WebSearch",
12
+ "mcp__*",
13
+ "Bash(python:*)",
14
+ "Bash(python3:*)",
15
+ "Bash(py:*)",
16
+ "Bash(git:*)",
17
+ "Bash(npm:*)",
18
+ "Bash(npx:*)",
19
+ "Bash(node:*)",
20
+ "Bash(ls:*)",
21
+ "Bash(cat:*)",
22
+ "Bash(echo:*)",
23
+ "Bash(test:*)",
24
+ "Bash(mkdir:*)",
25
+ "Bash(cp:*)",
26
+ "Bash(mv:*)",
27
+ "Bash(find:*)",
28
+ "Bash(grep:*)",
29
+ "Bash(sed:*)",
30
+ "Bash(cd:*)"
13
31
  ],
14
32
  "deny": []
15
33
  },
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: design-import
3
- description: "把设计师的 Figma/Axure 设计稿录入工作流,生成设计规范 spec.json 并存入知识图谱。Import designer's Figma/Axure design into the pipeline as a style spec. "
4
- trigger: "user invokes /wl-* command explicitly"
3
+ description: "把设计师的设计稿录入工作流,生成设计规范 spec.json 并存入知识图谱。蓝湖链接直读为主(精确提取颜色/尺寸/字体/切图),无蓝湖可截图口述降级。/wl-design 预览模式走这里。"
4
+ trigger: " /wl-design 预览模式内部调用(实现层)"
5
5
  ---
6
6
 
7
7
 
@@ -48,7 +48,7 @@ ls "$R/data/style/"*-design-spec.json 2>/dev/null # 列所有 spec
48
48
 
49
49
  ## Step 0: 确认平台
50
50
 
51
- 跟 /wl-prd-full 一样,先问:Web 管理端 / APP 移动端 / 两端都要?
51
+ 跟 /wl-prd 一样,先问:Web 管理端 / APP 移动端 / 两端都要?
52
52
  **问了就停,等设计师回答。**
53
53
 
54
54
  ## Step 1: 收集设计信息
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: design-review
3
3
  description: "评审设计交付物的完整性。Review design artifacts for completeness. "
4
- trigger: "user invokes /wl-* command explicitly"
4
+ trigger: " /wl-design 评审模式内部调用(实现层)"
5
5
  ---
6
6
 
7
7
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: prd-generator
3
- description: "生成 PRD + 平台匹配的原型。Generate PRD with platform-aware prototype. "
4
- trigger: "user invokes /wl-* command explicitly"
3
+ description: "/wl-prd 完整档/快速档的实现层。生成 PRD + 平台匹配的原型,带 EVA 质量门禁。"
4
+ trigger: " /wl-prd 完整/快速模式内部调用(实现层,不直接响应自然语言)"
5
5
  ---
6
6
 
7
7
 
@@ -16,8 +16,8 @@ PY=$(python --version >/dev/null 2>&1 && echo python || echo python3)
16
16
 
17
17
  # PRD Generator Skill
18
18
 
19
- > 📌 **主入口:`/wl-prd-full`(正经需求)/ `/wl-prd-quick`(小改动)命令。** 本 skill 是它们的实现层 + Quest/QoderWork 自然语言路由。
20
- > 两者功能等价,优先用 `/wl-prd-full` 或 `/wl-prd-quick`(参数提示更全、更稳定)。
19
+ > 📌 **主入口:`/wl-prd` 命令(完整档 / 快速档 / 评审 三模式)。** 本 skill 是完整/快速模式的实现层 + Quest/QoderWork 自然语言路由。
20
+ > 命令见 `.qoder/commands/wl-prd.md`(参数提示更全、更稳定)。
21
21
  > **模块契约**(输入/输出/校验):`.qoder/contracts/prd.md`
22
22
  > **第一性原理:又快又准**(快=并行+砍模式,准=语义归档+质量锁)
23
23
  > **定位:专注"已确定需求"的落地。** 探索/调研/规划/对标已移交 `/wl-insight`,
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: prd-review
3
3
  description: "评审 PRD 完整性与质量。Review PRD for completeness and quality. "
4
- trigger: "user invokes /wl-* command explicitly"
4
+ trigger: " /wl-prd 评审模式内部调用(实现层)"
5
5
  ---
6
6
 
7
7
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: prototype-generator
3
3
  description: "生成匹配项目 UI 风格的交互式 HTML 原型(Web 端 fywl-ui 用 Vben Admin + Ant Design Vue;APP 端 Carmg-H5 用 Vant 3)。Generate interactive HTML prototypes matching project UI style. "
4
- trigger: "user invokes /wl-* command explicitly"
4
+ trigger: " /wl-prd /wl-design 原型生成内部调用(实现层)"
5
5
  ---
6
6
 
7
7
 
@@ -37,7 +37,7 @@ Generate production-ready HTML prototypes that match the project's actual UI sty
37
37
 
38
38
  本 skill **只在用户明确选择"画原型"后才进入**。
39
39
 
40
- - 如果上游 `/wl-prd-full` 或 `/wl-prd` 在「批量确认」环节已决定**不画**(PRD 第 1 行
40
+ - 如果上游 `/wl-prd` 在「批量确认」环节已决定**不画**(PRD 第 1 行
41
41
  `@contract` 头标记 `prototype: none`),**立即退出本 skill**,不生成任何 HTML。
42
42
  - 如果上游没有 contract 头(独立调用本 skill),**先问用户**:
43
43
  `这个需求要画原型吗?(画 / 不画)`
@@ -50,7 +50,7 @@ Generate production-ready HTML prototypes that match the project's actual UI sty
50
50
  ---
51
51
  ## Step 0: Know the Platform
52
52
 
53
- The platform MUST already be decided (asked by /wl-prd-full Step 0).
53
+ The platform MUST already be decided (asked by /wl-prd Step 0).
54
54
  If you don't know it, STOP and ask the user (Web 管理端 / APP 移动端 / 两端都要).
55
55
 
56
56
  | Platform | Project | Template | Design Tokens |
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: spec-coder
3
3
  description: "严格按开发 Spec 实现代码。Implement code strictly following development Spec. 需用户确认(DANGEROUS)。"
4
- trigger: "user invokes /wl-* command explicitly"
4
+ trigger: " /wl-code 内部调用(实现层)"
5
5
  ---
6
6
 
7
7