@hupan56/wlkj 3.3.12 → 3.3.14
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 +1 -23
- package/package.json +29 -29
- package/templates/qoder/agents/design-agent.md +20 -0
- package/templates/qoder/agents/spec-generator.md +21 -0
- package/templates/qoder/commands/optional/wl-spec.md +2 -12
- package/templates/qoder/commands/optional/wl-status.md +0 -8
- package/templates/qoder/commands/wl-code.md +0 -8
- package/templates/qoder/commands/wl-commit.md +1 -11
- package/templates/qoder/commands/wl-init.md +129 -129
- package/templates/qoder/commands/wl-prd.md +0 -24
- package/templates/qoder/commands/wl-search.md +11 -26
- package/templates/qoder/commands/wl-task.md +1 -11
- package/templates/qoder/commands/wl-test.md +0 -8
- package/templates/qoder/config.yaml +8 -3
- package/templates/qoder/hooks/session-start.py +365 -384
- package/templates/qoder/hooks/stop-eval.py +254 -383
- package/templates/qoder/scripts/capability/adapters/mcp.py +314 -322
- package/templates/qoder/scripts/capability/registry.py +248 -245
- package/templates/qoder/scripts/capability/registry_mcp.py +4 -10
- package/templates/qoder/scripts/capability/smoke_test_report.json.new +94 -0
- package/templates/qoder/scripts/deployment/setup/init_doctor.py +2 -6
- package/templates/qoder/scripts/deployment/setup/install_qoderwork.py +14 -0
- package/templates/qoder/scripts/deployment/setup/setup.py +2 -16
- package/templates/qoder/scripts/domain/integration/return_to_platform.py +2 -4
- package/templates/qoder/scripts/domain/integration/spec_upload.py +4 -4
- package/templates/qoder/scripts/domain/kg/build/build_entity_registry.py +12 -12
- package/templates/qoder/scripts/domain/kg/build/build_relations.py +12 -12
- package/templates/qoder/scripts/domain/kg/extract/asset/__init__.py +10 -0
- package/templates/qoder/scripts/domain/kg/extract/asset/asset_tree.py +57 -0
- package/templates/qoder/scripts/domain/kg/extract/asset/discussion_importer.py +62 -0
- package/templates/qoder/scripts/domain/kg/extract/asset/prd_importer.py +146 -0
- package/templates/qoder/scripts/domain/kg/extract/asset/prototype_importer.py +64 -0
- package/templates/qoder/scripts/domain/kg/extract/asset/returns_importer.py +52 -0
- package/templates/qoder/scripts/domain/kg/extract/build_goal3.py +104 -0
- package/templates/qoder/scripts/domain/kg/extract/build_goal4.py +55 -0
- package/templates/qoder/scripts/domain/kg/extract/build_goal5.py +95 -0
- package/templates/qoder/scripts/domain/kg/extract/db/__init__.py +8 -0
- package/templates/qoder/scripts/domain/kg/extract/db/data_profile.py +22 -0
- package/templates/qoder/scripts/domain/kg/extract/db/fk_extractor.py +55 -0
- package/templates/qoder/scripts/domain/kg/extract/db/schema_extractor.py +90 -0
- package/templates/qoder/scripts/domain/kg/extract/inference/__init__.py +9 -0
- package/templates/qoder/scripts/domain/kg/extract/inference/community_summarizer.py +206 -0
- package/templates/qoder/scripts/domain/kg/extract/inference/embed_builder.py +132 -0
- package/templates/qoder/scripts/domain/kg/extract/inference/naming_matcher.py +80 -0
- package/templates/qoder/scripts/domain/kg/extract/inference/promote.py +59 -0
- package/templates/qoder/scripts/domain/kg/extract/inference/recompute.py +93 -0
- package/templates/qoder/scripts/domain/kg/extract/inference/weak_link.py +421 -0
- package/templates/qoder/scripts/domain/kg/extract/mybatis/__init__.py +9 -0
- package/templates/qoder/scripts/domain/kg/extract/mybatis/all.py +79 -0
- package/templates/qoder/scripts/domain/kg/extract/mybatis/mapper_parser.py +99 -0
- package/templates/qoder/scripts/domain/kg/extract/mybatis/relation_builder.py +69 -0
- package/templates/qoder/scripts/domain/kg/extract/mybatis/sql_extractor.py +78 -0
- package/templates/qoder/scripts/domain/kg/extract/prd/__init__.py +8 -0
- package/templates/qoder/scripts/domain/kg/extract/prd/prd_chunk_embed.py +105 -0
- package/templates/qoder/scripts/domain/kg/extract/prd/prd_llm_extract.py +153 -0
- package/templates/qoder/scripts/domain/kg/extract/prd/req_anchor.py +120 -0
- package/templates/qoder/scripts/domain/kg/graph/kg_link_db.py +235 -0
- package/templates/qoder/scripts/domain/kg/graph/kg_semantic.py +5 -41
- package/templates/qoder/scripts/domain/kg/kg.py +0 -14
- package/templates/qoder/scripts/domain/kg/search/enrich_prompt.py +238 -0
- package/templates/qoder/scripts/domain/kg/server/perf_bench.py +197 -0
- package/templates/qoder/scripts/domain/kg/switch_project.py +2 -2
- package/templates/qoder/scripts/domain/kg/sync_repowiki.py +109 -0
- package/templates/qoder/scripts/domain/task/wlkj_panel.py +1348 -1503
- package/templates/qoder/scripts/engine/poller.py +219 -0
- package/templates/qoder/scripts/foundation/bootstrap.py +145 -145
- package/templates/qoder/scripts/orchestration/wlkj.py +189 -306
- package/templates/qoder/settings.json +0 -8
- package/templates/qoder/skills/wl-spec/SKILL.md +1 -1
- package/templates/qoder/commands/wl-data.md +0 -46
- package/templates/qoder/commands/wl-fix.md +0 -47
- package/templates/qoder/commands/wl-knowledge.md +0 -41
- package/templates/qoder/commands/wl-review.md +0 -49
- package/templates/qoder/contracts/insight.md +0 -55
- package/templates/qoder/hooks/pre-tool-use-commit.py +0 -124
- package/templates/qoder/scripts/foundation/io/context_cache.py +0 -94
- package/templates/qoder/scripts/tool_guide.md +0 -70
- package/templates/qoder/scripts/validation/eval/alignment_matrix.py +0 -176
- package/templates/qoder/scripts/validation/eval/bf2_content_fidelity.py +0 -110
- package/templates/qoder/scripts/validation/eval/bf2_llmjudge.py +0 -104
- package/templates/qoder/scripts/validation/eval/bf_score.py +0 -218
- package/templates/qoder/scripts/validation/eval/code_flywheel.py +0 -150
- package/templates/qoder/scripts/validation/eval/dispatcher_ab.py +0 -156
- package/templates/qoder/scripts/validation/eval/dispatcher_ab_2026-07-21.json +0 -23
- package/templates/qoder/scripts/validation/eval/feature_fidelity_flywheel.py +0 -143
- package/templates/qoder/scripts/validation/eval/gradient_matrix.py +0 -261
- package/templates/qoder/scripts/validation/eval/gradient_matrix_baseline_2026-07-21.json +0 -33
- package/templates/qoder/scripts/validation/eval/metrics_dashboard.py +0 -105
- package/templates/qoder/scripts/validation/eval/multi_turn_flywheel.py +0 -118
- package/templates/qoder/scripts/validation/eval/prd_fidelity_flywheel.py +0 -128
- package/templates/qoder/scripts/validation/eval/prd_flywheel.py +0 -148
- package/templates/qoder/scripts/validation/eval/prototype_fidelity_flywheel.py +0 -166
- package/templates/qoder/scripts/validation/eval/recall_flywheel.py +0 -148
- package/templates/qoder/scripts/validation/eval/robustness_flywheel.py +0 -139
- package/templates/qoder/scripts/validation/eval/speed_accuracy_flywheel.py +0 -188
- package/templates/qoder/scripts/validation/eval/task_flywheel.py +0 -124
- package/templates/qoder/scripts/validation/eval/token_flywheel.py +0 -88
- package/templates/qoder/scripts/validation/metrics/eval_code_ac.py +0 -177
- package/templates/qoder/scripts/validation/metrics/lint_cases.py +0 -170
- package/templates/qoder/scripts/validation/test/test_context_cache.py +0 -78
- package/templates/qoder/scripts/validation/test/test_lint_cases.py +0 -60
- package/templates/qoder/scripts/validation/test/test_pre_tool_use_commit.py +0 -70
- package/templates/workspace/specs/REQ-TEST-CTX/.context-cache.json +0 -1
|
@@ -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()
|
|
@@ -1,145 +1,145 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
"""
|
|
3
|
-
bootstrap.py - 统一 CLI 启动样板 (DRY) + 子目录化路径自举 (v3.0)
|
|
4
|
-
|
|
5
|
-
治"70处 sys.path + 50处 reconfigure" + 子目录化后的路径定位。
|
|
6
|
-
|
|
7
|
-
子目录化后, 脚本在 scripts/domain/kg/kg.py, 它的 dirname(__file__)=scripts/domain/kg/,
|
|
8
|
-
直接 sys.path.insert(scripts/domain/kg/) 后 `from foundation.x` 会断 (foundation 在 scripts/foundation/)。
|
|
9
|
-
|
|
10
|
-
本模块的 setup() 解决: 无论脚本在 scripts/ 根还是 scripts/某子包/,
|
|
11
|
-
都能正确定位 scripts/ 根并注入, 让 `from foundation.x` 和跨包 import 都能用。
|
|
12
|
-
|
|
13
|
-
核心: _find_scripts_root() 向上找含 'foundation/' 子目录的那个目录 = scripts/ 根。
|
|
14
|
-
|
|
15
|
-
Usage (两种姿势):
|
|
16
|
-
# 姿势1: 子包脚本开头 (推荐)
|
|
17
|
-
import os, sys
|
|
18
|
-
_S = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # 子包→scripts
|
|
19
|
-
if _S not in sys.path: sys.path.insert(0, _S)
|
|
20
|
-
from bootstrap import setup; setup()
|
|
21
|
-
|
|
22
|
-
# 姿势2: scripts/ 根脚本 (老姿势仍可用)
|
|
23
|
-
from bootstrap import setup; setup()
|
|
24
|
-
"""
|
|
25
|
-
|
|
26
|
-
import os
|
|
27
|
-
import sys
|
|
28
|
-
|
|
29
|
-
__all__ = ["setup", "SCRIPTS_DIR", "REPO_ROOT", "find_scripts_root", "ensure_scripts_path"]
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def find_scripts_root(start=None):
|
|
33
|
-
"""定位 .qoder/scripts/ 根目录。
|
|
34
|
-
|
|
35
|
-
策略: 从 start (默认 __file__ 的目录) 向上找, 直到某目录含 'common/' 子目录
|
|
36
|
-
且该 common/ 下有 bootstrap.py (即本文件) → 那就是 scripts/ 根。
|
|
37
|
-
兜底: 找含 '.qoder' 的目录 + '/scripts'。
|
|
38
|
-
"""
|
|
39
|
-
start = start or os.path.dirname(os.path.abspath(__file__))
|
|
40
|
-
cur = start
|
|
41
|
-
for _ in range(10):
|
|
42
|
-
# scripts/ 根的标志: 含 common/bootstrap.py
|
|
43
|
-
if os.path.isfile(os.path.join(cur, "foundation", "bootstrap.py")):
|
|
44
|
-
return cur
|
|
45
|
-
parent = os.path.dirname(cur)
|
|
46
|
-
if parent == cur:
|
|
47
|
-
break
|
|
48
|
-
cur = parent
|
|
49
|
-
# 兜底: cwd 向上找 .qoder/scripts
|
|
50
|
-
cwd = os.getcwd()
|
|
51
|
-
for _ in range(10):
|
|
52
|
-
cand = os.path.join(cwd, ".qoder", "scripts")
|
|
53
|
-
if os.path.isdir(cand):
|
|
54
|
-
return cand
|
|
55
|
-
parent = os.path.dirname(cwd)
|
|
56
|
-
if parent == cwd:
|
|
57
|
-
break
|
|
58
|
-
cwd = parent
|
|
59
|
-
return start
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def ensure_scripts_path(start=None):
|
|
63
|
-
"""确保 scripts/ 根在 sys.path 最前。幂等。
|
|
64
|
-
|
|
65
|
-
子包脚本调用此函数后, `from foundation.x` 即可用。
|
|
66
|
-
"""
|
|
67
|
-
root = find_scripts_root(start)
|
|
68
|
-
if root not in sys.path:
|
|
69
|
-
sys.path.insert(0, root)
|
|
70
|
-
return root
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def _find_scripts_dir() -> str:
|
|
74
|
-
"""[内部] 定位 scripts/ 根 (兼容旧名)。"""
|
|
75
|
-
return find_scripts_root()
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
def ensure_utf8_stdio():
|
|
79
|
-
"""UTF-8 stdio 配置 (v3.0: 从 utf8.py 并入)。
|
|
80
|
-
|
|
81
|
-
把 stdout/stderr 切到 UTF-8, 失败则忽略 (不崩溃)。
|
|
82
|
-
幂等: 多次调用无害。
|
|
83
|
-
"""
|
|
84
|
-
for stream_name in ("stdout", "stderr"):
|
|
85
|
-
stream = getattr(sys, stream_name, None)
|
|
86
|
-
if stream is None:
|
|
87
|
-
continue
|
|
88
|
-
reconfigure = getattr(stream, "reconfigure", None)
|
|
89
|
-
if reconfigure is None:
|
|
90
|
-
continue
|
|
91
|
-
try:
|
|
92
|
-
reconfigure(encoding="utf-8", errors="replace")
|
|
93
|
-
except (TypeError, ValueError, OSError, IOError):
|
|
94
|
-
try:
|
|
95
|
-
reconfigure(encoding="utf-8")
|
|
96
|
-
except Exception:
|
|
97
|
-
pass
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
def setup():
|
|
101
|
-
"""统一 CLI 启动初始化。幂等。
|
|
102
|
-
|
|
103
|
-
做: ① ensure_utf8_stdio ② scripts/ 根注入 sys.path ③ scripts/common/ 注入
|
|
104
|
-
Returns: (SCRIPTS_DIR, REPO_ROOT)
|
|
105
|
-
"""
|
|
106
|
-
# 先确保 scripts/ 根在 path (本文件可能在 common/ 或被引用)
|
|
107
|
-
scripts_dir = ensure_scripts_root = find_scripts_root()
|
|
108
|
-
if scripts_dir not in sys.path:
|
|
109
|
-
sys.path.insert(0, scripts_dir)
|
|
110
|
-
common_dir = os.path.join(scripts_dir, "foundation")
|
|
111
|
-
if os.path.isdir(common_dir) and common_dir not in sys.path:
|
|
112
|
-
sys.path.insert(0, common_dir)
|
|
113
|
-
# 注入所有子包目录 (kg/build/mcp/test/setup/carriers/task/search/report)
|
|
114
|
-
# 让跨包 import (旧式不带前缀, 如 import search_index) 仍可用
|
|
115
|
-
try:
|
|
116
|
-
for _name in os.listdir(scripts_dir):
|
|
117
|
-
_subdir = os.path.join(scripts_dir, _name)
|
|
118
|
-
if os.path.isdir(_subdir) and _subdir not in sys.path:
|
|
119
|
-
sys.path.insert(0, _subdir)
|
|
120
|
-
except OSError:
|
|
121
|
-
pass
|
|
122
|
-
|
|
123
|
-
# 1. UTF-8 stdio (v3.0: utf8.py 已并入 bootstrap, 内联实现)
|
|
124
|
-
try:
|
|
125
|
-
ensure_utf8_stdio()
|
|
126
|
-
except Exception:
|
|
127
|
-
for sname in ("stdout", "stderr"):
|
|
128
|
-
s = getattr(sys, sname, None)
|
|
129
|
-
rc = getattr(s, "reconfigure", None) if s else None
|
|
130
|
-
if rc:
|
|
131
|
-
try:
|
|
132
|
-
rc(encoding="utf-8", errors="replace")
|
|
133
|
-
except Exception:
|
|
134
|
-
try:
|
|
135
|
-
rc(encoding="utf-8")
|
|
136
|
-
except Exception:
|
|
137
|
-
pass
|
|
138
|
-
|
|
139
|
-
repo_root = os.path.dirname(os.path.dirname(scripts_dir))
|
|
140
|
-
return scripts_dir, repo_root
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
# 模块级常量
|
|
144
|
-
SCRIPTS_DIR = find_scripts_root()
|
|
145
|
-
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(SCRIPTS_DIR))))
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
bootstrap.py - 统一 CLI 启动样板 (DRY) + 子目录化路径自举 (v3.0)
|
|
4
|
+
|
|
5
|
+
治"70处 sys.path + 50处 reconfigure" + 子目录化后的路径定位。
|
|
6
|
+
|
|
7
|
+
子目录化后, 脚本在 scripts/domain/kg/kg.py, 它的 dirname(__file__)=scripts/domain/kg/,
|
|
8
|
+
直接 sys.path.insert(scripts/domain/kg/) 后 `from foundation.x` 会断 (foundation 在 scripts/foundation/)。
|
|
9
|
+
|
|
10
|
+
本模块的 setup() 解决: 无论脚本在 scripts/ 根还是 scripts/某子包/,
|
|
11
|
+
都能正确定位 scripts/ 根并注入, 让 `from foundation.x` 和跨包 import 都能用。
|
|
12
|
+
|
|
13
|
+
核心: _find_scripts_root() 向上找含 'foundation/' 子目录的那个目录 = scripts/ 根。
|
|
14
|
+
|
|
15
|
+
Usage (两种姿势):
|
|
16
|
+
# 姿势1: 子包脚本开头 (推荐)
|
|
17
|
+
import os, sys
|
|
18
|
+
_S = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # 子包→scripts
|
|
19
|
+
if _S not in sys.path: sys.path.insert(0, _S)
|
|
20
|
+
from bootstrap import setup; setup()
|
|
21
|
+
|
|
22
|
+
# 姿势2: scripts/ 根脚本 (老姿势仍可用)
|
|
23
|
+
from bootstrap import setup; setup()
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
import os
|
|
27
|
+
import sys
|
|
28
|
+
|
|
29
|
+
__all__ = ["setup", "SCRIPTS_DIR", "REPO_ROOT", "find_scripts_root", "ensure_scripts_path"]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def find_scripts_root(start=None):
|
|
33
|
+
"""定位 .qoder/scripts/ 根目录。
|
|
34
|
+
|
|
35
|
+
策略: 从 start (默认 __file__ 的目录) 向上找, 直到某目录含 'common/' 子目录
|
|
36
|
+
且该 common/ 下有 bootstrap.py (即本文件) → 那就是 scripts/ 根。
|
|
37
|
+
兜底: 找含 '.qoder' 的目录 + '/scripts'。
|
|
38
|
+
"""
|
|
39
|
+
start = start or os.path.dirname(os.path.abspath(__file__))
|
|
40
|
+
cur = start
|
|
41
|
+
for _ in range(10):
|
|
42
|
+
# scripts/ 根的标志: 含 common/bootstrap.py
|
|
43
|
+
if os.path.isfile(os.path.join(cur, "foundation", "bootstrap.py")):
|
|
44
|
+
return cur
|
|
45
|
+
parent = os.path.dirname(cur)
|
|
46
|
+
if parent == cur:
|
|
47
|
+
break
|
|
48
|
+
cur = parent
|
|
49
|
+
# 兜底: cwd 向上找 .qoder/scripts
|
|
50
|
+
cwd = os.getcwd()
|
|
51
|
+
for _ in range(10):
|
|
52
|
+
cand = os.path.join(cwd, ".qoder", "scripts")
|
|
53
|
+
if os.path.isdir(cand):
|
|
54
|
+
return cand
|
|
55
|
+
parent = os.path.dirname(cwd)
|
|
56
|
+
if parent == cwd:
|
|
57
|
+
break
|
|
58
|
+
cwd = parent
|
|
59
|
+
return start
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def ensure_scripts_path(start=None):
|
|
63
|
+
"""确保 scripts/ 根在 sys.path 最前。幂等。
|
|
64
|
+
|
|
65
|
+
子包脚本调用此函数后, `from foundation.x` 即可用。
|
|
66
|
+
"""
|
|
67
|
+
root = find_scripts_root(start)
|
|
68
|
+
if root not in sys.path:
|
|
69
|
+
sys.path.insert(0, root)
|
|
70
|
+
return root
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _find_scripts_dir() -> str:
|
|
74
|
+
"""[内部] 定位 scripts/ 根 (兼容旧名)。"""
|
|
75
|
+
return find_scripts_root()
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def ensure_utf8_stdio():
|
|
79
|
+
"""UTF-8 stdio 配置 (v3.0: 从 utf8.py 并入)。
|
|
80
|
+
|
|
81
|
+
把 stdout/stderr 切到 UTF-8, 失败则忽略 (不崩溃)。
|
|
82
|
+
幂等: 多次调用无害。
|
|
83
|
+
"""
|
|
84
|
+
for stream_name in ("stdout", "stderr"):
|
|
85
|
+
stream = getattr(sys, stream_name, None)
|
|
86
|
+
if stream is None:
|
|
87
|
+
continue
|
|
88
|
+
reconfigure = getattr(stream, "reconfigure", None)
|
|
89
|
+
if reconfigure is None:
|
|
90
|
+
continue
|
|
91
|
+
try:
|
|
92
|
+
reconfigure(encoding="utf-8", errors="replace")
|
|
93
|
+
except (TypeError, ValueError, OSError, IOError):
|
|
94
|
+
try:
|
|
95
|
+
reconfigure(encoding="utf-8")
|
|
96
|
+
except Exception:
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def setup():
|
|
101
|
+
"""统一 CLI 启动初始化。幂等。
|
|
102
|
+
|
|
103
|
+
做: ① ensure_utf8_stdio ② scripts/ 根注入 sys.path ③ scripts/common/ 注入
|
|
104
|
+
Returns: (SCRIPTS_DIR, REPO_ROOT)
|
|
105
|
+
"""
|
|
106
|
+
# 先确保 scripts/ 根在 path (本文件可能在 common/ 或被引用)
|
|
107
|
+
scripts_dir = ensure_scripts_root = find_scripts_root()
|
|
108
|
+
if scripts_dir not in sys.path:
|
|
109
|
+
sys.path.insert(0, scripts_dir)
|
|
110
|
+
common_dir = os.path.join(scripts_dir, "foundation")
|
|
111
|
+
if os.path.isdir(common_dir) and common_dir not in sys.path:
|
|
112
|
+
sys.path.insert(0, common_dir)
|
|
113
|
+
# 注入所有子包目录 (kg/build/mcp/test/setup/carriers/task/search/report)
|
|
114
|
+
# 让跨包 import (旧式不带前缀, 如 import search_index) 仍可用
|
|
115
|
+
try:
|
|
116
|
+
for _name in os.listdir(scripts_dir):
|
|
117
|
+
_subdir = os.path.join(scripts_dir, _name)
|
|
118
|
+
if os.path.isdir(_subdir) and _subdir not in sys.path:
|
|
119
|
+
sys.path.insert(0, _subdir)
|
|
120
|
+
except OSError:
|
|
121
|
+
pass
|
|
122
|
+
|
|
123
|
+
# 1. UTF-8 stdio (v3.0: utf8.py 已并入 bootstrap, 内联实现)
|
|
124
|
+
try:
|
|
125
|
+
ensure_utf8_stdio()
|
|
126
|
+
except Exception:
|
|
127
|
+
for sname in ("stdout", "stderr"):
|
|
128
|
+
s = getattr(sys, sname, None)
|
|
129
|
+
rc = getattr(s, "reconfigure", None) if s else None
|
|
130
|
+
if rc:
|
|
131
|
+
try:
|
|
132
|
+
rc(encoding="utf-8", errors="replace")
|
|
133
|
+
except Exception:
|
|
134
|
+
try:
|
|
135
|
+
rc(encoding="utf-8")
|
|
136
|
+
except Exception:
|
|
137
|
+
pass
|
|
138
|
+
|
|
139
|
+
repo_root = os.path.dirname(os.path.dirname(scripts_dir))
|
|
140
|
+
return scripts_dir, repo_root
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# 模块级常量
|
|
144
|
+
SCRIPTS_DIR = find_scripts_root()
|
|
145
|
+
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(SCRIPTS_DIR))))
|