@hupan56/wlkj 3.0.0 → 3.1.3
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 -1373
- package/package.json +1 -1
- package/templates/qoder/commands/optional/wl-insight.md +6 -4
- 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 +5 -5
- 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 +3 -3
- 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 +10 -10
- package/templates/qoder/config.yaml +35 -13
- package/templates/qoder/hooks/post-tool-use.py +181 -0
- package/templates/qoder/hooks/session-start.py +82 -1
- 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 +70 -8
- package/templates/qoder/scripts/README.md +78 -115
- 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/{report → deployment/setup}/__init__.py +11 -11
- package/templates/qoder/scripts/{setup → deployment/setup}/carriers.py +33 -26
- package/templates/qoder/scripts/deployment/setup/carriers_verify.py +148 -0
- package/templates/qoder/scripts/{setup → deployment/setup}/init_doctor.py +55 -23
- package/templates/qoder/scripts/{setup → deployment/setup}/install_qoderwork.py +20 -12
- package/templates/qoder/scripts/{setup → deployment/setup}/platform_doctor.py +8 -2
- package/templates/qoder/scripts/{setup → deployment/setup}/repo_root.py +113 -112
- package/templates/qoder/scripts/{setup → deployment/setup}/setup.py +35 -12
- package/templates/qoder/scripts/{setup → deployment/setup}/setup_lanhu.py +13 -7
- package/templates/qoder/scripts/domain/__init__.py +0 -0
- package/templates/qoder/scripts/domain/design/__init__.py +0 -0
- package/templates/qoder/scripts/{report → domain/design}/fill_prototype.py +12 -6
- package/templates/qoder/scripts/{report → domain/design}/gen_design_doc.py +8 -2
- package/templates/qoder/scripts/{setup → domain/kg}/__init__.py +11 -11
- package/templates/qoder/scripts/domain/kg/build/__init__.py +0 -0
- package/templates/qoder/scripts/{kg → domain/kg/build}/build_entity_registry.py +7 -2
- package/templates/qoder/scripts/{kg → domain/kg/build}/build_relations.py +7 -2
- package/templates/qoder/scripts/{kg → domain/kg/build}/build_style_index.py +13 -7
- package/templates/qoder/scripts/{kg → domain/kg/build}/build_workflows.py +7 -2
- package/templates/qoder/scripts/{kg → domain/kg/build}/kg_build.py +76 -9
- package/templates/qoder/scripts/{kg → domain/kg/build}/kg_build_db.py +7 -2
- package/templates/qoder/scripts/{kg → domain/kg/build}/kg_incremental.py +11 -6
- package/templates/qoder/scripts/{report → domain/kg/build}/learn_aggregate.py +9 -4
- 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/{common → domain/kg/extract}/test_extract.py +115 -115
- 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 → domain/kg/graph}/kg_link_db.py +7 -2
- package/templates/qoder/scripts/domain/kg/graph/kg_semantic.py +683 -0
- package/templates/qoder/scripts/{kg → domain/kg}/kg.py +263 -30
- package/templates/qoder/scripts/{common → domain/kg}/kg_capabilities.py +182 -182
- package/templates/qoder/scripts/domain/kg/search/__init__.py +0 -0
- package/templates/qoder/scripts/{kg → domain/kg/search}/context_pack.py +30 -9
- package/templates/qoder/scripts/{kg → domain/kg/search}/enrich_prompt.py +10 -4
- package/templates/qoder/scripts/{kg → domain/kg/search}/prefetch.py +33 -8
- package/templates/qoder/scripts/{common → domain/kg/search}/search_engine.py +207 -205
- package/templates/qoder/scripts/{kg → domain/kg/search}/search_index.py +143 -77
- 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 → domain/kg/storage}/kg_duckdb.py +38 -14
- package/templates/qoder/scripts/domain/learning/__init__.py +0 -0
- package/templates/qoder/scripts/{report → domain/learning}/learn.py +8 -3
- package/templates/qoder/scripts/{mcp → domain/report}/__init__.py +11 -11
- package/templates/qoder/scripts/{report → domain/report}/add_session.py +9 -3
- package/templates/qoder/scripts/{report → domain/report}/export.py +10 -5
- package/templates/qoder/scripts/{report → domain/report}/report.py +9 -4
- package/templates/qoder/scripts/domain/report/report_snapshot.py +261 -0
- package/templates/qoder/scripts/{report → domain/report}/role.py +10 -4
- package/templates/qoder/scripts/{report → domain/report}/status.py +112 -9
- 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/{report → domain/requirement}/archive_prd.py +8 -2
- package/templates/qoder/scripts/{report → domain/requirement}/req.py +228 -222
- package/templates/qoder/scripts/{kg → domain/task}/__init__.py +11 -11
- package/templates/qoder/scripts/domain/task/__pycache__/syncgate.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/{task → domain/task}/git_sync.py +40 -21
- package/templates/qoder/scripts/{task → domain/task}/syncgate.py +13 -8
- package/templates/qoder/scripts/{task → domain/task}/task.py +13 -5
- package/templates/qoder/scripts/{task → domain/task}/task_lifecycle.py +606 -596
- package/templates/qoder/scripts/{task → domain/task}/task_query.py +162 -161
- package/templates/qoder/scripts/{task → domain/task}/task_relations.py +425 -424
- package/templates/qoder/scripts/{task → domain/task}/team_sync.py +9 -4
- 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/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/foundation/__pycache__/bootstrap.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/{common → foundation}/bootstrap.py +10 -10
- package/templates/qoder/scripts/foundation/core/__init__.py +0 -0
- package/templates/qoder/scripts/foundation/core/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/foundation/core/__pycache__/paths.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/{common → foundation/core}/cmd_registry.py +1 -1
- package/templates/qoder/scripts/{common → foundation/core}/config.py +1 -1
- package/templates/qoder/scripts/{common → foundation/core}/paths.py +706 -586
- package/templates/qoder/scripts/foundation/data/__init__.py +0 -0
- package/templates/qoder/scripts/{common → foundation/data}/contract.py +2 -2
- package/templates/qoder/scripts/{common → foundation/data}/events.py +2 -2
- package/templates/qoder/scripts/{common → foundation/data}/result.py +1 -1
- package/templates/qoder/scripts/{common → foundation/data}/task_utils.py +427 -404
- package/templates/qoder/scripts/foundation/identity/__init__.py +0 -0
- package/templates/qoder/scripts/foundation/identity/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/foundation/identity/__pycache__/identity.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/{common → foundation/identity}/check_publish.py +103 -98
- package/templates/qoder/scripts/{common → foundation/identity}/developer.py +239 -239
- package/templates/qoder/scripts/{common → foundation/identity}/guard.py +2 -2
- package/templates/qoder/scripts/{common → foundation/identity}/identity.py +14 -10
- package/templates/qoder/scripts/{common → foundation/identity}/roles.py +1 -1
- 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 +2 -2
- 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/foundation/io/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/foundation/io/__pycache__/atomicio.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/foundation/io/__pycache__/filelock.cpython-39.pyc +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/{common → foundation/protocol}/mcp_base.py +1 -1
- 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/{mcp → protocol/mcp}/lanhu_stdio_wrapper.py +11 -5
- package/templates/qoder/scripts/{mcp → protocol/mcp}/mcp_doctor.py +47 -14
- package/templates/qoder/scripts/protocol/mcp/mcp_launcher.py +600 -0
- package/templates/qoder/scripts/{mcp → protocol/mcp}/mysql_mcp_server.py +181 -67
- 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/{report → validation/metrics}/eval_prd.py +17 -10
- 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/{test → validation/test}/autotest.py +64 -4
- package/templates/qoder/scripts/{common → validation/test}/autotest_auth.py +1 -1
- package/templates/qoder/scripts/{test → validation/test}/autotest_batch.py +16 -3
- package/templates/qoder/scripts/{test → validation/test}/autotest_data.py +17 -12
- package/templates/qoder/scripts/validation/test/autotest_opencli.py +646 -0
- package/templates/qoder/scripts/{test → validation/test}/autotest_run.py +17 -3
- package/templates/qoder/scripts/validation/test/autotest_webaccess.py +571 -0
- package/templates/qoder/scripts/{test → validation/test}/benchmark.py +12 -6
- package/templates/qoder/scripts/{test → validation/test}/kg_auto_login.py +8 -2
- package/templates/qoder/scripts/{test → validation/test}/kg_test_runner.py +7 -2
- package/templates/qoder/scripts/{test → validation/test}/page_probe.py +8 -3
- package/templates/qoder/scripts/validation/test/smoke_all_commands.py +170 -0
- package/templates/qoder/settings.json +30 -0
- package/templates/qoder/skills/design-import/SKILL.md +22 -13
- package/templates/qoder/skills/design-review/SKILL.md +19 -10
- package/templates/qoder/skills/prd-generator/SKILL.md +22 -18
- package/templates/qoder/skills/prd-review/SKILL.md +6 -4
- package/templates/qoder/skills/prototype-generator/SKILL.md +63 -14
- package/templates/qoder/skills/spec-coder/SKILL.md +22 -13
- package/templates/qoder/skills/spec-generator/SKILL.md +22 -10
- package/templates/qoder/skills/test-generator/SKILL.md +7 -5
- package/templates/qoder/skills/wl-code/SKILL.md +23 -11
- package/templates/qoder/skills/wl-commit/SKILL.md +9 -8
- package/templates/qoder/skills/wl-design/SKILL.md +5 -3
- package/templates/qoder/skills/wl-init/SKILL.md +6 -6
- package/templates/qoder/skills/wl-insight/SKILL.md +23 -10
- package/templates/qoder/skills/wl-prd-full/SKILL.md +57 -6
- package/templates/qoder/skills/wl-prd-quick/SKILL.md +3 -3
- package/templates/qoder/skills/wl-prd-review/SKILL.md +16 -4
- package/templates/qoder/skills/wl-report/SKILL.md +25 -23
- package/templates/qoder/skills/wl-search/SKILL.md +149 -78
- package/templates/qoder/skills/wl-spec/SKILL.md +7 -5
- package/templates/qoder/skills/wl-status/SKILL.md +48 -16
- package/templates/qoder/skills/wl-task/SKILL.md +92 -10
- package/templates/qoder/skills/wl-test/SKILL.md +75 -29
- package/templates/qoder/templates/prd-full-template.md +7 -0
- package/templates/root/AGENTS.md +275 -236
- 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/.runtime/ctx-cache-5660152f1d6dd819.md +0 -23
- package/templates/.qoder/.runtime/ctx-cache-afdce0dac06b25b0.md +0 -23
- package/templates/.qoder/.runtime/search-cache-eae7644e7b122f35.txt +0 -1
- package/templates/.qoder/learning/eval-history.jsonl +0 -28
- package/templates/data/index/wiki-index.json +0 -8
- package/templates/qoder/nul +0 -4
- package/templates/qoder/scripts/kg/extract.py +0 -398
- package/templates/qoder/scripts/kg/kg_semantic.py +0 -156
- package/templates/qoder/scripts/mcp/kg_mcp_server.py +0 -606
- package/templates/qoder/scripts/mcp/mcp_launcher.py +0 -442
- package/templates/qoder/scripts/mcp/zentao_mcp_server.py +0 -384
- package/templates/qoder/scripts/task/__init__.py +0 -11
- package/templates/qoder/scripts/test/__init__.py +0 -11
- package/templates/qoder/scripts/wlkj.py +0 -116
- /package/templates/qoder/scripts/{common → deployment}/__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/{test → validation/test}/secure-ls.js +0 -0
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
# v3.0 路径自举: 引导到 common/bootstrap, 统一 sys.path 逻辑
|
|
4
4
|
import os as _o, sys as _s
|
|
5
|
-
|
|
5
|
+
_f = _o.path.abspath(__file__)
|
|
6
|
+
for _ in range(10):
|
|
7
|
+
_f = _o.path.dirname(_f)
|
|
8
|
+
_cp = _o.path.join(_f, 'foundation')
|
|
9
|
+
if _o.path.isfile(_o.path.join(_cp, 'bootstrap.py')):
|
|
10
|
+
break
|
|
6
11
|
if _cp not in _s.path: _s.path.insert(0, _cp)
|
|
7
12
|
from bootstrap import setup; setup()
|
|
8
13
|
|
|
@@ -40,8 +45,8 @@ if sys.platform == 'win32':
|
|
|
40
45
|
pass
|
|
41
46
|
|
|
42
47
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
43
|
-
from
|
|
44
|
-
from
|
|
48
|
+
from foundation.core.paths import get_repo_root, get_developer
|
|
49
|
+
from foundation.io.filelock import FileLock, LockTimeoutError
|
|
45
50
|
from syncgate import run_gates, SAFE_EXTENSIONS
|
|
46
51
|
|
|
47
52
|
BASE = str(get_repo_root())
|
|
@@ -114,7 +119,7 @@ def _notify_prd_publications(staged_files):
|
|
|
114
119
|
"""
|
|
115
120
|
import re as _re
|
|
116
121
|
try:
|
|
117
|
-
from
|
|
122
|
+
from foundation.integrations.feishu import notify_prd_published, is_enabled
|
|
118
123
|
except ImportError as e:
|
|
119
124
|
print('[sync] WARN: feishu 模块缺失, 跳过 PRD 发布通知: %s' % e, file=sys.stderr)
|
|
120
125
|
return
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
zentao_sync.py - 本地任务 ↔ 禅道 双向同步(安全优先)
|
|
5
|
+
|
|
6
|
+
设计目标 (回应团队最担心的「拉错/覆盖队友」):
|
|
7
|
+
- 拉取(禅道→本地): 强制 assignedTo == 我的禅道账号 过滤, 物理上拉不到队友任务。
|
|
8
|
+
- 推送(本地→禅道): 默认 --dry-run, 仅推 creator==我 的任务, 且禅道同名任务
|
|
9
|
+
assignedTo 须与我一致才更新, 不一致拒绝 (防误改队友任务)。
|
|
10
|
+
- 幂等: task.json 的 zentao_id 做主键, 已有则跳过 create。
|
|
11
|
+
|
|
12
|
+
隔离契约: contracts/isolation.md「禅道同步」红线。
|
|
13
|
+
归属映射: member.json 的 zentao_account 字段 (本地 dev 名 ↔ 禅道 account)。
|
|
14
|
+
|
|
15
|
+
用法:
|
|
16
|
+
python .qoder/scripts/domain/task/zentao_sync.py pull <execution_id> [--apply] # 默认 dry-run 预览
|
|
17
|
+
python .qoder/scripts/domain/task/zentao_sync.py push [--execution_id N] [--apply]
|
|
18
|
+
python .qoder/scripts/domain/task/zentao_sync.py status # 看同步状态概览
|
|
19
|
+
|
|
20
|
+
退出码:
|
|
21
|
+
0 成功 (含 dry-run 预览); 2 busy; 3 配置/归属错误; 4 权限拒绝; 5 网络不通
|
|
22
|
+
"""
|
|
23
|
+
import os
|
|
24
|
+
import sys
|
|
25
|
+
|
|
26
|
+
# ── 路径自举: 让脚本能独立运行 (复用 bootstrap 模式) ──
|
|
27
|
+
# task_utils/identity 用相对导入 (.paths), 必须从 common 包上下文导入,
|
|
28
|
+
# 因此把 scripts/ 加入 path, 用 common.xxx / task.xxx 访问。
|
|
29
|
+
_HERE = os.path.dirname(os.path.abspath(__file__))
|
|
30
|
+
_SCRIPTS = os.path.dirname(_HERE) # .qoder/scripts
|
|
31
|
+
_COMMON = os.path.join(_SCRIPTS, 'foundation')
|
|
32
|
+
for _p in (_SCRIPTS, _COMMON):
|
|
33
|
+
if _p not in sys.path:
|
|
34
|
+
sys.path.insert(0, _p)
|
|
35
|
+
try:
|
|
36
|
+
from bootstrap import setup; setup()
|
|
37
|
+
except Exception:
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
import json
|
|
41
|
+
import argparse
|
|
42
|
+
|
|
43
|
+
from foundation.core.paths import get_repo_root, get_tasks_dir, get_developer
|
|
44
|
+
from foundation.identity.identity import get_member
|
|
45
|
+
from foundation.data.task_utils import modify_task_json, load_task_json, write_task_json
|
|
46
|
+
from foundation.integrations.zentao_client import ZentaoClient
|
|
47
|
+
from datetime import datetime
|
|
48
|
+
from pathlib import Path
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _create_local_task(repo_root, title, slug_seed, zentao_id, developer):
|
|
52
|
+
"""直接创建本地任务目录 + task.json (含 zentao_id), 不走 cmd_create。
|
|
53
|
+
|
|
54
|
+
自包含, 避免 cmd_create 的 argparse Namespace 签名 + team_sync stage 副作用。
|
|
55
|
+
返回新建的 task_dir 或 None。"""
|
|
56
|
+
date_prefix = datetime.now().strftime("%m-%d")
|
|
57
|
+
tasks_dir = get_tasks_dir(repo_root)
|
|
58
|
+
task_dir_name = "%s-%s" % (date_prefix, slug_seed)
|
|
59
|
+
task_dir = tasks_dir / task_dir_name
|
|
60
|
+
if task_dir.exists():
|
|
61
|
+
# 撞名加后缀
|
|
62
|
+
task_dir_name = "%s-%s-%s" % (date_prefix, slug_seed, developer[:6])
|
|
63
|
+
task_dir = tasks_dir / task_dir_name
|
|
64
|
+
task_dir.mkdir(parents=True, exist_ok=True)
|
|
65
|
+
now_iso = datetime.now().isoformat()
|
|
66
|
+
task_data = {
|
|
67
|
+
"title": title, "slug": slug_seed, "status": "planning", "priority": "P2",
|
|
68
|
+
"assignee": developer, "creator": developer,
|
|
69
|
+
"created_at": now_iso, "updated_at": now_iso,
|
|
70
|
+
"branch": None, "base_branch": None, "scope": None,
|
|
71
|
+
"parent": None, "children": [], "tags": [],
|
|
72
|
+
"due_date": None, "start_date": None, "estimate_hours": None,
|
|
73
|
+
"blocked_by": [], "blocks": [],
|
|
74
|
+
"stage_ts": {"created": now_iso, "started": None, "completed": None},
|
|
75
|
+
"zentao_id": zentao_id,
|
|
76
|
+
}
|
|
77
|
+
if write_task_json(task_dir, task_data):
|
|
78
|
+
return task_dir
|
|
79
|
+
return None
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# ============================================================
|
|
83
|
+
# 配置与归属解析
|
|
84
|
+
# ============================================================
|
|
85
|
+
|
|
86
|
+
def _zentao_creds():
|
|
87
|
+
"""构建禅道客户端。凭据不全返回 None。
|
|
88
|
+
底层 HTTP 逻辑收敛在 common.zentao_client.ZentaoClient (与 mcp_server 共享)。"""
|
|
89
|
+
return ZentaoClient.from_config()
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _resolve_my_account(developer):
|
|
93
|
+
"""解析当前本地 developer → 禅道 account。
|
|
94
|
+
无 member.json 或无 zentao_account → 返回 (None, 原因)。"""
|
|
95
|
+
mem = get_member(developer)
|
|
96
|
+
if not mem:
|
|
97
|
+
return None, '当前开发者 %s 未注册 (无 member.json)。请先 /wl-init。' % developer
|
|
98
|
+
acc = (mem.get('zentao_account') or '').strip()
|
|
99
|
+
if not acc:
|
|
100
|
+
return None, ('%s 的 member.json 未配 zentao_account (本地名↔禅道账号映射)。'
|
|
101
|
+
'\n 手动编辑 workspace/members/%s/member.json 加 "zentao_account": "你的禅道账号"。'
|
|
102
|
+
% (developer, developer))
|
|
103
|
+
return acc, None
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# ============================================================
|
|
107
|
+
# 禅道 HTTP 层已收敛到 common.zentao_client.ZentaoClient (与 mcp_server 共享同一份)
|
|
108
|
+
# 下面 cmd_* 通过 creds (ZentaoClient 实例) 的 .api() / .check_intranet() 调用
|
|
109
|
+
# ============================================================
|
|
110
|
+
|
|
111
|
+
# ============================================================
|
|
112
|
+
# 本地任务遍历
|
|
113
|
+
# ============================================================
|
|
114
|
+
|
|
115
|
+
def _iter_local_tasks(repo_root):
|
|
116
|
+
"""遍历 workspace/tasks/*/task.json, yield (task_dir, data)。"""
|
|
117
|
+
tdir = get_tasks_dir(repo_root)
|
|
118
|
+
if not tdir.is_dir():
|
|
119
|
+
return
|
|
120
|
+
for child in sorted(tdir.iterdir()):
|
|
121
|
+
if not child.is_dir():
|
|
122
|
+
continue
|
|
123
|
+
data = load_task_json(child)
|
|
124
|
+
if data:
|
|
125
|
+
yield child, data
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
# ============================================================
|
|
129
|
+
# 拉取: 禅道 → 本地 (归属硬过滤)
|
|
130
|
+
# ============================================================
|
|
131
|
+
|
|
132
|
+
def cmd_pull(args, creds, my_account):
|
|
133
|
+
"""拉取某执行下 assignedTo==我 的任务到本地。默认 dry-run。"""
|
|
134
|
+
eid = args.execution_id
|
|
135
|
+
print('═══ 拉取 执行 #%s (仅 assignedTo=%s) ═══' % (eid, my_account))
|
|
136
|
+
if not args.apply:
|
|
137
|
+
print('[DRY-RUN] 仅预览, 不写本地。加 --apply 才真写。\n')
|
|
138
|
+
|
|
139
|
+
r = creds.api('GET', '/executions/%s/tasks' % eid, {'limit': 100})
|
|
140
|
+
if r.status_code != 200:
|
|
141
|
+
print('查询失败: %s %s' % (r.status_code, r.text[:120]))
|
|
142
|
+
return 3
|
|
143
|
+
data = r.json()
|
|
144
|
+
tasks = data.get('tasks') if isinstance(data, dict) else None
|
|
145
|
+
if not isinstance(tasks, list):
|
|
146
|
+
print('响应格式异常 (无 tasks 列表)。')
|
|
147
|
+
return 3
|
|
148
|
+
|
|
149
|
+
# ── 归属硬过滤: 只取 assignedTo == 我 ──
|
|
150
|
+
mine = [t for t in tasks if isinstance(t, dict)
|
|
151
|
+
and str(t.get('assignedTo', '')) == my_account]
|
|
152
|
+
skipped = len(tasks) - len(mine)
|
|
153
|
+
print('执行 #%s 共 %d 个任务, 归属我的 %d 个 (跳过队友 %d 个, 不拉)。\n'
|
|
154
|
+
% (eid, len(tasks), len(mine), skipped))
|
|
155
|
+
|
|
156
|
+
if not mine:
|
|
157
|
+
print('没有归属你的任务, 无需同步。')
|
|
158
|
+
return 0
|
|
159
|
+
|
|
160
|
+
# 建立本地 zentao_id 索引 (防重复 create)
|
|
161
|
+
repo_root = get_repo_root()
|
|
162
|
+
existing_zids = {}
|
|
163
|
+
for td, d in _iter_local_tasks(repo_root):
|
|
164
|
+
zid = d.get('zentao_id')
|
|
165
|
+
if zid:
|
|
166
|
+
existing_zids[int(zid)] = td
|
|
167
|
+
|
|
168
|
+
created = skipped_dup = 0
|
|
169
|
+
for t in mine:
|
|
170
|
+
zid = int(t.get('id', 0))
|
|
171
|
+
name = str(t.get('name', ''))[:50]
|
|
172
|
+
status = t.get('status', '')
|
|
173
|
+
if zid in existing_zids:
|
|
174
|
+
print(' ⊘ 跳过 #%s「%s」(本地已有 zentao_id=%s)' % (zid, name, zid))
|
|
175
|
+
skipped_dup += 1
|
|
176
|
+
continue
|
|
177
|
+
if not args.apply:
|
|
178
|
+
print(' + [将创建] #%s「%s」status=%s' % (zid, name, status))
|
|
179
|
+
created += 1
|
|
180
|
+
continue
|
|
181
|
+
# 真写: 直接创建本地任务 (含 zentao_id), 自包含不走 cmd_create
|
|
182
|
+
slug_seed = 'zentao-%s' % zid
|
|
183
|
+
new_dir = _create_local_task(repo_root, 'zentao#%s %s' % (zid, name),
|
|
184
|
+
slug_seed, zid, developer)
|
|
185
|
+
if new_dir:
|
|
186
|
+
print(' ✅ 创建 #%s「%s」并回填 zentao_id -> %s' % (zid, name, new_dir.name))
|
|
187
|
+
created += 1
|
|
188
|
+
else:
|
|
189
|
+
print(' ⚠️ 创建 #%s 失败' % zid)
|
|
190
|
+
|
|
191
|
+
print('\n小结: 创建 %d, 幂等跳过 %d, 队友任务跳过 %d。%s'
|
|
192
|
+
% (created, skipped_dup, skipped,
|
|
193
|
+
'(dry-run, 未真写)' if not args.apply else ''))
|
|
194
|
+
return 0
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
# ============================================================
|
|
198
|
+
# 推送: 本地 → 禅道 (默认 dry-run + 双校验)
|
|
199
|
+
# ============================================================
|
|
200
|
+
|
|
201
|
+
def cmd_push(args, creds, my_account, developer):
|
|
202
|
+
"""推送本地 creator==我 的任务到禅道。默认 dry-run。"""
|
|
203
|
+
print('═══ 推送 本地 → 禅道 (仅 creator=%s, 强制 assignedTo=%s) ═══'
|
|
204
|
+
% (developer, my_account))
|
|
205
|
+
if not args.apply:
|
|
206
|
+
print('[DRY-RUN] 仅预览, 不写禅道。加 --apply 才真推。\n')
|
|
207
|
+
|
|
208
|
+
repo_root = get_repo_root()
|
|
209
|
+
candidates = []
|
|
210
|
+
for td, d in _iter_local_tasks(repo_root):
|
|
211
|
+
# ── 校验 1: 只推我创建的 (复用本地归属) ──
|
|
212
|
+
if (d.get('creator') or '') != developer:
|
|
213
|
+
continue
|
|
214
|
+
candidates.append((td, d))
|
|
215
|
+
|
|
216
|
+
if not candidates:
|
|
217
|
+
print('没有你创建的本地任务可推送。')
|
|
218
|
+
return 0
|
|
219
|
+
|
|
220
|
+
print('本地归属你的任务 %d 个:\n' % len(candidates))
|
|
221
|
+
pushed = skipped = 0
|
|
222
|
+
for td, d in candidates:
|
|
223
|
+
zid = d.get('zentao_id')
|
|
224
|
+
title = str(d.get('title', ''))[:50]
|
|
225
|
+
if zid:
|
|
226
|
+
# 已有 zentao_id → 更新前校验归属一致性
|
|
227
|
+
chk = creds.api('GET', '/tasks/%s' % zid)
|
|
228
|
+
if chk.status_code == 200:
|
|
229
|
+
remote_assign = str(chk.json().get('assignedTo', ''))
|
|
230
|
+
if remote_assign != my_account:
|
|
231
|
+
print(' 🚫 拒绝更新 #%s「%s」: 禅道 assignedTo=%s ≠ 你(%s)'
|
|
232
|
+
' (防误改队友任务)' % (zid, title, remote_assign, my_account))
|
|
233
|
+
skipped += 1
|
|
234
|
+
continue
|
|
235
|
+
if not args.apply:
|
|
236
|
+
print(' ⟳ [将更新] #%s「%s」' % (zid, title))
|
|
237
|
+
pushed += 1
|
|
238
|
+
continue
|
|
239
|
+
payload = {'name': d.get('title', ''), 'assignedTo': my_account}
|
|
240
|
+
if d.get('estimate_hours'):
|
|
241
|
+
payload['estimate'] = d['estimate_hours']
|
|
242
|
+
if d.get('due_date'):
|
|
243
|
+
payload['deadline'] = d['due_date']
|
|
244
|
+
upd = creds.api('PUT', '/tasks/%s' % zid, payload)
|
|
245
|
+
print(' %s 更新 #%s「%s」%s'
|
|
246
|
+
% ('✅' if upd.status_code in (200, 201) else '⚠️', zid, title,
|
|
247
|
+
'' if upd.status_code in (200, 201) else '(%s)' % upd.status_code))
|
|
248
|
+
pushed += 1
|
|
249
|
+
else:
|
|
250
|
+
print(' ⚠️ 查询 #%s 失败 (%s), 跳过' % (zid, chk.status_code))
|
|
251
|
+
skipped += 1
|
|
252
|
+
else:
|
|
253
|
+
# 无 zentao_id → 新建 (强制 assignedTo=我)
|
|
254
|
+
if not args.execution_id:
|
|
255
|
+
print(' ⊙ 跳过「%s」(无 zentao_id, 且未给 --execution_id, 无法新建)'
|
|
256
|
+
% title)
|
|
257
|
+
skipped += 1
|
|
258
|
+
continue
|
|
259
|
+
if not args.apply:
|
|
260
|
+
print(' + [将新建]「%s」→ 执行 #%s' % (title, args.execution_id))
|
|
261
|
+
pushed += 1
|
|
262
|
+
continue
|
|
263
|
+
payload = {'execution': args.execution_id, 'name': d.get('title', ''),
|
|
264
|
+
'assignedTo': my_account, 'type': 'devel', 'pri': 3}
|
|
265
|
+
cre = creds.api('POST', '/tasks', payload)
|
|
266
|
+
if cre.status_code in (200, 201):
|
|
267
|
+
new_zid = cre.json().get('id')
|
|
268
|
+
def _set(d, _z=new_zid):
|
|
269
|
+
d['zentao_id'] = _z
|
|
270
|
+
modify_task_json(td, _set)
|
|
271
|
+
print(' ✅ 新建「%s」→ 禅道 #%s, 回填 zentao_id' % (title, new_zid))
|
|
272
|
+
pushed += 1
|
|
273
|
+
else:
|
|
274
|
+
print(' ⚠️ 新建「%s」失败 (%s)' % (title, cre.status_code))
|
|
275
|
+
skipped += 1
|
|
276
|
+
|
|
277
|
+
print('\n小结: 推送 %d, 跳过 %d。%s'
|
|
278
|
+
% (pushed, skipped, '(dry-run, 未真写)' if not args.apply else ''))
|
|
279
|
+
return 0
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
# ============================================================
|
|
283
|
+
# status 概览
|
|
284
|
+
# ============================================================
|
|
285
|
+
|
|
286
|
+
def cmd_status(args, creds, my_account, developer):
|
|
287
|
+
"""看本地任务与禅道的关联状态概览 (只读)。"""
|
|
288
|
+
repo_root = get_repo_root()
|
|
289
|
+
total = mine = linked = unlinked = 0
|
|
290
|
+
for td, d in _iter_local_tasks(repo_root):
|
|
291
|
+
total += 1
|
|
292
|
+
if (d.get('creator') or '') == developer:
|
|
293
|
+
mine += 1
|
|
294
|
+
if d.get('zentao_id'):
|
|
295
|
+
linked += 1
|
|
296
|
+
else:
|
|
297
|
+
unlinked += 1
|
|
298
|
+
print('═══ 同步状态 (%s / 禅道账号 %s) ═══' % (developer, my_account))
|
|
299
|
+
print('本地任务总数: %d' % total)
|
|
300
|
+
print(' 归属你的: %d' % mine)
|
|
301
|
+
print(' 已关联禅道 (有 zentao_id): %d' % linked)
|
|
302
|
+
print(' 未关联禅道 (推送时需 --execution_id 新建): %d' % unlinked)
|
|
303
|
+
ok, _ = creds.check_intranet()
|
|
304
|
+
print('\n禅道连通: %s (%s)' % ('✅ 可达' if ok else '❌ 不可达', creds.url))
|
|
305
|
+
return 0
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
# ============================================================
|
|
309
|
+
# 主入口
|
|
310
|
+
# ============================================================
|
|
311
|
+
|
|
312
|
+
def main(argv=None):
|
|
313
|
+
p = argparse.ArgumentParser(description='本地任务 ↔ 禅道安全同步',
|
|
314
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
315
|
+
epilog='安全原则: 拉取只取 assignedTo=我; 推送默认 dry-run 且'
|
|
316
|
+
' 仅推 creator=我, 同名任务归属不一致则拒绝。')
|
|
317
|
+
sub = p.add_subparsers(dest='cmd')
|
|
318
|
+
|
|
319
|
+
pp = sub.add_parser('pull', help='拉取禅道任务到本地 (仅 assignedTo=我)')
|
|
320
|
+
pp.add_argument('execution_id', type=int, help='执行ID')
|
|
321
|
+
pp.add_argument('--apply', action='store_true', help='真写 (默认 dry-run 仅预览)')
|
|
322
|
+
|
|
323
|
+
sp = sub.add_parser('push', help='推送本地任务到禅道 (默认 dry-run)')
|
|
324
|
+
sp.add_argument('--execution_id', type=int, help='新建任务落到哪个执行 (无 zentao_id 的任务需要)')
|
|
325
|
+
sp.add_argument('--apply', action='store_true', help='真推 (默认 dry-run 仅预览)')
|
|
326
|
+
|
|
327
|
+
sub.add_parser('status', help='看同步状态概览 (只读)')
|
|
328
|
+
|
|
329
|
+
args = p.parse_args(argv)
|
|
330
|
+
if not args.cmd:
|
|
331
|
+
p.print_help()
|
|
332
|
+
return 0
|
|
333
|
+
|
|
334
|
+
developer = get_developer()
|
|
335
|
+
if not developer:
|
|
336
|
+
print('ERROR: 未设置当前开发者 (无 .developer)。请先 /wl-init。', file=sys.stderr)
|
|
337
|
+
return 4
|
|
338
|
+
|
|
339
|
+
my_account, err = _resolve_my_account(developer)
|
|
340
|
+
if err:
|
|
341
|
+
print('ERROR: %s' % err, file=sys.stderr)
|
|
342
|
+
return 3
|
|
343
|
+
|
|
344
|
+
creds = _zentao_creds()
|
|
345
|
+
if not creds:
|
|
346
|
+
print('ERROR: 禅道凭据未配置。请在 .qoder/config.yaml 的 zentao: 段填 url/user/password。',
|
|
347
|
+
file=sys.stderr)
|
|
348
|
+
return 3
|
|
349
|
+
|
|
350
|
+
# 连通性预检
|
|
351
|
+
ok, msg = creds.check_intranet()
|
|
352
|
+
if not ok:
|
|
353
|
+
print('ERROR: %s' % msg, file=sys.stderr)
|
|
354
|
+
return 5
|
|
355
|
+
|
|
356
|
+
try:
|
|
357
|
+
if args.cmd == 'pull':
|
|
358
|
+
return cmd_pull(args, creds, my_account)
|
|
359
|
+
elif args.cmd == 'push':
|
|
360
|
+
return cmd_push(args, creds, my_account, developer)
|
|
361
|
+
elif args.cmd == 'status':
|
|
362
|
+
return cmd_status(args, creds, my_account, developer)
|
|
363
|
+
except RuntimeError as e:
|
|
364
|
+
print('ERROR: %s' % e, file=sys.stderr)
|
|
365
|
+
return 3
|
|
366
|
+
return 0
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
if __name__ == '__main__':
|
|
370
|
+
sys.exit(main())
|
|
File without changes
|
|
@@ -4,23 +4,23 @@ bootstrap.py - 统一 CLI 启动样板 (DRY) + 子目录化路径自举 (v3.0)
|
|
|
4
4
|
|
|
5
5
|
治"70处 sys.path + 50处 reconfigure" + 子目录化后的路径定位。
|
|
6
6
|
|
|
7
|
-
子目录化后, 脚本在 scripts/kg/kg.py, 它的 dirname(__file__)=scripts/kg/,
|
|
8
|
-
直接 sys.path.insert(scripts/kg/) 后 `from
|
|
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
9
|
|
|
10
10
|
本模块的 setup() 解决: 无论脚本在 scripts/ 根还是 scripts/某子包/,
|
|
11
|
-
都能正确定位 scripts/ 根并注入, 让 `from
|
|
11
|
+
都能正确定位 scripts/ 根并注入, 让 `from foundation.x` 和跨包 import 都能用。
|
|
12
12
|
|
|
13
|
-
核心: _find_scripts_root() 向上找含 '
|
|
13
|
+
核心: _find_scripts_root() 向上找含 'foundation/' 子目录的那个目录 = scripts/ 根。
|
|
14
14
|
|
|
15
15
|
Usage (两种姿势):
|
|
16
16
|
# 姿势1: 子包脚本开头 (推荐)
|
|
17
17
|
import os, sys
|
|
18
18
|
_S = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # 子包→scripts
|
|
19
19
|
if _S not in sys.path: sys.path.insert(0, _S)
|
|
20
|
-
from
|
|
20
|
+
from bootstrap import setup; setup()
|
|
21
21
|
|
|
22
22
|
# 姿势2: scripts/ 根脚本 (老姿势仍可用)
|
|
23
|
-
from
|
|
23
|
+
from bootstrap import setup; setup()
|
|
24
24
|
"""
|
|
25
25
|
|
|
26
26
|
import os
|
|
@@ -40,7 +40,7 @@ def find_scripts_root(start=None):
|
|
|
40
40
|
cur = start
|
|
41
41
|
for _ in range(10):
|
|
42
42
|
# scripts/ 根的标志: 含 common/bootstrap.py
|
|
43
|
-
if os.path.isfile(os.path.join(cur, "
|
|
43
|
+
if os.path.isfile(os.path.join(cur, "foundation", "bootstrap.py")):
|
|
44
44
|
return cur
|
|
45
45
|
parent = os.path.dirname(cur)
|
|
46
46
|
if parent == cur:
|
|
@@ -62,7 +62,7 @@ def find_scripts_root(start=None):
|
|
|
62
62
|
def ensure_scripts_path(start=None):
|
|
63
63
|
"""确保 scripts/ 根在 sys.path 最前。幂等。
|
|
64
64
|
|
|
65
|
-
子包脚本调用此函数后, `from
|
|
65
|
+
子包脚本调用此函数后, `from foundation.x` 即可用。
|
|
66
66
|
"""
|
|
67
67
|
root = find_scripts_root(start)
|
|
68
68
|
if root not in sys.path:
|
|
@@ -107,7 +107,7 @@ def setup():
|
|
|
107
107
|
scripts_dir = ensure_scripts_root = find_scripts_root()
|
|
108
108
|
if scripts_dir not in sys.path:
|
|
109
109
|
sys.path.insert(0, scripts_dir)
|
|
110
|
-
common_dir = os.path.join(scripts_dir, "
|
|
110
|
+
common_dir = os.path.join(scripts_dir, "foundation")
|
|
111
111
|
if os.path.isdir(common_dir) and common_dir not in sys.path:
|
|
112
112
|
sys.path.insert(0, common_dir)
|
|
113
113
|
# 注入所有子包目录 (kg/build/mcp/test/setup/carriers/task/search/report)
|
|
@@ -142,4 +142,4 @@ def setup():
|
|
|
142
142
|
|
|
143
143
|
# 模块级常量
|
|
144
144
|
SCRIPTS_DIR = find_scripts_root()
|
|
145
|
-
REPO_ROOT = os.path.dirname(os.path.dirname(SCRIPTS_DIR))
|
|
145
|
+
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(SCRIPTS_DIR))))
|
|
File without changes
|
|
Binary file
|
|
@@ -12,7 +12,7 @@ config.py - Config 单例 (Singleton 模式, 治"4份 load_config + 明文凭据
|
|
|
12
12
|
- 凭据从代码移除: mysql/zentao 密码只在 config.yaml, 不在源码默认值
|
|
13
13
|
|
|
14
14
|
Usage:
|
|
15
|
-
from
|
|
15
|
+
from foundation.core.config import Config, SECTIONS
|
|
16
16
|
cfg = Config.load() # 单例, 全进程共享
|
|
17
17
|
feishu = cfg.section('feishu') # {'enabled': false, ...}
|
|
18
18
|
mysql = cfg.mysql # 类型化便捷访问
|