@hupan56/wlkj 2.7.12 → 3.1.2
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 +1435 -1107
- package/package.json +29 -29
- package/templates/qoder/agents/insight-planning.md +1 -1
- package/templates/qoder/agents/insight-research.md +28 -15
- package/templates/qoder/commands/optional/wl-insight.md +161 -161
- package/templates/qoder/commands/optional/wl-report.md +20 -39
- package/templates/qoder/commands/optional/wl-status.md +4 -4
- package/templates/qoder/commands/wl-code.md +2 -2
- package/templates/qoder/commands/wl-design.md +6 -6
- package/templates/qoder/commands/wl-init.md +3 -3
- package/templates/qoder/commands/wl-prd.md +26 -13
- package/templates/qoder/commands/wl-req.md +43 -0
- package/templates/qoder/commands/wl-search.md +78 -47
- package/templates/qoder/commands/wl-task.md +343 -40
- package/templates/qoder/commands/wl-test.md +43 -17
- package/templates/qoder/config.yaml +52 -14
- package/templates/qoder/hooks/post-tool-use.py +181 -0
- package/templates/qoder/hooks/session-start.py +94 -23
- package/templates/qoder/hooks/stop-eval.py +207 -0
- package/templates/qoder/hooks/user-prompt-submit.py +139 -0
- package/templates/qoder/rules/wl-pipeline.md +85 -49
- package/templates/qoder/scripts/README.md +102 -0
- package/templates/qoder/scripts/capability/__init__.py +26 -0
- package/templates/qoder/scripts/capability/__main__.py +72 -0
- package/templates/qoder/scripts/capability/adapters/__init__.py +29 -0
- package/templates/qoder/scripts/capability/adapters/cli.py +316 -0
- package/templates/qoder/scripts/capability/adapters/mcp.py +334 -0
- package/templates/qoder/scripts/capability/adapters/qw.py +271 -0
- package/templates/qoder/scripts/capability/caps/__init__.py +27 -0
- package/templates/qoder/scripts/capability/caps/context.py +36 -0
- package/templates/qoder/scripts/capability/caps/cron.py +50 -0
- package/templates/qoder/scripts/capability/caps/identity.py +43 -0
- package/templates/qoder/scripts/capability/caps/memory.py +71 -0
- package/templates/qoder/scripts/capability/caps/notify.py +41 -0
- package/templates/qoder/scripts/capability/caps/present.py +48 -0
- package/templates/qoder/scripts/capability/caps/repo.py +31 -0
- package/templates/qoder/scripts/capability/caps/sandbox.py +43 -0
- package/templates/qoder/scripts/capability/chain.py +92 -0
- package/templates/qoder/scripts/capability/memory_chain.py +41 -0
- package/templates/qoder/scripts/capability/registry.py +246 -0
- package/templates/qoder/scripts/capability/registry_mcp.py +250 -0
- package/templates/qoder/scripts/capability/smoke_test.py +211 -0
- package/templates/qoder/scripts/capability/smoke_test_report.json +94 -0
- package/templates/qoder/scripts/deployment/setup/__init__.py +11 -0
- package/templates/qoder/scripts/deployment/setup/carriers.py +669 -0
- package/templates/qoder/scripts/deployment/setup/carriers_verify.py +148 -0
- package/templates/qoder/scripts/{init_doctor.py → deployment/setup/init_doctor.py} +100 -39
- package/templates/qoder/scripts/{install_qoderwork.py → deployment/setup/install_qoderwork.py} +48 -30
- package/templates/qoder/scripts/{platform_doctor.py → deployment/setup/platform_doctor.py} +271 -259
- package/templates/qoder/scripts/{repo_root.py → deployment/setup/repo_root.py} +9 -2
- package/templates/qoder/scripts/{setup.py → deployment/setup/setup.py} +143 -14
- package/templates/qoder/scripts/{setup_lanhu.py → deployment/setup/setup_lanhu.py} +979 -963
- package/templates/qoder/scripts/domain/design/__init__.py +0 -0
- package/templates/qoder/scripts/{fill_prototype.py → domain/design/fill_prototype.py} +17 -5
- package/templates/qoder/scripts/{gen_design_doc.py → domain/design/gen_design_doc.py} +406 -394
- package/templates/qoder/scripts/domain/kg/__init__.py +11 -0
- package/templates/qoder/scripts/domain/kg/build/__init__.py +0 -0
- package/templates/qoder/scripts/domain/kg/build/build_entity_registry.py +201 -0
- package/templates/qoder/scripts/domain/kg/build/build_relations.py +132 -0
- package/templates/qoder/scripts/{build_style_index.py → domain/kg/build/build_style_index.py} +50 -15
- package/templates/qoder/scripts/domain/kg/build/build_workflows.py +149 -0
- package/templates/qoder/scripts/{kg_build.py → domain/kg/build/kg_build.py} +685 -612
- package/templates/qoder/scripts/{kg_build_db.py → domain/kg/build/kg_build_db.py} +338 -327
- package/templates/qoder/scripts/{kg_incremental.py → domain/kg/build/kg_incremental.py} +425 -393
- package/templates/qoder/scripts/{learn_aggregate.py → domain/kg/build/learn_aggregate.py} +212 -201
- package/templates/qoder/scripts/domain/kg/extract/__init__.py +0 -0
- package/templates/qoder/scripts/{common → domain/kg/extract}/extract.py +430 -419
- package/templates/qoder/scripts/domain/kg/extract/test_extract.py +115 -0
- package/templates/qoder/scripts/{common → domain/kg/extract}/ts_extract.py +614 -536
- package/templates/qoder/scripts/domain/kg/graph/__init__.py +0 -0
- package/templates/qoder/scripts/{kg_link_db.py → domain/kg/graph/kg_link_db.py} +235 -224
- package/templates/qoder/scripts/domain/kg/graph/kg_semantic.py +683 -0
- package/templates/qoder/scripts/{kg.py → domain/kg/kg.py} +871 -708
- package/templates/qoder/scripts/domain/kg/kg_capabilities.py +182 -0
- package/templates/qoder/scripts/domain/kg/search/__init__.py +0 -0
- package/templates/qoder/scripts/{context_pack.py → domain/kg/search/context_pack.py} +45 -17
- package/templates/qoder/scripts/{enrich_prompt.py → domain/kg/search/enrich_prompt.py} +238 -226
- package/templates/qoder/scripts/domain/kg/search/prefetch.py +384 -0
- package/templates/qoder/scripts/{common → domain/kg/search}/search_engine.py +207 -205
- package/templates/qoder/scripts/{search_index.py → domain/kg/search/search_index.py} +210 -88
- package/templates/qoder/scripts/domain/kg/server/__init__.py +0 -0
- package/templates/qoder/scripts/domain/kg/server/kgd.py +549 -0
- package/templates/qoder/scripts/domain/kg/server/perf_bench.py +197 -0
- package/templates/qoder/scripts/domain/kg/storage/__init__.py +0 -0
- package/templates/qoder/scripts/{kg_duckdb.py → domain/kg/storage/kg_duckdb.py} +70 -45
- package/templates/qoder/scripts/domain/learning/__init__.py +0 -0
- package/templates/qoder/scripts/{learn.py → domain/learning/learn.py} +157 -146
- package/templates/qoder/scripts/domain/report/__init__.py +11 -0
- package/templates/qoder/scripts/{add_session.py → domain/report/add_session.py} +256 -244
- package/templates/qoder/scripts/{export.py → domain/report/export.py} +72 -4
- package/templates/qoder/scripts/{report.py → domain/report/report.py} +292 -281
- package/templates/qoder/scripts/domain/report/report_snapshot.py +261 -0
- package/templates/qoder/scripts/domain/report/role.py +39 -0
- package/templates/qoder/scripts/{status.py → domain/report/status.py} +737 -628
- package/templates/qoder/scripts/domain/report/status_snapshot.py +191 -0
- package/templates/qoder/scripts/domain/requirement/__init__.py +0 -0
- package/templates/qoder/scripts/{archive_prd.py → domain/requirement/archive_prd.py} +389 -377
- package/templates/qoder/scripts/domain/requirement/req.py +228 -0
- package/templates/qoder/scripts/domain/task/__init__.py +11 -0
- package/templates/qoder/scripts/{git_sync.py → domain/task/git_sync.py} +90 -50
- package/templates/qoder/scripts/{syncgate.py → domain/task/syncgate.py} +18 -7
- package/templates/qoder/scripts/domain/task/task.py +229 -0
- package/templates/qoder/scripts/domain/task/task_lifecycle.py +606 -0
- package/templates/qoder/scripts/domain/task/task_query.py +162 -0
- package/templates/qoder/scripts/domain/task/task_relations.py +425 -0
- package/templates/qoder/scripts/{team_sync.py → domain/task/team_sync.py} +101 -23
- package/templates/qoder/scripts/domain/task/zentao_sync.py +370 -0
- package/templates/qoder/scripts/foundation/__init__.py +0 -0
- package/templates/qoder/scripts/foundation/bootstrap.py +145 -0
- package/templates/qoder/scripts/foundation/core/__init__.py +0 -0
- package/templates/qoder/scripts/foundation/core/cmd_registry.py +112 -0
- package/templates/qoder/scripts/foundation/core/config.py +187 -0
- package/templates/qoder/scripts/{common → foundation/core}/paths.py +706 -400
- package/templates/qoder/scripts/foundation/data/__init__.py +0 -0
- package/templates/qoder/scripts/foundation/data/contract.py +317 -0
- package/templates/qoder/scripts/{common → foundation/data}/events.py +2 -2
- package/templates/qoder/scripts/foundation/data/result.py +223 -0
- package/templates/qoder/scripts/{common → foundation/data}/task_utils.py +427 -392
- package/templates/qoder/scripts/foundation/identity/__init__.py +0 -0
- package/templates/qoder/scripts/foundation/identity/check_publish.py +103 -0
- package/templates/qoder/scripts/{common → foundation/identity}/developer.py +239 -238
- package/templates/qoder/scripts/foundation/identity/guard.py +159 -0
- package/templates/qoder/scripts/{common → foundation/identity}/identity.py +132 -9
- package/templates/qoder/scripts/foundation/identity/roles.py +60 -0
- package/templates/qoder/scripts/foundation/integrations/__init__.py +0 -0
- package/templates/qoder/scripts/{common → foundation/integrations}/active_task.py +30 -15
- package/templates/qoder/scripts/{common → foundation/integrations}/feishu.py +11 -10
- package/templates/qoder/scripts/{common → foundation/integrations}/terms.py +3 -3
- package/templates/qoder/scripts/foundation/integrations/zentao_client.py +220 -0
- package/templates/qoder/scripts/foundation/io/__init__.py +0 -0
- package/templates/qoder/scripts/{common → foundation/io}/filelock.py +1 -1
- package/templates/qoder/scripts/{common → foundation/io}/reqid.py +1 -1
- package/templates/qoder/scripts/foundation/protocol/__init__.py +0 -0
- package/templates/qoder/scripts/foundation/protocol/duckdb_conn.py +39 -0
- package/templates/qoder/scripts/foundation/protocol/mcp_base.py +268 -0
- package/templates/qoder/scripts/foundation/utils/__init__.py +0 -0
- package/templates/qoder/scripts/{common → foundation/utils}/eval_api.py +12 -1
- package/templates/qoder/scripts/{common → foundation/utils}/pip_install.py +23 -6
- package/templates/qoder/scripts/{common → foundation/utils}/platform_guard.py +1 -1
- package/templates/qoder/scripts/orchestration/__init__.py +0 -0
- package/templates/qoder/scripts/orchestration/wlkj.py +185 -0
- package/templates/qoder/scripts/protocol/__init__.py +0 -0
- package/templates/qoder/scripts/protocol/browser/README.md +207 -0
- package/templates/qoder/scripts/protocol/browser/SKILL.md +265 -0
- package/templates/qoder/scripts/protocol/browser/config.env +9 -0
- package/templates/qoder/scripts/protocol/browser/references/cdp-api.md +110 -0
- package/templates/qoder/scripts/protocol/browser/references/migration-2.5.3.md +72 -0
- package/templates/qoder/scripts/protocol/browser/references/site-patterns/.gitkeep +0 -0
- package/templates/qoder/scripts/protocol/browser/scripts/browser-discovery.mjs +138 -0
- package/templates/qoder/scripts/protocol/browser/scripts/cdp-proxy.mjs +672 -0
- package/templates/qoder/scripts/protocol/browser/scripts/check-deps.mjs +206 -0
- package/templates/qoder/scripts/protocol/browser/scripts/find-url.mjs +253 -0
- package/templates/qoder/scripts/protocol/browser/scripts/match-site.mjs +46 -0
- package/templates/qoder/scripts/protocol/browser/templates/config.env.template +9 -0
- package/templates/qoder/scripts/protocol/mcp/__init__.py +11 -0
- package/templates/qoder/scripts/protocol/mcp/kg_mcp_server.py +478 -0
- package/templates/qoder/scripts/{lanhu_stdio_wrapper.py → protocol/mcp/lanhu_stdio_wrapper.py} +131 -119
- package/templates/qoder/scripts/protocol/mcp/mcp_doctor.py +548 -0
- package/templates/qoder/scripts/protocol/mcp/mcp_launcher.py +600 -0
- package/templates/qoder/scripts/protocol/mcp/mysql_mcp_server.py +461 -0
- package/templates/qoder/scripts/protocol/mcp/zentao_mcp_server.py +1920 -0
- package/templates/qoder/scripts/protocol/transports/__init__.py +56 -0
- package/templates/qoder/scripts/protocol/transports/base.py +87 -0
- package/templates/qoder/scripts/protocol/transports/cli.py +132 -0
- package/templates/qoder/scripts/protocol/transports/http.py +141 -0
- package/templates/qoder/scripts/protocol/transports/stdio.py +293 -0
- package/templates/qoder/scripts/run_weekly_update.bat +27 -18
- package/templates/qoder/scripts/run_weekly_update.sh +22 -13
- package/templates/qoder/scripts/validation/__init__.py +0 -0
- package/templates/qoder/scripts/validation/eval/__init__.py +0 -0
- package/templates/qoder/scripts/validation/eval/perf-report-2026-06-24.md +139 -0
- package/templates/qoder/scripts/validation/eval/qwork_harness.py +617 -0
- package/templates/qoder/scripts/validation/eval/report-commands.md +220 -0
- package/templates/qoder/scripts/validation/eval/transcript_timing.py +386 -0
- package/templates/qoder/scripts/validation/metrics/__init__.py +0 -0
- package/templates/qoder/scripts/{eval_prd.py → validation/metrics/eval_prd.py} +84 -15
- package/templates/qoder/scripts/validation/metrics/usability_score.py +750 -0
- package/templates/qoder/scripts/validation/test/__init__.py +11 -0
- package/templates/qoder/scripts/validation/test/assertion_gen.py +551 -0
- package/templates/qoder/scripts/{autotest.py → validation/test/autotest.py} +1234 -1751
- package/templates/qoder/scripts/validation/test/autotest_auth.py +109 -0
- package/templates/qoder/scripts/{autotest_batch.py → validation/test/autotest_batch.py} +255 -224
- package/templates/qoder/scripts/validation/test/autotest_data.py +680 -0
- package/templates/qoder/scripts/validation/test/autotest_opencli.py +646 -0
- package/templates/qoder/scripts/{autotest_run.py → validation/test/autotest_run.py} +323 -297
- package/templates/qoder/scripts/validation/test/autotest_webaccess.py +571 -0
- package/templates/qoder/scripts/{benchmark.py → validation/test/benchmark.py} +17 -5
- package/templates/qoder/scripts/{kg_auto_login.py → validation/test/kg_auto_login.py} +208 -196
- package/templates/qoder/scripts/{kg_test_runner.py → validation/test/kg_test_runner.py} +13 -2
- package/templates/qoder/scripts/{page_probe.py → validation/test/page_probe.py} +470 -459
- package/templates/qoder/scripts/validation/test/smoke_all_commands.py +170 -0
- package/templates/qoder/settings.json +23 -2
- package/templates/qoder/skills/design-import/SKILL.md +235 -226
- package/templates/qoder/skills/design-review/SKILL.md +91 -82
- package/templates/qoder/skills/prd-generator/SKILL.md +38 -24
- package/templates/qoder/skills/prd-review/SKILL.md +8 -6
- package/templates/qoder/skills/prototype-generator/SKILL.md +296 -247
- package/templates/qoder/skills/spec-coder/SKILL.md +24 -15
- package/templates/qoder/skills/spec-generator/SKILL.md +24 -12
- package/templates/qoder/skills/test-generator/SKILL.md +9 -7
- package/templates/qoder/skills/wl-code/SKILL.md +25 -13
- package/templates/qoder/skills/wl-commit/SKILL.md +10 -9
- package/templates/qoder/skills/wl-design/SKILL.md +7 -5
- package/templates/qoder/skills/wl-init/SKILL.md +8 -8
- package/templates/qoder/skills/wl-insight/SKILL.md +25 -12
- package/templates/qoder/skills/wl-prd-full/SKILL.md +59 -8
- package/templates/qoder/skills/wl-prd-quick/SKILL.md +7 -7
- package/templates/qoder/skills/wl-prd-review/SKILL.md +18 -6
- package/templates/qoder/skills/wl-report/SKILL.md +27 -25
- package/templates/qoder/skills/wl-search/SKILL.md +151 -80
- package/templates/qoder/skills/wl-spec/SKILL.md +9 -7
- package/templates/qoder/skills/wl-status/SKILL.md +50 -18
- package/templates/qoder/skills/wl-task/SKILL.md +94 -12
- package/templates/qoder/skills/wl-test/SKILL.md +154 -45
- package/templates/qoder/templates/prd-full-template.md +7 -0
- package/templates/root/AGENTS.md +275 -237
- package/templates/root/requirements.txt +3 -0
- package/templates/root//344/275/277/347/224/250/350/257/264/346/230/216.md +3 -3
- package/templates/root//346/226/260/346/211/213/346/214/207/345/215/227.md +2 -2
- package/templates/qoder/hooks/inject-workflow-state.py +0 -169
- package/templates/qoder/scripts/__pycache__/check_mcp_launch.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/__pycache__/install_qoderwork.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/__pycache__/mcp_launcher.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/__pycache__/platform_doctor.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/check_carriers.py +0 -238
- package/templates/qoder/scripts/check_mcp.py +0 -298
- package/templates/qoder/scripts/check_mcp_launch.py +0 -183
- package/templates/qoder/scripts/check_qoderwork_consistency.py +0 -166
- package/templates/qoder/scripts/collect_prds.py +0 -31
- package/templates/qoder/scripts/common/mentions.py +0 -134
- package/templates/qoder/scripts/common/utf8.py +0 -38
- package/templates/qoder/scripts/extract_api_params.py +0 -246
- package/templates/qoder/scripts/extract_routes.py +0 -54
- package/templates/qoder/scripts/extract_routes_tree.py +0 -78
- package/templates/qoder/scripts/handoff.py +0 -22
- package/templates/qoder/scripts/init_developer.py +0 -76
- package/templates/qoder/scripts/kg_mcp_server.py +0 -801
- package/templates/qoder/scripts/kg_semantic.py +0 -150
- package/templates/qoder/scripts/mcp_launcher.py +0 -414
- package/templates/qoder/scripts/mysql_mcp_server.py +0 -396
- package/templates/qoder/scripts/parse_prds.py +0 -33
- package/templates/qoder/scripts/role.py +0 -51
- package/templates/qoder/scripts/sync_carriers.py +0 -259
- package/templates/qoder/scripts/task.py +0 -1261
- package/templates/qoder/scripts/workspace_init.py +0 -102
- package/templates/qoder/scripts/zentao_mcp_server.py +0 -424
- package/templates/qoder/skills/prompt-enrich/SKILL.md +0 -90
- /package/templates/qoder/scripts/{common → deployment}/__init__.py +0 -0
- /package/templates/qoder/{skills/prototype-generator/SKILL.md.zcode-79180-2af4721f-f9a6-412c-88db-c0af680d211b.tmp → scripts/domain/__init__.py} +0 -0
- /package/templates/qoder/scripts/{common → domain/kg/graph}/graph_traverse.py +0 -0
- /package/templates/qoder/scripts/{common → domain/kg/server}/repowiki.py +0 -0
- /package/templates/qoder/scripts/{common → foundation/io}/atomicio.py +0 -0
- /package/templates/qoder/scripts/{secure-ls.js → validation/test/secure-ls.js} +0 -0
|
@@ -0,0 +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))))
|
|
File without changes
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
cmd_registry.py - 命令注册器 (Command Pattern, 治 argparse 样板重复)
|
|
4
|
+
|
|
5
|
+
现状: task.py 14 子命令(81行样板) + autotest.py 12 子命令(105行样板),
|
|
6
|
+
每个子命令都要 add_parser + add_argument×N + set_defaults(func=) 三行,
|
|
7
|
+
新增命令要改 3 处 (函数定义 + add_parser + set_defaults), 容易漏。
|
|
8
|
+
|
|
9
|
+
本模块引入声明式注册: 用 (name, help, args_spec, func) 元组表 + build_parser
|
|
10
|
+
一次性构建 subparser, 消除样板。
|
|
11
|
+
|
|
12
|
+
args_spec 格式 (简化 argparse.add_argument):
|
|
13
|
+
[
|
|
14
|
+
('title', {...}), # 位置参数
|
|
15
|
+
('--slug', {'help': 'slug'}), # 可选参数
|
|
16
|
+
('--pri', {'choices':[1,2,3,4], 'type':int, 'default':3}),
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
Usage:
|
|
20
|
+
from foundation.core.cmd_registry import build_parser
|
|
21
|
+
|
|
22
|
+
COMMANDS = [
|
|
23
|
+
('create', '建任务', [
|
|
24
|
+
('title', {'help': '任务标题'}),
|
|
25
|
+
('--slug', {'help': 'URL slug'}),
|
|
26
|
+
], cmd_create),
|
|
27
|
+
('list', '列任务', [], cmd_list),
|
|
28
|
+
]
|
|
29
|
+
parser = build_parser('task', '任务系统', COMMANDS)
|
|
30
|
+
args = parser.parse_args()
|
|
31
|
+
args.func(args) # set_defaults 已自动绑定
|
|
32
|
+
|
|
33
|
+
收益: task/autotest 各省 ~60-80 行样板, 新增命令从改 3 处降到改 1 处元组行。
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
import argparse
|
|
37
|
+
import sys
|
|
38
|
+
from typing import Any, Callable, Dict, List, Sequence, Tuple
|
|
39
|
+
|
|
40
|
+
__all__ = ["build_parser", "run_command"]
|
|
41
|
+
|
|
42
|
+
# 类型别名: (name, help, args_spec, handler)
|
|
43
|
+
CommandSpec = Tuple[str, str, List[Tuple[str, Dict[str, Any]]], Callable]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _add_args(subparser: argparse.ArgumentParser, args_spec: List[Tuple[str, Dict[str, Any]]]) -> None:
|
|
47
|
+
"""把 args_spec 转成 subparser.add_argument 调用。"""
|
|
48
|
+
for name, opts in args_spec:
|
|
49
|
+
# 拆出 positional vs optional 的特殊处理
|
|
50
|
+
kwargs = dict(opts) if opts else {}
|
|
51
|
+
try:
|
|
52
|
+
subparser.add_argument(name, **kwargs)
|
|
53
|
+
except (TypeError, ValueError):
|
|
54
|
+
# 容错: 个别参数键不兼容时降级
|
|
55
|
+
subparser.add_argument(name)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def build_parser(
|
|
59
|
+
prog: str,
|
|
60
|
+
description: str,
|
|
61
|
+
commands: Sequence[CommandSpec],
|
|
62
|
+
*,
|
|
63
|
+
common_args: List[Tuple[str, Dict[str, Any]]] = None,
|
|
64
|
+
) -> argparse.ArgumentParser:
|
|
65
|
+
"""从命令元组表构建 argparse parser。
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
prog: 程序名 (如 'task')
|
|
69
|
+
description: 顶部描述 (如 '任务系统')
|
|
70
|
+
commands: [(name, help, args_spec, handler), ...] 命令元组表
|
|
71
|
+
common_args: 所有子命令共享的可选参数 (如 --platform)
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
配置好的 ArgumentParser, parse_args 后 args.func 即处理函数。
|
|
75
|
+
"""
|
|
76
|
+
parser = argparse.ArgumentParser(prog=prog, description=description)
|
|
77
|
+
sub = parser.add_subparsers(dest="command")
|
|
78
|
+
|
|
79
|
+
for name, help_text, args_spec, handler in commands:
|
|
80
|
+
sp = sub.add_parser(name, help=help_text, description=help_text)
|
|
81
|
+
_add_args(sp, args_spec)
|
|
82
|
+
# 共享参数
|
|
83
|
+
if common_args:
|
|
84
|
+
_add_args(sp, common_args)
|
|
85
|
+
sp.set_defaults(func=handler)
|
|
86
|
+
|
|
87
|
+
return parser
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def run_command(
|
|
91
|
+
commands: Sequence[CommandSpec],
|
|
92
|
+
argv: List[str] = None,
|
|
93
|
+
*,
|
|
94
|
+
prog: str = "",
|
|
95
|
+
description: str = "",
|
|
96
|
+
common_args: List[Tuple[str, Dict[str, Any]]] = None,
|
|
97
|
+
) -> int:
|
|
98
|
+
"""便捷运行: 构建 parser + parse + 调用 func。
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
处理函数的返回值 (通常是 int exit code), 无命令时返回 0。
|
|
102
|
+
"""
|
|
103
|
+
parser = build_parser(prog or "cmd", description, commands, common_args=common_args)
|
|
104
|
+
args = parser.parse_args(argv if argv is not None else sys.argv[1:])
|
|
105
|
+
if not getattr(args, "command", None):
|
|
106
|
+
parser.print_help()
|
|
107
|
+
return 0
|
|
108
|
+
func = getattr(args, "func", None)
|
|
109
|
+
if func is None:
|
|
110
|
+
return 1
|
|
111
|
+
ret = func(args)
|
|
112
|
+
return int(ret) if isinstance(ret, int) else 0
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
config.py - Config 单例 (Singleton 模式, 治"4份 load_config + 明文凭据")
|
|
4
|
+
|
|
5
|
+
现状: config.yaml 被至少 4 处各自重读 (autotest/git_sync/init_doctor/feishu),
|
|
6
|
+
每次都 open + yaml.safe_load, 无缓存, 且段名用裸字符串散落各处 (拼写风险)。
|
|
7
|
+
|
|
8
|
+
本模块引入单例 Config:
|
|
9
|
+
- 单次 yaml.safe_load + mtime 缓存 (config 改了自动重读)
|
|
10
|
+
- 14 段名抽常量 (Config.SECTIONS), 消拼写风险
|
|
11
|
+
- 类型化访问: Config.load().feishu / .autotest / .mysql ...
|
|
12
|
+
- 凭据从代码移除: mysql/zentao 密码只在 config.yaml, 不在源码默认值
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
from foundation.core.config import Config, SECTIONS
|
|
16
|
+
cfg = Config.load() # 单例, 全进程共享
|
|
17
|
+
feishu = cfg.section('feishu') # {'enabled': false, ...}
|
|
18
|
+
mysql = cfg.mysql # 类型化便捷访问
|
|
19
|
+
if not feishu.get('enabled'): ...
|
|
20
|
+
|
|
21
|
+
向后兼容: load_config() / load_config_section() 旧函数保留, 转发到 Config。
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import os
|
|
25
|
+
import sys
|
|
26
|
+
import threading
|
|
27
|
+
from typing import Any, Dict, Optional
|
|
28
|
+
|
|
29
|
+
__all__ = ["Config", "SECTIONS", "load_config", "load_config_section"]
|
|
30
|
+
|
|
31
|
+
# 相对导入
|
|
32
|
+
try:
|
|
33
|
+
from .paths import get_repo_root
|
|
34
|
+
except ImportError:
|
|
35
|
+
_COMMON_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
36
|
+
if _COMMON_DIR not in sys.path:
|
|
37
|
+
sys.path.insert(0, _COMMON_DIR)
|
|
38
|
+
from paths import get_repo_root # type: ignore
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# ============================================================
|
|
42
|
+
# 14 个顶层段名常量 (单一真源, 消拼写风险)
|
|
43
|
+
# ============================================================
|
|
44
|
+
|
|
45
|
+
class SECTIONS:
|
|
46
|
+
"""config.yaml 顶层段名。调用方用 SECTIONS.FEISHU 而非裸字符串 'feishu'。"""
|
|
47
|
+
ROLES = "roles"
|
|
48
|
+
PIPELINE = "pipeline"
|
|
49
|
+
MYSQL = "mysql"
|
|
50
|
+
ZENTAO = "zentao"
|
|
51
|
+
PLATFORMS = "platforms"
|
|
52
|
+
AUTOTEST = "autotest"
|
|
53
|
+
TEAM_REMOTE = "team_remote"
|
|
54
|
+
GIT_SYNC = "git_sync"
|
|
55
|
+
PRD_MODES = "prd_modes"
|
|
56
|
+
TASK = "task"
|
|
57
|
+
STATUS = "status"
|
|
58
|
+
INSIGHT = "insight"
|
|
59
|
+
FEISHU = "feishu"
|
|
60
|
+
NOTIFICATION = "notification"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# ============================================================
|
|
64
|
+
# Config 单例
|
|
65
|
+
# ============================================================
|
|
66
|
+
|
|
67
|
+
class _ConfigData:
|
|
68
|
+
"""config.yaml 的类型化包装。由 Config.load() 返回实例。"""
|
|
69
|
+
|
|
70
|
+
def __init__(self, raw: Dict[str, Any]):
|
|
71
|
+
self._raw = raw or {}
|
|
72
|
+
|
|
73
|
+
def section(self, name: str, default: Optional[dict] = None) -> dict:
|
|
74
|
+
"""取某段 (顶层 key)。不存在返回 default (默认 {})。"""
|
|
75
|
+
val = self._raw.get(name, {} if default is None else default)
|
|
76
|
+
return val if isinstance(val, dict) else ({} if default is None else default)
|
|
77
|
+
|
|
78
|
+
def get(self, name: str, default: Any = None) -> Any:
|
|
79
|
+
"""取任意顶层 key (非段, 如标量)。"""
|
|
80
|
+
return self._raw.get(name, default)
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def raw(self) -> Dict[str, Any]:
|
|
84
|
+
"""原始整份 dict (兼容旧 load_config() 返回)。"""
|
|
85
|
+
return self._raw
|
|
86
|
+
|
|
87
|
+
# ---- 类型化便捷访问 (常用段) ----
|
|
88
|
+
@property
|
|
89
|
+
def roles(self) -> dict:
|
|
90
|
+
return self.section(SECTIONS.ROLES)
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def mysql(self) -> dict:
|
|
94
|
+
return self.section(SECTIONS.MYSQL)
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def zentao(self) -> dict:
|
|
98
|
+
return self.section(SECTIONS.ZENTAO)
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def autotest(self) -> dict:
|
|
102
|
+
return self.section(SECTIONS.AUTOTEST)
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def platforms(self) -> dict:
|
|
106
|
+
return self.section(SECTIONS.PLATFORMS)
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def git_sync(self) -> dict:
|
|
110
|
+
return self.section(SECTIONS.GIT_SYNC)
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def feishu(self) -> dict:
|
|
114
|
+
return self.section(SECTIONS.FEISHU)
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def team_remote(self) -> dict:
|
|
118
|
+
return self.section(SECTIONS.TEAM_REMOTE)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class Config:
|
|
122
|
+
"""config.yaml 单例加载器 (mtime 缓存)。
|
|
123
|
+
|
|
124
|
+
- 首次 load() 读文件 + yaml.safe_load, 缓存结果 + 文件 mtime
|
|
125
|
+
- 后续 load() 检查 mtime, 变了才重读 (热重载, 无需重启)
|
|
126
|
+
- config.yaml 不存在 → 返回空 ConfigData (软降级, 不崩)
|
|
127
|
+
- 线程安全 (Lock 保护首次加载)
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
_instance: Optional[_ConfigData] = None
|
|
131
|
+
_mtime: Optional[float] = None
|
|
132
|
+
_path: Optional[str] = None
|
|
133
|
+
_lock = threading.Lock()
|
|
134
|
+
|
|
135
|
+
@classmethod
|
|
136
|
+
def _config_path(cls) -> str:
|
|
137
|
+
if cls._path is None:
|
|
138
|
+
cls._path = str(get_repo_root() / ".qoder" / "config.yaml")
|
|
139
|
+
return cls._path
|
|
140
|
+
|
|
141
|
+
@classmethod
|
|
142
|
+
def load(cls) -> _ConfigData:
|
|
143
|
+
"""加载/返回缓存的 config。mtime 变化时自动重读。"""
|
|
144
|
+
path = cls._config_path()
|
|
145
|
+
try:
|
|
146
|
+
cur_mtime = os.path.getmtime(path)
|
|
147
|
+
except OSError:
|
|
148
|
+
# 文件不存在 → 空配置 (软降级)
|
|
149
|
+
return _ConfigData({})
|
|
150
|
+
with cls._lock:
|
|
151
|
+
if cls._instance is not None and cls._mtime == cur_mtime:
|
|
152
|
+
return cls._instance
|
|
153
|
+
# (重)读
|
|
154
|
+
try:
|
|
155
|
+
import yaml
|
|
156
|
+
with open(path, encoding="utf-8") as f:
|
|
157
|
+
raw = yaml.safe_load(f) or {}
|
|
158
|
+
except Exception:
|
|
159
|
+
raw = {}
|
|
160
|
+
cls._instance = _ConfigData(raw)
|
|
161
|
+
cls._mtime = cur_mtime
|
|
162
|
+
return cls._instance
|
|
163
|
+
|
|
164
|
+
@classmethod
|
|
165
|
+
def reload(cls) -> _ConfigData:
|
|
166
|
+
"""强制重读 (忽略缓存)。测试/配置热更新用。"""
|
|
167
|
+
cls._mtime = None
|
|
168
|
+
return cls.load()
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
# ============================================================
|
|
172
|
+
# 向后兼容: 旧函数签名转发到 Config 单例
|
|
173
|
+
# ============================================================
|
|
174
|
+
|
|
175
|
+
def load_config() -> dict:
|
|
176
|
+
"""[兼容] 返回整份 config dict (替代 git_sync/init_doctor 各自的 load_config)。"""
|
|
177
|
+
return Config.load().raw
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def load_config_section(section: str = SECTIONS.AUTOTEST, default: Optional[dict] = None) -> dict:
|
|
181
|
+
"""[兼容] 返回某段 dict (替代 autotest/feishu 各自的 load_config_section/_load_config)。
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
section: 段名 (用 SECTIONS.XXX 常量, 不要裸字符串)
|
|
185
|
+
default: 段不存在时的返回值 (默认 {})
|
|
186
|
+
"""
|
|
187
|
+
return Config.load().section(section, default if default is not None else {})
|