@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,159 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
guard.py - 角色能力边界守卫 (解耦的执行点)
|
|
4
|
+
|
|
5
|
+
把角色从"标签"变成"边界"。各角色在能力边界内自治, 互不越界 = 解耦。
|
|
6
|
+
|
|
7
|
+
现状: role.py 只查询角色名, config.yaml 的 permissions 声明了但从不强制。
|
|
8
|
+
本次: guard.py 在"产出动作前"校验角色能否产出该契约类型, 越权则降级 (不报错)。
|
|
9
|
+
|
|
10
|
+
设计原则 (与方案一致):
|
|
11
|
+
- 降级而非阻塞: 越权不报错/不崩溃, 而是降级 (转给有权限的人 / 标记待确认)
|
|
12
|
+
这与契约层的"降级不停摆"哲学一致
|
|
13
|
+
- 软边界: 默认不强制 (ensure=False), 调用方显式 ensure=True 才硬拦
|
|
14
|
+
- 可观测: 每次越权都留痕 (返回 Result + hint), 让 AI/用户知道发生了什么
|
|
15
|
+
|
|
16
|
+
核心 API:
|
|
17
|
+
check_produce(contract_type, role=None, ensure=False) -> Result
|
|
18
|
+
检查当前角色能否产出某契约
|
|
19
|
+
ensure=False (默认): 越权返回 WARN (降级提示, 不阻塞)
|
|
20
|
+
ensure=True: 越权返回 FAIL (硬拦, 给破坏性操作用)
|
|
21
|
+
current_role() -> str | None
|
|
22
|
+
当前角色名 (identity.get_role 的便捷封装)
|
|
23
|
+
|
|
24
|
+
Usage:
|
|
25
|
+
from foundation.identity.guard import check_produce
|
|
26
|
+
r = check_produce('unit-test')
|
|
27
|
+
if not r.ok:
|
|
28
|
+
print(r.hint) # "你的角色 pm 不能产出 unit-test, 需 dev/test 角色"
|
|
29
|
+
return # 降级: 不执行产出
|
|
30
|
+
# ... 继续产出 unit-test
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
import os
|
|
34
|
+
import sys
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
from foundation.data.result import Result, CODE
|
|
38
|
+
from .identity import get_role, can_produce, can_consume, get_role_config
|
|
39
|
+
except ImportError:
|
|
40
|
+
_COMMON_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
41
|
+
if _COMMON_DIR not in sys.path:
|
|
42
|
+
sys.path.insert(0, _COMMON_DIR)
|
|
43
|
+
from result import Result, CODE
|
|
44
|
+
from identity import get_role, can_produce, can_consume, get_role_config
|
|
45
|
+
|
|
46
|
+
__all__ = [
|
|
47
|
+
"check_produce",
|
|
48
|
+
"check_consume",
|
|
49
|
+
"current_role",
|
|
50
|
+
"suggest_role_for",
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
# 角色中文名映射 (给提示文案用)
|
|
54
|
+
_ROLE_CN = {
|
|
55
|
+
"pm": "产品经理",
|
|
56
|
+
"design": "设计师",
|
|
57
|
+
"dev": "开发",
|
|
58
|
+
"test": "测试",
|
|
59
|
+
"admin": "管理员",
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def current_role() -> str:
|
|
64
|
+
"""当前角色名。未初始化/未注册 → 回退 'pm' (软引导, 与 role.py 一致)。"""
|
|
65
|
+
return get_role() or "pm"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _role_cn(role: str) -> str:
|
|
69
|
+
return _ROLE_CN.get(role, role)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def suggest_role_for(contract_type: str) -> list:
|
|
73
|
+
"""建议"哪些角色能产出该契约", 给越权提示用。"""
|
|
74
|
+
# 反查 config.yaml 哪些角色的 can_produce 含此类型或 '*'
|
|
75
|
+
try:
|
|
76
|
+
from .identity import _load_roles_config
|
|
77
|
+
except ImportError:
|
|
78
|
+
from identity import _load_roles_config
|
|
79
|
+
roles_cfg = _load_roles_config()
|
|
80
|
+
out = []
|
|
81
|
+
for r, cfg in roles_cfg.items():
|
|
82
|
+
caps = list(cfg.get("can_produce", []) or [])
|
|
83
|
+
if "*" in caps or contract_type in caps:
|
|
84
|
+
out.append(r)
|
|
85
|
+
return out
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def check_produce(
|
|
89
|
+
contract_type: str,
|
|
90
|
+
role: str = None,
|
|
91
|
+
ensure: bool = False,
|
|
92
|
+
) -> Result:
|
|
93
|
+
"""检查角色能否产出某契约类型。
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
contract_type: 契约类型 (prd/prototype/spec/design-spec/code/unit-test/test-report)
|
|
97
|
+
role: 角色名, 不传则用当前角色
|
|
98
|
+
ensure: False=越权返回WARN(降级); True=越权返回FAIL(硬拦)
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
Result.ok + CODE.OK: 允许产出
|
|
102
|
+
Result (WARN): 越权但降级 (ensure=False), 带 hint 提示该找谁
|
|
103
|
+
Result (FAIL): 越权且硬拦 (ensure=True)
|
|
104
|
+
"""
|
|
105
|
+
role = role or current_role()
|
|
106
|
+
if can_produce(role, contract_type):
|
|
107
|
+
return Result.success(msg="✓ {} 可产出 {}".format(_role_cn(role), contract_type))
|
|
108
|
+
# 越权
|
|
109
|
+
suggest = suggest_role_for(contract_type)
|
|
110
|
+
suggest_cn = "、".join(_role_cn(r) for r in suggest) if suggest else "对应角色"
|
|
111
|
+
hint = "你的角色({})不能产出 {}, 需 {} 角色。用 /wl-init 切换或转交。".format(
|
|
112
|
+
_role_cn(role), contract_type, suggest_cn
|
|
113
|
+
)
|
|
114
|
+
if ensure:
|
|
115
|
+
return Result.failure(
|
|
116
|
+
"[越权] {} 不能产出 {}".format(_role_cn(role), contract_type),
|
|
117
|
+
code=CODE.AUTHZ,
|
|
118
|
+
hint=hint,
|
|
119
|
+
)
|
|
120
|
+
# 降级: 不阻塞, 但强烈提示
|
|
121
|
+
return Result.warning(
|
|
122
|
+
"⚠️ {} 通常不产出 {} (建议 {})".format(_role_cn(role), contract_type, suggest_cn),
|
|
123
|
+
code=CODE.AUTHZ,
|
|
124
|
+
hint=hint,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def check_consume(
|
|
129
|
+
capability: str,
|
|
130
|
+
role: str = None,
|
|
131
|
+
) -> Result:
|
|
132
|
+
"""检查角色能否读取/消费某能力。越权则只读降级 (永远不阻塞)。"""
|
|
133
|
+
role = role or current_role()
|
|
134
|
+
if can_consume(role, capability):
|
|
135
|
+
return Result.success(msg="✓ {} 可消费 {}".format(_role_cn(role), capability))
|
|
136
|
+
return Result.warning(
|
|
137
|
+
"⚠️ {} 读取 {} 超出常规职责 (只读降级)".format(_role_cn(role), capability),
|
|
138
|
+
code=CODE.AUTHZ,
|
|
139
|
+
hint="这是软提示, 仍可继续 (越权只读降级, 不阻塞)",
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# ============================================================
|
|
144
|
+
# CLI (供 skill 脚本诊断用)
|
|
145
|
+
# ============================================================
|
|
146
|
+
|
|
147
|
+
if __name__ == "__main__":
|
|
148
|
+
import json
|
|
149
|
+
role = current_role()
|
|
150
|
+
print("当前角色: {} ({})".format(role, _role_cn(role)))
|
|
151
|
+
cfg = get_role_config(role)
|
|
152
|
+
print(" can_produce: {}".format(cfg.get("can_produce", [])))
|
|
153
|
+
print(" can_consume: {}".format(cfg.get("can_consume", [])))
|
|
154
|
+
if len(sys.argv) > 1:
|
|
155
|
+
ct = sys.argv[1]
|
|
156
|
+
r = check_produce(ct)
|
|
157
|
+
print("\n能否产出 {}: {}".format(ct, "能" if r.ok and not r.is_warn else "否"))
|
|
158
|
+
if r.hint:
|
|
159
|
+
print(" -> {}".format(r.hint))
|
|
@@ -41,8 +41,8 @@ from typing import Optional, Dict, Any, Union
|
|
|
41
41
|
|
|
42
42
|
# 相对导入兄弟模块
|
|
43
43
|
try:
|
|
44
|
-
from .atomicio import atomic_write_json, safe_read_json
|
|
45
|
-
from .paths import get_repo_root, MEMBERS_DIR
|
|
44
|
+
from foundation.io.atomicio import atomic_write_json, safe_read_json
|
|
45
|
+
from foundation.core.paths import get_repo_root, MEMBERS_DIR
|
|
46
46
|
except ImportError:
|
|
47
47
|
_COMMON_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
48
48
|
if _COMMON_DIR not in sys.path:
|
|
@@ -52,7 +52,7 @@ except ImportError:
|
|
|
52
52
|
_SCRIPTS_DIR = os.path.dirname(_COMMON_DIR)
|
|
53
53
|
if _SCRIPTS_DIR not in sys.path:
|
|
54
54
|
sys.path.insert(0, _SCRIPTS_DIR)
|
|
55
|
-
from
|
|
55
|
+
from foundation.core.paths import get_repo_root, MEMBERS_DIR
|
|
56
56
|
|
|
57
57
|
__all__ = [
|
|
58
58
|
"IdentityError",
|
|
@@ -65,9 +65,16 @@ __all__ = [
|
|
|
65
65
|
"verify_signature",
|
|
66
66
|
"require_member",
|
|
67
67
|
"ensure_current_member",
|
|
68
|
+
# ★ 角色能力 (v3.0 合并自 role.py) ★
|
|
69
|
+
"get_role",
|
|
70
|
+
"get_role_config",
|
|
71
|
+
"get_capabilities",
|
|
72
|
+
"get_permissions",
|
|
73
|
+
"can_produce",
|
|
74
|
+
"can_consume",
|
|
68
75
|
]
|
|
69
76
|
|
|
70
|
-
# 退出码 (
|
|
77
|
+
# 退出码 (与 common/result.py 的 EXIT[AUTHZ] 对齐): 4 = authz_denied
|
|
71
78
|
EXIT_AUTHZ = 4
|
|
72
79
|
|
|
73
80
|
# 名字合法字符: 字母/数字/中文/下划线/短横, 2-32 字符
|
|
@@ -96,7 +103,12 @@ def _member_json(name: str, repo_root: Optional[Union[str, Path]] = None) -> Pat
|
|
|
96
103
|
|
|
97
104
|
|
|
98
105
|
def _key_file(name: str, repo_root: Optional[Union[str, Path]] = None) -> Path:
|
|
99
|
-
|
|
106
|
+
"""签名密钥路径 (v3.0: 收进 .private/signing_key, 向后兼容老 .signing_key)。"""
|
|
107
|
+
try:
|
|
108
|
+
from foundation.core.paths import get_private_file
|
|
109
|
+
return get_private_file("signing_key", name)
|
|
110
|
+
except Exception:
|
|
111
|
+
return _member_dir(name, repo_root) / ".signing_key"
|
|
100
112
|
|
|
101
113
|
|
|
102
114
|
# ============================================================
|
|
@@ -192,6 +204,10 @@ def register_member(
|
|
|
192
204
|
or "%s <unknown@local>" % name
|
|
193
205
|
),
|
|
194
206
|
"joined_at": joined_at,
|
|
207
|
+
# 禅道账号映射 (v3.1.3): 本地 developer 名(如 hupan56) ↔ 禅道 account(如 hupan)
|
|
208
|
+
# 不一致时, zentao_sync 靠此过滤归属 (见 contracts/isolation.md「禅道同步」)。
|
|
209
|
+
# 首次注册留空 (None), 成员后续填; 已注册的保留原值不覆盖。
|
|
210
|
+
"zentao_account": (existing.get("zentao_account") if existing else None),
|
|
195
211
|
}
|
|
196
212
|
atomic_write_json(mj, record)
|
|
197
213
|
return record
|
|
@@ -260,11 +276,11 @@ def require_member(name: Optional[str] = None, repo_root: Optional[Union[str, Pa
|
|
|
260
276
|
"""
|
|
261
277
|
if name is None:
|
|
262
278
|
try:
|
|
263
|
-
from .
|
|
279
|
+
from foundation.core.paths import get_developer
|
|
264
280
|
except ImportError:
|
|
265
281
|
_this = os.path.dirname(os.path.abspath(__file__))
|
|
266
282
|
sys.path.insert(0, _this)
|
|
267
|
-
from paths import get_developer # type: ignore
|
|
283
|
+
from foundation.core.paths import get_developer # type: ignore
|
|
268
284
|
name = get_developer(repo_root)
|
|
269
285
|
if not name:
|
|
270
286
|
sys.stderr.write("[authz] 拒绝: 未设置开发者身份。先跑 /wl-init 注册。\n")
|
|
@@ -280,11 +296,118 @@ def require_member(name: Optional[str] = None, repo_root: Optional[Union[str, Pa
|
|
|
280
296
|
def ensure_current_member(repo_root: Optional[Union[str, Path]] = None) -> Optional[str]:
|
|
281
297
|
"""软检查: 返回当前已验证的成员名, 或 None (不 exit)。给非破坏性命令用。"""
|
|
282
298
|
try:
|
|
283
|
-
from paths import get_developer
|
|
299
|
+
from foundation.core.paths import get_developer
|
|
284
300
|
except ImportError:
|
|
285
301
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
286
|
-
from paths import get_developer # type: ignore
|
|
302
|
+
from foundation.core.paths import get_developer # type: ignore
|
|
287
303
|
name = get_developer(repo_root)
|
|
288
304
|
if name and verify_member(name, repo_root):
|
|
289
305
|
return name
|
|
290
306
|
return None
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
# ============================================================
|
|
310
|
+
# 角色能力 (v3.0 合并自 role.py —— 角色从"标签"升级为"能力边界")
|
|
311
|
+
#
|
|
312
|
+
# config.yaml 的 roles 段升级为能力声明:
|
|
313
|
+
# roles.<role>.can_produce: [prd, prototype, ...] 允许产出的契约类型
|
|
314
|
+
# roles.<role>.can_consume: ["*", prd, ...] 允许读取的能力
|
|
315
|
+
# guard.py 据此强制边界: 越权降级 (不报错), 各角色在边界内自治 = 解耦
|
|
316
|
+
#
|
|
317
|
+
# 向后兼容: get_role / get_permissions 保留 (老脚本可继续用)
|
|
318
|
+
# ============================================================
|
|
319
|
+
|
|
320
|
+
def _load_roles_config(repo_root: Optional[Union[str, Path]] = None) -> Dict[str, Any]:
|
|
321
|
+
"""读 config.yaml 的 roles 段。失败返回 {} (软降级, 不崩)。"""
|
|
322
|
+
if repo_root is None:
|
|
323
|
+
repo_root = get_repo_root()
|
|
324
|
+
cfg = Path(repo_root) / ".qoder" / "config.yaml"
|
|
325
|
+
if not cfg.is_file():
|
|
326
|
+
return {}
|
|
327
|
+
try:
|
|
328
|
+
import yaml
|
|
329
|
+
with open(cfg, encoding="utf-8") as f:
|
|
330
|
+
data = yaml.safe_load(f) or {}
|
|
331
|
+
return data.get("roles", {}) or {}
|
|
332
|
+
except Exception:
|
|
333
|
+
return {}
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def get_role(name: Optional[str] = None, repo_root: Optional[Union[str, Path]] = None) -> Optional[str]:
|
|
337
|
+
"""返回成员的角色名 (pm/design/dev/test/admin)。
|
|
338
|
+
|
|
339
|
+
优先级:
|
|
340
|
+
1. .developer 里记录的 role (init 时写)
|
|
341
|
+
2. member.json 里的 role
|
|
342
|
+
3. None (未注册)
|
|
343
|
+
无角色默认回退 'pm' (软引导, 不报错) —— 与原 role.py 行为一致。
|
|
344
|
+
"""
|
|
345
|
+
if not name:
|
|
346
|
+
# 从 .developer 读
|
|
347
|
+
try:
|
|
348
|
+
from foundation.core.paths import get_developer, get_developer_info
|
|
349
|
+
except ImportError:
|
|
350
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
351
|
+
from foundation.core.paths import get_developer, get_developer_info # type: ignore
|
|
352
|
+
info = get_developer_info(repo_root) if "get_developer_info" in dir() else None
|
|
353
|
+
if isinstance(info, dict) and info.get("role"):
|
|
354
|
+
return info["role"]
|
|
355
|
+
name = get_developer(repo_root)
|
|
356
|
+
if not name:
|
|
357
|
+
return None
|
|
358
|
+
m = get_member(name, repo_root)
|
|
359
|
+
return m.get("role") if m else None
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def get_role_config(role: Optional[str], repo_root: Optional[Union[str, Path]] = None) -> Dict[str, Any]:
|
|
363
|
+
"""返回某角色的完整配置 (can_produce/can_consume/commands/permissions/name)。
|
|
364
|
+
|
|
365
|
+
角色不存在或 config 缺失 → 返回 {} (软降级)。
|
|
366
|
+
"""
|
|
367
|
+
if not role:
|
|
368
|
+
return {}
|
|
369
|
+
return _load_roles_config(repo_root).get(role, {}) or {}
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def get_permissions(role: Optional[str], repo_root: Optional[Union[str, Path]] = None) -> list:
|
|
373
|
+
"""遗留 API: 返回角色的 permissions 列表 (向后兼容)。空则 []。"""
|
|
374
|
+
return list(get_role_config(role, repo_root).get("permissions", []) or [])
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def get_capabilities(
|
|
378
|
+
role: Optional[str], kind: str = "produce", repo_root: Optional[Union[str, Path]] = None
|
|
379
|
+
) -> list:
|
|
380
|
+
"""返回角色的能力列表。
|
|
381
|
+
|
|
382
|
+
Args:
|
|
383
|
+
role: 角色名
|
|
384
|
+
kind: 'produce' (能产出哪些契约) 或 'consume' (能读哪些)
|
|
385
|
+
Returns:
|
|
386
|
+
能力列表, 含 '*' 表示无限制。未知角色返回 [] (从严, 无能力)。
|
|
387
|
+
"""
|
|
388
|
+
key = "can_produce" if kind == "produce" else "can_consume"
|
|
389
|
+
caps = get_role_config(role, repo_root).get(key, [])
|
|
390
|
+
return list(caps) if caps else []
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def _has(cap_list: list, contract_type: str) -> bool:
|
|
394
|
+
"""判断 contract_type 是否在能力列表内 ('*' 通配)。"""
|
|
395
|
+
if not cap_list:
|
|
396
|
+
return False
|
|
397
|
+
if "*" in cap_list:
|
|
398
|
+
return True
|
|
399
|
+
return contract_type in cap_list
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def can_produce(
|
|
403
|
+
role: Optional[str], contract_type: str, repo_root: Optional[Union[str, Path]] = None
|
|
404
|
+
) -> bool:
|
|
405
|
+
"""该角色能否产出指定契约类型? guard.py 的核心查询。"""
|
|
406
|
+
return _has(get_capabilities(role, "produce", repo_root), contract_type)
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def can_consume(
|
|
410
|
+
role: Optional[str], capability: str, repo_root: Optional[Union[str, Path]] = None
|
|
411
|
+
) -> bool:
|
|
412
|
+
"""该角色能否读取/消费指定能力? 越权则只读降级。"""
|
|
413
|
+
return _has(get_capabilities(role, "consume", repo_root), capability)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
roles.py - 角色章节过滤 (单一真源, 消除 kg.py↔kg_mcp_server.py 逐字重复)
|
|
4
|
+
|
|
5
|
+
现状: ROLE_SECTIONS 字典 + _filter_by_role 函数在 kg.py(:645-672) 和
|
|
6
|
+
kg_mcp_server.py(:249-276) 逐字重复。改一处忘改另一处 → CLI 和 MCP 行为分叉。
|
|
7
|
+
|
|
8
|
+
本模块作为单一真源, kg.py 和 kg_mcp_server.py 都从这里 import。
|
|
9
|
+
|
|
10
|
+
过滤规则: 按角色裁剪 context_pack 输出的章节 (## 1. / ## 2. / ## 2b. 等),
|
|
11
|
+
让不同角色只看到与其职责相关的段落, 减少噪音。
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
from foundation.identity.roles import filter_by_role, ROLE_SECTIONS
|
|
15
|
+
text = filter_by_role(context_pack_output, role='pm')
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import re
|
|
19
|
+
|
|
20
|
+
__all__ = ["ROLE_SECTIONS", "filter_by_role"]
|
|
21
|
+
|
|
22
|
+
# 各角色可见的 context_pack 章节号
|
|
23
|
+
# (与 kg.py ROLE_SECTIONS / kg_mcp_server.py ROLE_SECTIONS 对齐, 此处为唯一真源)
|
|
24
|
+
ROLE_SECTIONS = {
|
|
25
|
+
'pm': {'1', '2', '2b', '3', '4', '5', '7'}, # PM: 字段+PRD+API,无代码噪音
|
|
26
|
+
'design': {'2', '2b', '3', '6'}, # 设计: 页面+布局+字段+风格
|
|
27
|
+
'dev': {'1', '3', '5', '7'}, # 开发: 代码+字段+API+wiki
|
|
28
|
+
'test': {'1', '4', '5'}, # 测试: 代码+PRD+API
|
|
29
|
+
'admin': {'4', '5'}, # 管理: PRD+API 概览
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def filter_by_role(text, role):
|
|
34
|
+
"""按角色过滤 context_pack 输出的章节。
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
text: context_pack 的多段输出 (## 1. xxx / ## 2. xxx ...)
|
|
38
|
+
role: 角色名 (pm/design/dev/test/admin), 未知角色不过滤
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
过滤后的文本 (只保留该角色可见章节)
|
|
42
|
+
"""
|
|
43
|
+
visible = ROLE_SECTIONS.get((role or '').lower())
|
|
44
|
+
if not visible:
|
|
45
|
+
return text # 未知角色不过滤
|
|
46
|
+
# 按 "## 数字." 切段 (含 2b 这种带字母的)
|
|
47
|
+
sections = re.split(r'(\n## \d+[ab]?\.)', text)
|
|
48
|
+
result = sections[0]
|
|
49
|
+
i = 1
|
|
50
|
+
while i < len(sections):
|
|
51
|
+
header = sections[i]
|
|
52
|
+
num_m = re.match(r'\n## (\d+[ab]?)\.', header)
|
|
53
|
+
if num_m and num_m.group(1) in visible:
|
|
54
|
+
result += header
|
|
55
|
+
if i + 1 < len(sections):
|
|
56
|
+
result += sections[i + 1]
|
|
57
|
+
i += 2
|
|
58
|
+
else:
|
|
59
|
+
i += 2
|
|
60
|
+
return result.strip()
|
|
File without changes
|
|
@@ -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
|
|
@@ -57,20 +57,21 @@ THEME_COLORS = {
|
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
def _load_config():
|
|
60
|
-
"""加载飞书配置 (config.yaml + 环境变量)。
|
|
61
|
-
|
|
60
|
+
"""加载飞书配置 (config.yaml + 环境变量)。
|
|
61
|
+
|
|
62
|
+
v3.0: 优先用 Config 单例 (带缓存), 失败回退文本解析。
|
|
63
|
+
"""
|
|
62
64
|
config = {}
|
|
63
65
|
try:
|
|
64
|
-
|
|
65
|
-
from .
|
|
66
|
-
|
|
67
|
-
with open(cfg_path, encoding='utf-8') as f:
|
|
68
|
-
full = yaml.safe_load(f) or {}
|
|
69
|
-
config = full.get('feishu') or {}
|
|
66
|
+
# 优先: Config 单例 (mtime 缓存, 避免每次重读)
|
|
67
|
+
from foundation.core.config import Config, SECTIONS
|
|
68
|
+
config = Config.load().section(SECTIONS.FEISHU, {})
|
|
70
69
|
except Exception:
|
|
71
|
-
|
|
70
|
+
pass
|
|
71
|
+
if not config:
|
|
72
|
+
# 回退: 简单文本解析 (PyYAML 不可用时)
|
|
72
73
|
try:
|
|
73
|
-
from .paths import get_repo_root
|
|
74
|
+
from foundation.core.paths import get_repo_root
|
|
74
75
|
cfg_path = os.path.join(str(get_repo_root()), '.qoder', 'config.yaml')
|
|
75
76
|
in_feishu = False
|
|
76
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'],
|