@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
|
@@ -28,15 +28,30 @@ from pathlib import Path
|
|
|
28
28
|
from typing import Any
|
|
29
29
|
|
|
30
30
|
# 支持的 AI 平台及其会话环境变量
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
31
|
+
# -----------------------------------------------------------------------------
|
|
32
|
+
# 单一事实源: nexus.registry.HOST_TABLE(宿主能力抽象层)。
|
|
33
|
+
# 本表从 HOST_TABLE 派生,保持 tuple[tuple[str, tuple[str, ...]]] 格式兼容。
|
|
34
|
+
# 新增宿主只需改 nexus.HOST_TABLE 一处,active_task 自动跟随。
|
|
35
|
+
# nexus 不可用时回退到内联定义(向后兼容,零破坏)。
|
|
36
|
+
# -----------------------------------------------------------------------------
|
|
37
|
+
try:
|
|
38
|
+
from capability.registry import HOST_TABLE as _HOST_TABLE
|
|
39
|
+
|
|
40
|
+
_ENV_SESSION_KEYS: tuple[tuple[str, tuple[str, ...]], ...] = tuple(
|
|
41
|
+
(name, tuple(info.get("detect", ())))
|
|
42
|
+
for name, info in _HOST_TABLE.items()
|
|
43
|
+
if name != "cli" and info.get("detect")
|
|
44
|
+
)
|
|
45
|
+
except ImportError:
|
|
46
|
+
_ENV_SESSION_KEYS = (
|
|
47
|
+
("qoderwork", ("QODER_SESSION_ID",)),
|
|
48
|
+
("codex", ("CODEX_SESSION_ID", "CODEX_THREAD_ID")),
|
|
49
|
+
("claude", ("CLAUDE_SESSION_ID", "CLAUDE_CODE_SESSION_ID")),
|
|
50
|
+
("cursor", ("CURSOR_SESSION_ID",)),
|
|
51
|
+
("opencode", ("OPENCODE_SESSION_ID", "OPENCODE_RUN_ID")),
|
|
52
|
+
("gemini", ("GEMINI_SESSION_ID",)),
|
|
53
|
+
("copilot", ("COPILOT_SESSION_ID",)),
|
|
54
|
+
)
|
|
40
55
|
|
|
41
56
|
|
|
42
57
|
@dataclass(frozen=True)
|
|
@@ -127,7 +142,7 @@ def resolve_active_task(repo_root: Path | None = None) -> ActiveTask:
|
|
|
127
142
|
ActiveTask 对象。
|
|
128
143
|
"""
|
|
129
144
|
if repo_root is None:
|
|
130
|
-
from
|
|
145
|
+
from foundation.core.paths import get_repo_root
|
|
131
146
|
repo_root = get_repo_root()
|
|
132
147
|
|
|
133
148
|
# 1. 尝试会话级
|
|
@@ -147,7 +162,7 @@ def resolve_active_task(repo_root: Path | None = None) -> ActiveTask:
|
|
|
147
162
|
pass
|
|
148
163
|
|
|
149
164
|
# 2. 后备: 全局文件
|
|
150
|
-
from
|
|
165
|
+
from foundation.core.paths import get_current_task
|
|
151
166
|
global_task = get_current_task(repo_root)
|
|
152
167
|
if global_task:
|
|
153
168
|
return ActiveTask(
|
|
@@ -170,13 +185,13 @@ def set_active_task(task_path: str, repo_root: Path | None = None) -> ActiveTask
|
|
|
170
185
|
ActiveTask 对象, 无会话身份则返回 None。
|
|
171
186
|
"""
|
|
172
187
|
if repo_root is None:
|
|
173
|
-
from
|
|
188
|
+
from foundation.core.paths import get_repo_root
|
|
174
189
|
repo_root = get_repo_root()
|
|
175
190
|
|
|
176
191
|
session_key = resolve_context_key()
|
|
177
192
|
if not session_key:
|
|
178
193
|
# 无会话身份, 使用全局后备
|
|
179
|
-
from
|
|
194
|
+
from foundation.core.paths import set_current_task
|
|
180
195
|
set_current_task(task_path, repo_root)
|
|
181
196
|
return ActiveTask(
|
|
182
197
|
task_path=task_path,
|
|
@@ -208,7 +223,7 @@ def clear_active_task(repo_root: Path | None = None) -> ActiveTask:
|
|
|
208
223
|
清除前的 ActiveTask 状态。
|
|
209
224
|
"""
|
|
210
225
|
if repo_root is None:
|
|
211
|
-
from
|
|
226
|
+
from foundation.core.paths import get_repo_root
|
|
212
227
|
repo_root = get_repo_root()
|
|
213
228
|
|
|
214
229
|
# 获取清除前的状态
|
|
@@ -224,7 +239,7 @@ def clear_active_task(repo_root: Path | None = None) -> ActiveTask:
|
|
|
224
239
|
pass
|
|
225
240
|
|
|
226
241
|
# 也清除全局后备
|
|
227
|
-
from
|
|
242
|
+
from foundation.core.paths import set_current_task
|
|
228
243
|
set_current_task(None, repo_root)
|
|
229
244
|
|
|
230
245
|
return active
|
|
@@ -64,14 +64,14 @@ def _load_config():
|
|
|
64
64
|
config = {}
|
|
65
65
|
try:
|
|
66
66
|
# 优先: Config 单例 (mtime 缓存, 避免每次重读)
|
|
67
|
-
from .config import Config, SECTIONS
|
|
67
|
+
from foundation.core.config import Config, SECTIONS
|
|
68
68
|
config = Config.load().section(SECTIONS.FEISHU, {})
|
|
69
69
|
except Exception:
|
|
70
70
|
pass
|
|
71
71
|
if not config:
|
|
72
72
|
# 回退: 简单文本解析 (PyYAML 不可用时)
|
|
73
73
|
try:
|
|
74
|
-
from .paths import get_repo_root
|
|
74
|
+
from foundation.core.paths import get_repo_root
|
|
75
75
|
cfg_path = os.path.join(str(get_repo_root()), '.qoder', 'config.yaml')
|
|
76
76
|
in_feishu = False
|
|
77
77
|
with open(cfg_path, encoding='utf-8') as f:
|
|
@@ -12,7 +12,7 @@ of these maps in other scripts - import from here.
|
|
|
12
12
|
# 性能优化 A6: 从 40 词扩展到 ~120 词, 覆盖库存/采购/供应商/客户/财务/工单等通用业务域
|
|
13
13
|
CN_MAP = {
|
|
14
14
|
# 考勤/人事
|
|
15
|
-
'考勤': 'attendance
|
|
15
|
+
'考勤': 'attendance', '打卡': 'clock', '签到': 'sign',
|
|
16
16
|
'薪资': 'salary', '工资': 'salary', '请假': 'leave', '休假': 'leave',
|
|
17
17
|
'加班': 'overtime', '员工': 'employee staff', '用户': 'user',
|
|
18
18
|
'组织': 'org department', '部门': 'department', '岗位': 'position role',
|
|
@@ -108,8 +108,8 @@ CN_MAP = {
|
|
|
108
108
|
# so building and querying use the same semantics.
|
|
109
109
|
BUSINESS_PATH_MAP = {
|
|
110
110
|
'保险': ['insurance', 'Insurance', 'ins-', '/ins/', 'vehlife/insurance'],
|
|
111
|
-
'考勤': ['attendance', 'Attendance', '
|
|
112
|
-
'打卡': ['clock', 'Clock', 'photoClock', 'PhotoClock'],
|
|
111
|
+
'考勤': ['attendance', 'Attendance', 'Attend', 'timeManage', 'TimeWork'],
|
|
112
|
+
'打卡': ['clock', 'Clock', 'photoClock', 'PhotoClock', 'time/clock', 'TimeClock'],
|
|
113
113
|
'车辆': ['vehicle', 'Vehicle', 'veh', 'vehlife', '/veh/'],
|
|
114
114
|
'品质': ['quality', 'Quality', 'QualityInspection', 'patrol', 'Patrol'],
|
|
115
115
|
'巡查': ['inspection', 'Inspection', 'patrol', 'Patrol'],
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
zentao_client.py —— 禅道 HTTP 客户端(唯一真相源)。
|
|
4
|
+
|
|
5
|
+
消除重复: zentao_sync.py (CLI 批量同步) 和 zentao_mcp_server.py (MCP server)
|
|
6
|
+
原本各自实现了一份禅道 HTTP 层(内网探测/登录/token缓存/API/401重登),
|
|
7
|
+
现统一收敛到本类。
|
|
8
|
+
|
|
9
|
+
设计:
|
|
10
|
+
- ZentaoClient(url, user, password) —— 显式构造
|
|
11
|
+
- ZentaoClient.from_env() —— 从 ZENTAO_URL/USER/PASSWORD 环境变量 (mcp_launcher 注入)
|
|
12
|
+
- ZentaoClient.from_config() —— 从 .qoder/config.yaml 的 zentao 段 (CLI 用)
|
|
13
|
+
- 凭据不全时工厂返回 None(调用方自行降级/报错)
|
|
14
|
+
|
|
15
|
+
client.check_intranet() -> (ok, msg) 带缓存,避免连续 ping
|
|
16
|
+
client.api(method, path, data) -> Response 内含 401 自动重登
|
|
17
|
+
|
|
18
|
+
与 nexus 的关系:
|
|
19
|
+
禅道 MCP server (zentao_mcp_server.py) 是 nexus L1 层的 MCP 资产。
|
|
20
|
+
本 client 是其 HTTP 底座;未来 McpCap 实现后,工作流统一走
|
|
21
|
+
nexus.resolve().mcp.call("list_execution_tasks"),本 client 仍是底层实现。
|
|
22
|
+
"""
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import os
|
|
26
|
+
import socket
|
|
27
|
+
from typing import Optional
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ZentaoClient:
|
|
31
|
+
"""禅道 REST API 客户端(登录/token/401重登/内网探测)。
|
|
32
|
+
|
|
33
|
+
Attributes:
|
|
34
|
+
url: 禅道根地址(构造时已去尾斜杠)。
|
|
35
|
+
user/password: 账号密码。
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(self, url: str, user: str, password: str, intranet_ttl: int = 60):
|
|
39
|
+
self.url = (url or '').rstrip('/')
|
|
40
|
+
self.user = (user or '').strip()
|
|
41
|
+
self.password = password or ''
|
|
42
|
+
self._token: Optional[str] = None
|
|
43
|
+
# 内网可达性缓存 [timestamp, ok, msg],list 原地写避免 global 声明
|
|
44
|
+
self._intranet_cache: list = []
|
|
45
|
+
self._intranet_ttl = intranet_ttl
|
|
46
|
+
|
|
47
|
+
# ------------------------------------------------------------------
|
|
48
|
+
# 工厂
|
|
49
|
+
# ------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def from_env(cls) -> Optional['ZentaoClient']:
|
|
53
|
+
"""从环境变量构造(mcp_launcher 注入)。凭据不全返回 None。"""
|
|
54
|
+
url = os.environ.get('ZENTAO_URL', '').strip()
|
|
55
|
+
user = os.environ.get('ZENTAO_USER', '').strip()
|
|
56
|
+
pwd = os.environ.get('ZENTAO_PASSWORD', '')
|
|
57
|
+
if not url or not user or not pwd:
|
|
58
|
+
return None
|
|
59
|
+
return cls(url, user, pwd)
|
|
60
|
+
|
|
61
|
+
@classmethod
|
|
62
|
+
def from_config(cls) -> Optional['ZentaoClient']:
|
|
63
|
+
"""从 .qoder/config.yaml 的 zentao 段构造(CLI 用)。凭据不全返回 None。"""
|
|
64
|
+
try:
|
|
65
|
+
from foundation.core.config import load_config_section, SECTIONS
|
|
66
|
+
cfg = load_config_section(SECTIONS.ZENTAO)
|
|
67
|
+
except Exception:
|
|
68
|
+
return None
|
|
69
|
+
url = (cfg.get('url') or '').strip()
|
|
70
|
+
user = (cfg.get('user') or '').strip()
|
|
71
|
+
pwd = (cfg.get('password') or '')
|
|
72
|
+
if not url or not user or not pwd:
|
|
73
|
+
return None
|
|
74
|
+
return cls(url, user, pwd)
|
|
75
|
+
|
|
76
|
+
# ------------------------------------------------------------------
|
|
77
|
+
# 校验
|
|
78
|
+
# ------------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
def credentials_ok(self) -> tuple[bool, str]:
|
|
81
|
+
"""凭据完整性校验。返回 (ok, msg)。"""
|
|
82
|
+
missing = []
|
|
83
|
+
if not self.url:
|
|
84
|
+
missing.append('url')
|
|
85
|
+
if not self.user:
|
|
86
|
+
missing.append('user')
|
|
87
|
+
if not self.password:
|
|
88
|
+
missing.append('password')
|
|
89
|
+
if missing:
|
|
90
|
+
return False, '禅道凭据未配置 (缺 %s)。请在 .qoder/config.yaml 的 zentao: 段填写。' % '/'.join(missing)
|
|
91
|
+
return True, ''
|
|
92
|
+
|
|
93
|
+
# ------------------------------------------------------------------
|
|
94
|
+
# 内网探测
|
|
95
|
+
# ------------------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
def _ping_target(self) -> tuple[str, int]:
|
|
98
|
+
"""从 url 解析 (host, port)。"""
|
|
99
|
+
host = self.url.replace('http://', '').replace('https://', '').split('/')[0]
|
|
100
|
+
try:
|
|
101
|
+
port = 80
|
|
102
|
+
if self.url.count(':') >= 2:
|
|
103
|
+
port = int(self.url.split(':')[2].split('/')[0])
|
|
104
|
+
except (ValueError, IndexError):
|
|
105
|
+
port = 80
|
|
106
|
+
return host, port
|
|
107
|
+
|
|
108
|
+
def check_intranet(self) -> tuple[bool, str]:
|
|
109
|
+
"""内网连通性(带 TTL 缓存,失败不缓存)。
|
|
110
|
+
|
|
111
|
+
多工具连续调用时只 ping 一次。失败不缓存,便于网络恢复后立即重试。
|
|
112
|
+
"""
|
|
113
|
+
import time
|
|
114
|
+
now = time.time()
|
|
115
|
+
cached = self._intranet_cache
|
|
116
|
+
if cached and now - cached[0] < self._intranet_ttl and cached[1]:
|
|
117
|
+
return cached[1], cached[2]
|
|
118
|
+
host, port = self._ping_target()
|
|
119
|
+
try:
|
|
120
|
+
with socket.create_connection((host, port), timeout=3):
|
|
121
|
+
self._intranet_cache[:] = [now, True, '']
|
|
122
|
+
return True, ''
|
|
123
|
+
except Exception as e:
|
|
124
|
+
return False, '无法连接禅道 (%s): %s。请确认已连内网。' % (self.url, str(e)[:60])
|
|
125
|
+
|
|
126
|
+
# ------------------------------------------------------------------
|
|
127
|
+
# HTTP
|
|
128
|
+
# ------------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
@staticmethod
|
|
131
|
+
def _ensure_requests():
|
|
132
|
+
"""延迟导入 requests(未装时报清晰错误,而非启动即崩)。"""
|
|
133
|
+
try:
|
|
134
|
+
import requests
|
|
135
|
+
return requests
|
|
136
|
+
except ImportError:
|
|
137
|
+
raise RuntimeError('缺少 requests 库。请运行: pip install requests')
|
|
138
|
+
|
|
139
|
+
def login(self) -> str:
|
|
140
|
+
"""登录拿 token(进程内缓存)。"""
|
|
141
|
+
if self._token:
|
|
142
|
+
return self._token
|
|
143
|
+
requests = self._ensure_requests()
|
|
144
|
+
r = requests.post(self.url + '/api.php/v1/tokens',
|
|
145
|
+
json={'account': self.user, 'password': self.password},
|
|
146
|
+
timeout=10)
|
|
147
|
+
if r.status_code in (200, 201):
|
|
148
|
+
self._token = r.json().get('token')
|
|
149
|
+
return self._token
|
|
150
|
+
raise RuntimeError('禅道登录失败: %s %s' % (r.status_code, r.text[:100]))
|
|
151
|
+
|
|
152
|
+
def api(self, method: str, path: str, data: dict | None = None):
|
|
153
|
+
"""调禅道 REST API。401 自动重登一次。
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
method: GET/POST/PUT/DELETE
|
|
157
|
+
path: /api.php/v1 之后的路径(如 /executions/12/tasks)
|
|
158
|
+
data: GET 时作 query params,POST/PUT 时作 json body
|
|
159
|
+
Returns:
|
|
160
|
+
requests.Response
|
|
161
|
+
"""
|
|
162
|
+
requests = self._ensure_requests()
|
|
163
|
+
token = self.login()
|
|
164
|
+
full = self.url + '/api.php/v1' + path
|
|
165
|
+
kwargs = dict(
|
|
166
|
+
headers={'Token': token},
|
|
167
|
+
json=data if method in ('POST', 'PUT') else None,
|
|
168
|
+
params=data if method == 'GET' else None,
|
|
169
|
+
timeout=15,
|
|
170
|
+
)
|
|
171
|
+
r = requests.request(method, full, **kwargs)
|
|
172
|
+
if r.status_code == 401:
|
|
173
|
+
self._token = None
|
|
174
|
+
token = self.login()
|
|
175
|
+
kwargs['headers'] = {'Token': token}
|
|
176
|
+
r = requests.request(method, full, **kwargs)
|
|
177
|
+
return r
|
|
178
|
+
|
|
179
|
+
def upload_file(self, file_path: str, title: str = '', extra_fields: dict | None = None):
|
|
180
|
+
"""上传文件到禅道 (multipart/form-data, POST /api.php/v1/files)。
|
|
181
|
+
|
|
182
|
+
禅道文件接口需 multipart 而非 JSON, api() 不支持, 故单独实现。
|
|
183
|
+
用于: 上传原型图 HTML / PRD 附件到需求。
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
file_path: 本地文件绝对路径
|
|
187
|
+
title: 文件标题 (可选, 默认取文件名)
|
|
188
|
+
extra_fields: 额外表单字段 (如 {'objectType':'story','objectID':812} 关联到对象)
|
|
189
|
+
Returns:
|
|
190
|
+
requests.Response
|
|
191
|
+
"""
|
|
192
|
+
import os
|
|
193
|
+
requests = self._ensure_requests()
|
|
194
|
+
token = self.login()
|
|
195
|
+
full = self.url + '/api.php/v1/files'
|
|
196
|
+
fname = os.path.basename(file_path)
|
|
197
|
+
title = title or fname
|
|
198
|
+
fields = {'title': title}
|
|
199
|
+
if extra_fields:
|
|
200
|
+
fields.update(extra_fields)
|
|
201
|
+
with open(file_path, 'rb') as f:
|
|
202
|
+
files = {'file': (fname, f, 'application/octet-stream')}
|
|
203
|
+
r = requests.post(full, headers={'Token': token},
|
|
204
|
+
data=fields, files=files, timeout=30)
|
|
205
|
+
if r.status_code == 401:
|
|
206
|
+
self._token = None
|
|
207
|
+
token = self.login()
|
|
208
|
+
with open(file_path, 'rb') as f:
|
|
209
|
+
files = {'file': (fname, f, 'application/octet-stream')}
|
|
210
|
+
r = requests.post(full, headers={'Token': token},
|
|
211
|
+
data=fields, files=files, timeout=30)
|
|
212
|
+
return r
|
|
213
|
+
|
|
214
|
+
@staticmethod
|
|
215
|
+
def err_text(r) -> str:
|
|
216
|
+
"""从 response 提取错误文本(容错)。"""
|
|
217
|
+
try:
|
|
218
|
+
return r.json().get('error', r.text[:120])
|
|
219
|
+
except Exception:
|
|
220
|
+
return r.text[:120]
|
|
File without changes
|
|
@@ -36,7 +36,7 @@ if _THIS_DIR not in sys.path:
|
|
|
36
36
|
try:
|
|
37
37
|
from .atomicio import atomic_write_json, safe_read_json
|
|
38
38
|
from .filelock import FileLock, LockTimeoutError
|
|
39
|
-
from .paths import get_repo_root
|
|
39
|
+
from foundation.core.paths import get_repo_root
|
|
40
40
|
except ImportError:
|
|
41
41
|
# 被当作脚本直接运行时
|
|
42
42
|
from atomicio import atomic_write_json, safe_read_json
|
|
File without changes
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
duckdb_conn —— DuckDB 连接基础设施(L0 基础设施层)。
|
|
4
|
+
|
|
5
|
+
设计模式: Utility(纯连接管理,无领域语义)
|
|
6
|
+
|
|
7
|
+
职责:打开/关闭 kg.duckdb 连接。不知道表结构,不知道业务逻辑。
|
|
8
|
+
被 L2(nexus)和 L3(kg/learning)共用,避免 L2 反向依赖 L3。
|
|
9
|
+
|
|
10
|
+
init_schema 等领域 DDL 留在各域(kg_duckdb.py 定义图谱表,
|
|
11
|
+
nexus/adapters/mcp.py 定义自己的 learning_events 表)。
|
|
12
|
+
"""
|
|
13
|
+
import os
|
|
14
|
+
|
|
15
|
+
from foundation.core.paths import DATA_INDEX_DIR
|
|
16
|
+
|
|
17
|
+
DB_PATH = str(DATA_INDEX_DIR / 'kg.duckdb')
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_db():
|
|
21
|
+
"""Open DuckDB read-write connection (creates file if not exists)."""
|
|
22
|
+
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
|
|
23
|
+
import duckdb
|
|
24
|
+
return duckdb.connect(DB_PATH)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_db_readonly():
|
|
28
|
+
"""Open read-only DuckDB connection. Returns None if file missing.
|
|
29
|
+
|
|
30
|
+
DuckDB 不允许对同一文件混用 read_only 和 read_write 连接,
|
|
31
|
+
所以只读路径必须全程用 read_only。
|
|
32
|
+
"""
|
|
33
|
+
import duckdb
|
|
34
|
+
if not os.path.isfile(DB_PATH):
|
|
35
|
+
return None
|
|
36
|
+
try:
|
|
37
|
+
return duckdb.connect(DB_PATH, read_only=True)
|
|
38
|
+
except Exception:
|
|
39
|
+
return None
|
|
@@ -21,7 +21,7 @@ mcp_base.py - MCP Server 基类 (Template Method 模式)
|
|
|
21
21
|
- on_tool_error(e) -> (text, is_error): 自定义异常分类 (默认通用)
|
|
22
22
|
|
|
23
23
|
Usage (子类示例):
|
|
24
|
-
from
|
|
24
|
+
from foundation.protocol.mcp_base import BaseMCPServer
|
|
25
25
|
|
|
26
26
|
class ZentaoMCPServer(BaseMCPServer):
|
|
27
27
|
SERVER_NAME = "qoder-zentao"
|
|
File without changes
|
|
@@ -26,6 +26,7 @@ eval_prd.py 的 main() 也改为调用本模块。
|
|
|
26
26
|
|
|
27
27
|
import os
|
|
28
28
|
import sys
|
|
29
|
+
import re
|
|
29
30
|
|
|
30
31
|
_THIS_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
31
32
|
_SCRIPTS_DIR = os.path.dirname(_THIS_DIR)
|
|
@@ -33,7 +34,7 @@ if _SCRIPTS_DIR not in sys.path:
|
|
|
33
34
|
sys.path.insert(0, _SCRIPTS_DIR)
|
|
34
35
|
|
|
35
36
|
# 复用 eval_prd.py 的评分函数 (不重复实现)
|
|
36
|
-
from eval_prd import (
|
|
37
|
+
from validation.metrics.eval_prd import (
|
|
37
38
|
score_reality, score_style, score_template, read_text,
|
|
38
39
|
FULL_SECTIONS, QUICK_SECTIONS,
|
|
39
40
|
)
|
|
@@ -75,6 +76,16 @@ def evaluate(prd_path, html_path=None):
|
|
|
75
76
|
try:
|
|
76
77
|
r2, msg2, miss2 = score_style(read_text(html_path))
|
|
77
78
|
a2 = round(r2 * 30)
|
|
79
|
+
# v3.1 来源锚定检查: HTML 必须含 @prototype-source 或 x-source-pages 注释
|
|
80
|
+
# 没有 = AI 没做 Step 2 真实页面锚定, 直接从模板手抄 → 扣 5 分
|
|
81
|
+
html_text = read_text(html_path)
|
|
82
|
+
has_source = bool(re.search(r'@prototype-source:|x-source-pages', html_text))
|
|
83
|
+
if not has_source:
|
|
84
|
+
a2 = max(0, a2 - 5)
|
|
85
|
+
msg2 = (msg2 or '') + '; 缺来源锚定(@prototype-source 注释), 扣 5 分'
|
|
86
|
+
if miss2 is None:
|
|
87
|
+
miss2 = []
|
|
88
|
+
miss2 = list(miss2) + ['缺 @prototype-source 注释']
|
|
78
89
|
except Exception as e:
|
|
79
90
|
r2, msg2, miss2 = 0.0, "评估异常: %s" % e, []
|
|
80
91
|
a2 = 0
|
|
@@ -35,17 +35,34 @@ def _run_pip(args, index_url=None, timeout=MIRROR_TIMEOUT):
|
|
|
35
35
|
"""跑一次 pip, 返回 (returncode, stdout, stderr)。
|
|
36
36
|
index_url 非 None 时加 -i 指定镜像源。
|
|
37
37
|
用 trusted-host 避免某些镜像的 SSL 证书警告阻断。
|
|
38
|
+
|
|
39
|
+
⚠ 不用 subprocess.run / communicate:
|
|
40
|
+
Anaconda Python 3.9 + Windows 下, subprocess 的 communicate() 内部 join 读子进程
|
|
41
|
+
输出的线程时, 会触发 "PyEval_SaveThread: GIL released" 致命崩溃
|
|
42
|
+
(pip 跑 tree-sitter/playwright/ddddocr 这类大包时尤其稳定复现)。
|
|
43
|
+
50 人不同环境都要跑这步, 必须跨版本稳。
|
|
44
|
+
|
|
45
|
+
解法: Popen + wait(), stdout/stderr 全丢 DEVNULL。
|
|
46
|
+
pip 的啰嗦输出对我们无用 (成功与否以 _verify_packages 的 import 验证为准),
|
|
47
|
+
丢了反而避免大输出撑爆管道 + 触发 GIL 崩溃。超时用独立的计时线程判, 不走 communicate。
|
|
38
48
|
"""
|
|
39
|
-
cmd = [sys.executable, '-m', 'pip', 'install'
|
|
49
|
+
cmd = [sys.executable, '-m', 'pip', 'install', '--no-input',
|
|
50
|
+
'--progress-bar', 'off'] + args
|
|
40
51
|
if index_url:
|
|
41
52
|
cmd += ['-i', index_url,
|
|
42
53
|
'--trusted-host', _host_of(index_url)]
|
|
43
54
|
try:
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
55
|
+
proc = subprocess.Popen(cmd, stdout=subprocess.DEVNULL,
|
|
56
|
+
stderr=subprocess.DEVNULL)
|
|
57
|
+
try:
|
|
58
|
+
rc = proc.wait(timeout=timeout)
|
|
59
|
+
return rc, '', ''
|
|
60
|
+
except subprocess.TimeoutExpired:
|
|
61
|
+
try:
|
|
62
|
+
proc.kill()
|
|
63
|
+
except Exception:
|
|
64
|
+
pass
|
|
65
|
+
return 1, '', '安装超时 (%ds)' % timeout
|
|
49
66
|
except Exception as e:
|
|
50
67
|
return 1, '', str(e)
|
|
51
68
|
|
|
@@ -26,7 +26,7 @@ def _platform_explicitly_passed(argv=None):
|
|
|
26
26
|
return '--platform' in argv
|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
def assert_platform_selected(skill='wl-prd
|
|
29
|
+
def assert_platform_selected(skill='wl-prd', argv=None):
|
|
30
30
|
"""硬墙: 没显式传 --platform 就调用 PRD 链路脚本 = 违规。
|
|
31
31
|
|
|
32
32
|
1. best-effort 上报 rule_violation 事件 (不阻塞, 失败静默)
|
|
File without changes
|