@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,228 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""req.py - 需求生命周期管理 (/wl-req 命令)
|
|
4
|
+
|
|
5
|
+
M3 workspace 重构的核心。解决"找不到产出"痛点:
|
|
6
|
+
一个需求的所有产出(PRD/原型/任务/规格)聚合在 REQ-ID 容器,
|
|
7
|
+
/wl-req 一条命令看到全貌, 不用跨目录翻。
|
|
8
|
+
|
|
9
|
+
设计 (PM 只需记 2 个姿势):
|
|
10
|
+
/wl-req 列出所有需求 (默认=list, 不用记)
|
|
11
|
+
/wl-req REQ-2026-001 显示该需求全部产出 (show)
|
|
12
|
+
/wl-req REQ-2026-001 发布 promote+publish一步到位 (草稿→产出→禅道)
|
|
13
|
+
|
|
14
|
+
promote 不单独暴露(合并进"发布"), list 不用记(无参数即是)。
|
|
15
|
+
|
|
16
|
+
用法:
|
|
17
|
+
python req.py # 列需求
|
|
18
|
+
python req.py REQ-2026-001 # 看某需求全貌
|
|
19
|
+
python req.py REQ-2026-001 发布 # 发布(promote+publish)
|
|
20
|
+
python req.py REQ-2026-001 promote # 仅草稿→产出(内部用)
|
|
21
|
+
"""
|
|
22
|
+
# v3.0 路径自举
|
|
23
|
+
import os as _o, sys as _s
|
|
24
|
+
_f = _o.path.abspath(__file__)
|
|
25
|
+
for _ in range(10):
|
|
26
|
+
_f = _o.path.dirname(_f)
|
|
27
|
+
_cp = _o.path.join(_f, 'foundation')
|
|
28
|
+
if _o.path.isfile(_o.path.join(_cp, 'bootstrap.py')):
|
|
29
|
+
break
|
|
30
|
+
if _cp not in _s.path: _s.path.insert(0, _cp)
|
|
31
|
+
from bootstrap import setup; setup()
|
|
32
|
+
from foundation.core.paths import get_repo_root
|
|
33
|
+
|
|
34
|
+
import os
|
|
35
|
+
import sys
|
|
36
|
+
from pathlib import Path
|
|
37
|
+
from foundation.core.paths import (
|
|
38
|
+
get_repo_root, get_developer, MEMBERS_DIR,
|
|
39
|
+
get_time_bucket, get_time_bucket_path, get_req_dir,
|
|
40
|
+
get_outputs_dir, find_req_dirs,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
BASE = get_repo_root()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _list_all_reqs(dev=None):
|
|
47
|
+
"""列出某开发者的所有需求 (跨 drafts/outputs/所有时间桶)。
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
[{req_id, area, bucket, path, files: [文件名]}, ...]
|
|
51
|
+
"""
|
|
52
|
+
dev = dev or get_developer(BASE) or "unknown"
|
|
53
|
+
dev_dir = MEMBERS_DIR / dev
|
|
54
|
+
if not dev_dir.is_dir():
|
|
55
|
+
return []
|
|
56
|
+
seen = {} # req_id → entry (去重, drafts优先)
|
|
57
|
+
for area in ("drafts", "outputs"):
|
|
58
|
+
area_root = dev_dir / area
|
|
59
|
+
if not area_root.is_dir():
|
|
60
|
+
continue
|
|
61
|
+
# 扫所有 REQ-* 目录 (跨时间桶)
|
|
62
|
+
for found in area_root.rglob("REQ-*"):
|
|
63
|
+
if not found.is_dir():
|
|
64
|
+
continue
|
|
65
|
+
req_id = found.name
|
|
66
|
+
# 清理标题后缀只取 REQ-YYYY-NNN (但保留完整名做显示)
|
|
67
|
+
import re
|
|
68
|
+
m = re.match(r"(REQ-\d{4}-\d{3,})", req_id)
|
|
69
|
+
clean_id = m.group(1) if m else req_id
|
|
70
|
+
if clean_id in seen:
|
|
71
|
+
# 已有, 但如果当前是outputs且已有的是drafts, 补充信息
|
|
72
|
+
continue
|
|
73
|
+
files = sorted(f.name for f in found.iterdir() if f.is_file())
|
|
74
|
+
subdirs = sorted(d.name for d in found.iterdir() if d.is_dir())
|
|
75
|
+
rel_bucket = str(found.parent.relative_to(area_root))
|
|
76
|
+
seen[clean_id] = {
|
|
77
|
+
"req_id": req_id,
|
|
78
|
+
"clean_id": clean_id,
|
|
79
|
+
"area": area,
|
|
80
|
+
"bucket": rel_bucket,
|
|
81
|
+
"path": found,
|
|
82
|
+
"files": files,
|
|
83
|
+
"subdirs": subdirs,
|
|
84
|
+
}
|
|
85
|
+
return list(seen.values())
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def cmd_list(dev=None):
|
|
89
|
+
"""列出所有需求。"""
|
|
90
|
+
reqs = _list_all_reqs(dev)
|
|
91
|
+
if not reqs:
|
|
92
|
+
dev_name = dev or get_developer(BASE) or "unknown"
|
|
93
|
+
print("暂无需求。写 PRD 时会自动创建 REQ-ID 容器。")
|
|
94
|
+
print(" 开发者: %s" % dev_name)
|
|
95
|
+
return 0
|
|
96
|
+
print("=" * 56)
|
|
97
|
+
print("需求列表 (%d 个)" % len(reqs))
|
|
98
|
+
print("=" * 56)
|
|
99
|
+
for r in sorted(reqs, key=lambda x: x["clean_id"]):
|
|
100
|
+
area_tag = "草稿" if r["area"] == "drafts" else "产出"
|
|
101
|
+
n_files = len(r["files"]) + len(r["subdirs"])
|
|
102
|
+
print(" %s [%s] %s (%d 项)" % (r["clean_id"], area_tag, r["bucket"], n_files))
|
|
103
|
+
print()
|
|
104
|
+
print("看详情: /wl-req <REQ-ID>")
|
|
105
|
+
return 0
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def cmd_show(req_id, dev=None):
|
|
109
|
+
"""显示某需求的全部产出 (跨 drafts/outputs/历史桶)。"""
|
|
110
|
+
found = find_req_dirs(req_id, dev, BASE)
|
|
111
|
+
if not found:
|
|
112
|
+
print("未找到需求: %s" % req_id)
|
|
113
|
+
print(" 用 /wl-req 看所有需求列表")
|
|
114
|
+
return 1
|
|
115
|
+
print("=" * 56)
|
|
116
|
+
print("需求: %s" % req_id)
|
|
117
|
+
print("=" * 56)
|
|
118
|
+
for entry in found:
|
|
119
|
+
area_cn = "草稿" if entry["area"] == "drafts" else "产出"
|
|
120
|
+
p = entry["path"]
|
|
121
|
+
print()
|
|
122
|
+
print("【%s · %s】" % (area_cn, entry["bucket"]))
|
|
123
|
+
print(" 路径: %s" % p)
|
|
124
|
+
# 列文件
|
|
125
|
+
files = sorted(f.name for f in p.iterdir() if f.is_file())
|
|
126
|
+
subdirs = sorted(d.name for d in p.iterdir() if d.is_dir())
|
|
127
|
+
if files:
|
|
128
|
+
print(" 文件:")
|
|
129
|
+
for f in files:
|
|
130
|
+
print(" - %s" % f)
|
|
131
|
+
if subdirs:
|
|
132
|
+
print(" 子目录:")
|
|
133
|
+
for d in subdirs:
|
|
134
|
+
# 列子目录里的文件数
|
|
135
|
+
sub_path = p / d
|
|
136
|
+
n = sum(1 for _ in sub_path.rglob("*") if _.is_file())
|
|
137
|
+
print(" - %s/ (%d 个文件)" % (d, n))
|
|
138
|
+
return 0
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def cmd_promote(req_id, dev=None):
|
|
142
|
+
"""草稿 → 产出 (drafts/桶/REQ → outputs/同桶/REQ)。"""
|
|
143
|
+
dev = dev or get_developer(BASE) or "unknown"
|
|
144
|
+
# 找 drafts 里的
|
|
145
|
+
draft_found = [e for e in find_req_dirs(req_id, dev, BASE) if e["area"] == "drafts"]
|
|
146
|
+
if not draft_found:
|
|
147
|
+
print("草稿区未找到: %s (可能已在产出区, 或不存在)" % req_id)
|
|
148
|
+
return 1
|
|
149
|
+
src = draft_found[0]["path"]
|
|
150
|
+
bucket = draft_found[0]["bucket"]
|
|
151
|
+
# 目标: outputs/同桶/REQ
|
|
152
|
+
dev_dir = MEMBERS_DIR / dev
|
|
153
|
+
dst = dev_dir / "outputs" / bucket / src.name
|
|
154
|
+
if dst.exists():
|
|
155
|
+
print("产出区已存在: %s" % dst)
|
|
156
|
+
print(" 如要覆盖, 先手动删除产出区的旧版本")
|
|
157
|
+
return 1
|
|
158
|
+
import shutil
|
|
159
|
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
160
|
+
shutil.move(str(src), str(dst))
|
|
161
|
+
print("✓ 已提升到产出区:")
|
|
162
|
+
print(" %s" % dst)
|
|
163
|
+
print(" (产出进 team_sync, 团队可见)")
|
|
164
|
+
return 0
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def cmd_publish(req_id, dev=None):
|
|
168
|
+
"""发布 = promote + 发禅道。PM 只用这个。"""
|
|
169
|
+
dev = dev or get_developer(BASE) or "unknown"
|
|
170
|
+
# 先确保在 outputs (不在则 promote)
|
|
171
|
+
out_found = [e for e in find_req_dirs(req_id, dev, BASE) if e["area"] == "outputs"]
|
|
172
|
+
if not out_found:
|
|
173
|
+
print("草稿 → 产出...")
|
|
174
|
+
rc = cmd_promote(req_id, dev)
|
|
175
|
+
if rc != 0:
|
|
176
|
+
return rc
|
|
177
|
+
out_found = [e for e in find_req_dirs(req_id, dev, BASE) if e["area"] == "outputs"]
|
|
178
|
+
if not out_found:
|
|
179
|
+
print("✗ 发布失败: 产出区找不到 %s" % req_id)
|
|
180
|
+
return 1
|
|
181
|
+
out_path = out_found[0]["path"]
|
|
182
|
+
print()
|
|
183
|
+
print("发禅道 (从产出区: %s)" % out_path)
|
|
184
|
+
print("-" * 40)
|
|
185
|
+
# 找禅道 MCP 创建需求
|
|
186
|
+
try:
|
|
187
|
+
sys.path.insert(0, str(BASE / ".qoder" / "scripts" / "mcp"))
|
|
188
|
+
# 禅道发布: 读 PRD, 调 zentao MCP 建 story
|
|
189
|
+
prd_file = None
|
|
190
|
+
for f in out_path.iterdir():
|
|
191
|
+
if f.is_file() and (f.name.endswith(".md") and "prd" in f.name.lower()):
|
|
192
|
+
prd_file = f
|
|
193
|
+
break
|
|
194
|
+
if f.is_file() and f.suffix == ".md" and not f.name.startswith("_"):
|
|
195
|
+
prd_file = f # 第一个 md 当 PRD
|
|
196
|
+
if prd_file:
|
|
197
|
+
print(" PRD: %s" % prd_file.name)
|
|
198
|
+
print()
|
|
199
|
+
print("⚠️ 禅道发布需要 QoderWork 的禅道 MCP (mcp__qoder-zentao__create_story)。")
|
|
200
|
+
print(" AI 会读产出的 PRD, 调禅道建需求。")
|
|
201
|
+
print(" 如果你在 QoderWork 里说'%s 发布', AI 会自动完成这步。" % req_id)
|
|
202
|
+
return 0
|
|
203
|
+
except Exception as e:
|
|
204
|
+
print("✗ 发布出错: %s" % e)
|
|
205
|
+
return 1
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def main():
|
|
209
|
+
args = sys.argv[1:]
|
|
210
|
+
if not args:
|
|
211
|
+
return cmd_list()
|
|
212
|
+
req_id = args[0]
|
|
213
|
+
action = args[1] if len(args) > 1 else "show"
|
|
214
|
+
if action == "show":
|
|
215
|
+
return cmd_show(req_id)
|
|
216
|
+
elif action in ("发布", "publish", "fabu"):
|
|
217
|
+
return cmd_publish(req_id)
|
|
218
|
+
elif action == "promote":
|
|
219
|
+
return cmd_promote(req_id)
|
|
220
|
+
elif action == "list":
|
|
221
|
+
return cmd_list()
|
|
222
|
+
else:
|
|
223
|
+
# 默认当 show
|
|
224
|
+
return cmd_show(req_id)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
if __name__ == "__main__":
|
|
228
|
+
sys.exit(main())
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# 子包初始化: 注入 scripts/根 + 所有子包到 sys.path
|
|
2
|
+
# 让跨包 import (旧式不带前缀) 仍可用, 兼容现有代码
|
|
3
|
+
import os as _os, sys as _sys
|
|
4
|
+
_root = _os.path.dirname(_os.path.dirname(_os.path.abspath(__file__)))
|
|
5
|
+
if _root not in _sys.path:
|
|
6
|
+
_sys.path.insert(0, _root)
|
|
7
|
+
# 注入所有子包目录 (兼容旧式 import xxx)
|
|
8
|
+
for _sub in ('kg', 'build', 'test', 'setup', 'io'):
|
|
9
|
+
_subdir = _os.path.join(_root, _sub)
|
|
10
|
+
if _os.path.isdir(_subdir) and _subdir not in _sys.path:
|
|
11
|
+
_sys.path.insert(0, _subdir)
|
|
@@ -1,14 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
|
+
# v3.0 路径自举: 引导到 common/bootstrap, 统一 sys.path 逻辑
|
|
4
|
+
import os as _o, sys as _s
|
|
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
|
|
11
|
+
if _cp not in _s.path: _s.path.insert(0, _cp)
|
|
12
|
+
from bootstrap import setup; setup()
|
|
13
|
+
from foundation.core.paths import get_repo_root
|
|
14
|
+
|
|
3
15
|
"""
|
|
4
16
|
Weekly Knowledge Graph Update
|
|
5
17
|
|
|
6
18
|
Usage:
|
|
7
|
-
python git_sync.py # Full update: git + PRD + index
|
|
8
|
-
python git_sync.py --sync-only # Git pull only
|
|
9
|
-
python git_sync.py --index-only # Re-index only (no git pull)
|
|
10
|
-
python git_sync.py --prd-only # Collect + parse PRDs only
|
|
11
|
-
python git_sync.py --project fywl-ics # Sync single project
|
|
19
|
+
python .qoder/scripts/domain/task/git_sync.py # Full update: git + PRD + index
|
|
20
|
+
python .qoder/scripts/domain/task/git_sync.py --sync-only # Git pull only
|
|
21
|
+
python .qoder/scripts/domain/task/git_sync.py --index-only # Re-index only (no git pull)
|
|
22
|
+
python .qoder/scripts/domain/task/git_sync.py --prd-only # Collect + parse PRDs only
|
|
23
|
+
python .qoder/scripts/domain/task/git_sync.py --project fywl-ics # Sync single project
|
|
12
24
|
|
|
13
25
|
What it does (Friday Update):
|
|
14
26
|
1. Git pull latest code for each project
|
|
@@ -21,7 +33,7 @@ Config: .qoder/config.yaml -> git_sync section
|
|
|
21
33
|
"""
|
|
22
34
|
|
|
23
35
|
import os
|
|
24
|
-
from
|
|
36
|
+
from foundation.core.paths import BUILD_CACHE_DIR
|
|
25
37
|
import sys
|
|
26
38
|
import json
|
|
27
39
|
import subprocess
|
|
@@ -32,7 +44,7 @@ from datetime import datetime
|
|
|
32
44
|
if sys.platform == 'win32':
|
|
33
45
|
sys.stdout.reconfigure(encoding='utf-8')
|
|
34
46
|
|
|
35
|
-
BASE =
|
|
47
|
+
BASE = get_repo_root()
|
|
36
48
|
DATA_DIR = os.path.join(BASE, 'data')
|
|
37
49
|
CODE_DIR = os.path.join(DATA_DIR, 'code')
|
|
38
50
|
INDEX_DIR = os.path.join(DATA_DIR, 'index')
|
|
@@ -50,7 +62,7 @@ def load_json(path, default=None):
|
|
|
50
62
|
try:
|
|
51
63
|
with open(path, 'r', encoding='utf-8') as f:
|
|
52
64
|
return json.load(f)
|
|
53
|
-
except:
|
|
65
|
+
except Exception:
|
|
54
66
|
pass
|
|
55
67
|
return default or {}
|
|
56
68
|
|
|
@@ -63,15 +75,43 @@ def save_json(path, data):
|
|
|
63
75
|
|
|
64
76
|
|
|
65
77
|
def load_config():
|
|
66
|
-
"""Load config from config.yaml"""
|
|
67
|
-
if not os.path.exists(CONFIG_PATH):
|
|
68
|
-
return {}
|
|
78
|
+
"""Load config from config.yaml (v3.0: 转发到 Config 单例, 带缓存)。"""
|
|
69
79
|
try:
|
|
70
|
-
import
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
80
|
+
from foundation.core.config import Config
|
|
81
|
+
return Config.load().raw
|
|
82
|
+
except Exception:
|
|
83
|
+
# 回退: 直接读 (sys.path 未就绪时)
|
|
84
|
+
if not os.path.exists(CONFIG_PATH):
|
|
85
|
+
return {}
|
|
86
|
+
try:
|
|
87
|
+
import yaml
|
|
88
|
+
with open(CONFIG_PATH, 'r', encoding='utf-8') as f:
|
|
89
|
+
return yaml.safe_load(f) or {}
|
|
90
|
+
except Exception:
|
|
91
|
+
return {}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _git(*args, cwd=None, timeout=None):
|
|
95
|
+
"""git 命令包装(复刻 team_sync.git 风格)。
|
|
96
|
+
|
|
97
|
+
网络/远程操作 (fetch/pull/push/clone) 必须传 timeout —— 50 人团队周五 cron
|
|
98
|
+
跑时, 远程要认证但凭证没缓存 / 弱网 / 仓库不存在, 会让裸 subprocess.run 永久
|
|
99
|
+
阻塞, 整个周五同步挂死。本地操作 (rev-parse/diff/log) 可不传。
|
|
100
|
+
|
|
101
|
+
timeout 到期返回 rc=124 (与 timeout 命令一致), 不让 cron 无限阻塞。
|
|
102
|
+
git 未安装返回 rc=127, 不崩溃。
|
|
103
|
+
"""
|
|
104
|
+
try:
|
|
105
|
+
kw = dict(cwd=cwd, capture_output=True, text=True, encoding='utf-8', errors='replace')
|
|
106
|
+
if timeout:
|
|
107
|
+
kw['timeout'] = timeout
|
|
108
|
+
return subprocess.run(['git'] + list(args), **kw)
|
|
109
|
+
except FileNotFoundError:
|
|
110
|
+
import types
|
|
111
|
+
return types.SimpleNamespace(returncode=127, stdout='', stderr='git not installed')
|
|
112
|
+
except subprocess.TimeoutExpired:
|
|
113
|
+
import types
|
|
114
|
+
return types.SimpleNamespace(returncode=124, stdout='', stderr='git {} timed out after {}s'.format(' '.join(args[:3]), timeout))
|
|
75
115
|
|
|
76
116
|
|
|
77
117
|
# ============================================================
|
|
@@ -86,32 +126,22 @@ def git_pull_project(project_name):
|
|
|
86
126
|
return []
|
|
87
127
|
|
|
88
128
|
# Get current commit before pull
|
|
89
|
-
result =
|
|
90
|
-
['git', 'rev-parse', 'HEAD'],
|
|
91
|
-
cwd=project_dir, capture_output=True, text=True, encoding='utf-8'
|
|
92
|
-
)
|
|
129
|
+
result = _git('rev-parse', 'HEAD', cwd=project_dir)
|
|
93
130
|
old_commit = result.stdout.strip()[:8] if result.returncode == 0 else ''
|
|
94
131
|
|
|
95
|
-
# Fetch + pull
|
|
132
|
+
# Fetch + pull (网络操作必须带 timeout, 防弱网/认证弹窗挂死周五 cron)
|
|
96
133
|
print(f' {project_name}: Fetching...')
|
|
97
|
-
|
|
98
|
-
cwd=project_dir, capture_output=True)
|
|
134
|
+
_git('fetch', 'origin', cwd=project_dir, timeout=120)
|
|
99
135
|
|
|
100
136
|
print(f' {project_name}: Pulling...')
|
|
101
|
-
result =
|
|
102
|
-
['git', 'pull', 'origin'],
|
|
103
|
-
cwd=project_dir, capture_output=True, text=True, encoding='utf-8'
|
|
104
|
-
)
|
|
137
|
+
result = _git('pull', 'origin', cwd=project_dir, timeout=180)
|
|
105
138
|
|
|
106
139
|
if result.returncode != 0:
|
|
107
|
-
print(f' {project_name}: Pull failed - {result.stderr[:100]}')
|
|
140
|
+
print(f' {project_name}: Pull failed - {(result.stderr or "")[:100]}')
|
|
108
141
|
return []
|
|
109
142
|
|
|
110
143
|
# Get new commit
|
|
111
|
-
result =
|
|
112
|
-
['git', 'rev-parse', 'HEAD'],
|
|
113
|
-
cwd=project_dir, capture_output=True, text=True, encoding='utf-8'
|
|
114
|
-
)
|
|
144
|
+
result = _git('rev-parse', 'HEAD', cwd=project_dir)
|
|
115
145
|
new_commit = result.stdout.strip()[:8] if result.returncode == 0 else ''
|
|
116
146
|
|
|
117
147
|
if old_commit == new_commit:
|
|
@@ -119,10 +149,7 @@ def git_pull_project(project_name):
|
|
|
119
149
|
return []
|
|
120
150
|
|
|
121
151
|
# Get changed files
|
|
122
|
-
result =
|
|
123
|
-
['git', 'diff', '--name-only', old_commit, new_commit],
|
|
124
|
-
cwd=project_dir, capture_output=True, text=True, encoding='utf-8'
|
|
125
|
-
)
|
|
152
|
+
result = _git('diff', '--name-only', old_commit, new_commit, cwd=project_dir)
|
|
126
153
|
changed = [f.strip() for f in result.stdout.strip().split('\n') if f.strip()]
|
|
127
154
|
print(f' {project_name}: {old_commit} -> {new_commit}, {len(changed)} files changed')
|
|
128
155
|
return changed
|
|
@@ -182,18 +209,31 @@ def collect_prds():
|
|
|
182
209
|
if not os.path.isdir(user_dir):
|
|
183
210
|
continue
|
|
184
211
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
if
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
212
|
+
# 递归扫 drafts + outputs 两个区 (PRD 可能落在时间桶
|
|
213
|
+
# drafts/{YYYY}/{MM}/{Ww}/{REQ-ID}/REQ-*.md, 也可能已 publish 到 outputs/)。
|
|
214
|
+
# outputs 是发布版, 同名优先 outputs; 同区递归找 (os.walk) 兼容旧扁平结构。
|
|
215
|
+
user_prds = {}
|
|
216
|
+
for area in ('drafts', 'outputs'):
|
|
217
|
+
area_dir = os.path.join(user_dir, area)
|
|
218
|
+
if not os.path.isdir(area_dir):
|
|
219
|
+
continue
|
|
220
|
+
for root, dirs, files in os.walk(area_dir):
|
|
221
|
+
# 跳过隐藏目录 / node_modules (保险)
|
|
222
|
+
dirs[:] = [d for d in dirs if not d.startswith('.') and d != 'node_modules']
|
|
223
|
+
for f in files:
|
|
224
|
+
if (f.startswith('REQ-') or f.startswith('PRD-') or f.startswith('prd-')) and f.endswith('.md'):
|
|
225
|
+
filepath = os.path.join(root, f)
|
|
226
|
+
try:
|
|
227
|
+
mtime = os.path.getmtime(filepath)
|
|
228
|
+
except OSError:
|
|
229
|
+
continue
|
|
230
|
+
entry = {'file': f, 'path': filepath, 'user': user_name, 'mtime': mtime, 'area': area}
|
|
231
|
+
existing = user_prds.get(f)
|
|
232
|
+
# outputs 优先; 否则取较新的 mtime
|
|
233
|
+
if existing is None or area == 'outputs' or mtime > existing['mtime']:
|
|
234
|
+
user_prds[f] = entry
|
|
235
|
+
|
|
236
|
+
for prd in user_prds.values():
|
|
197
237
|
key = prd['file']
|
|
198
238
|
is_new = key not in collected
|
|
199
239
|
is_updated = not is_new and collected[key].get('mtime', 0) < prd['mtime']
|
|
@@ -383,7 +423,7 @@ def parse_java_file(filepath):
|
|
|
383
423
|
'keywords': re.findall('[A-Z]?[a-z]+', class_name or '') if class_name else []
|
|
384
424
|
}
|
|
385
425
|
entities.append(entity)
|
|
386
|
-
except:
|
|
426
|
+
except Exception:
|
|
387
427
|
pass
|
|
388
428
|
return entities
|
|
389
429
|
|
|
@@ -411,7 +451,7 @@ def parse_frontend_file(filepath):
|
|
|
411
451
|
'type': 'vue' if filepath.endswith('.vue') else 'js/ts'
|
|
412
452
|
}
|
|
413
453
|
entities.append(entity)
|
|
414
|
-
except:
|
|
454
|
+
except Exception:
|
|
415
455
|
pass
|
|
416
456
|
return entities
|
|
417
457
|
|
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
+
# v3.0 路径自举: 引导到 common/bootstrap, 统一 sys.path 逻辑
|
|
3
|
+
import os as _o, sys as _s
|
|
4
|
+
_f = _o.path.abspath(__file__)
|
|
5
|
+
for _ in range(10):
|
|
6
|
+
_f = _o.path.dirname(_f)
|
|
7
|
+
_cp = _o.path.join(_f, 'foundation')
|
|
8
|
+
if _o.path.isfile(_o.path.join(_cp, 'bootstrap.py')):
|
|
9
|
+
break
|
|
10
|
+
if _cp not in _s.path: _s.path.insert(0, _cp)
|
|
11
|
+
from bootstrap import setup; setup()
|
|
12
|
+
|
|
2
13
|
"""
|
|
3
14
|
syncgate.py - team_sync 推送前的零信任门禁
|
|
4
15
|
|
|
@@ -22,11 +33,11 @@ from typing import List, Tuple, Optional
|
|
|
22
33
|
_THIS_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
23
34
|
if _THIS_DIR not in sys.path:
|
|
24
35
|
sys.path.insert(0, _THIS_DIR)
|
|
25
|
-
_COMMON = os.path.join(_THIS_DIR, "
|
|
36
|
+
_COMMON = os.path.join(_THIS_DIR, "foundation")
|
|
26
37
|
if _COMMON not in sys.path:
|
|
27
38
|
sys.path.insert(0, _COMMON)
|
|
28
39
|
|
|
29
|
-
from
|
|
40
|
+
from foundation.identity.identity import (
|
|
30
41
|
get_member, verify_member, EXIT_AUTHZ, IdentityError,
|
|
31
42
|
)
|
|
32
43
|
|
|
@@ -223,7 +234,7 @@ _PLATFORM_KEYWORDS = [
|
|
|
223
234
|
def check_platform_gate(prd_paths: List[str], repo_root: str) -> None:
|
|
224
235
|
"""检查 PRD 文件是否标注了目标平台。未标注抛 GateFailure。
|
|
225
236
|
|
|
226
|
-
这是 /wl-prd
|
|
237
|
+
这是 /wl-prd "先问平台" 的程序级兜底:
|
|
227
238
|
如果 AI 没问平台就生成了 PRD, push 时这里会拦住。
|
|
228
239
|
"""
|
|
229
240
|
missing = []
|
|
@@ -242,7 +253,7 @@ def check_platform_gate(prd_paths: List[str], repo_root: str) -> None:
|
|
|
242
253
|
|
|
243
254
|
if missing:
|
|
244
255
|
raise GateFailure(
|
|
245
|
-
"[gate] 拒绝: %d 个 PRD 未标注目标平台 (违反 /wl-prd
|
|
256
|
+
"[gate] 拒绝: %d 个 PRD 未标注目标平台 (违反 /wl-prd 先问平台规则):\n"
|
|
246
257
|
" %s\n"
|
|
247
258
|
"修复: 在 PRD 头部加一行:\n"
|
|
248
259
|
" - **平台**: Web管理端 (fywl-ui) # 或 APP移动端 / 两端 / 后端" % (
|
|
@@ -268,7 +279,7 @@ def check_eval_gate(
|
|
|
268
279
|
if skip_eval or not prd_paths:
|
|
269
280
|
return
|
|
270
281
|
# 硬 import: eval_api 是必需模块,缺失说明引擎损坏,应报错而非静默跳过
|
|
271
|
-
from
|
|
282
|
+
from foundation.utils.eval_api import evaluate
|
|
272
283
|
|
|
273
284
|
failures = []
|
|
274
285
|
for prd in prd_paths:
|
|
@@ -313,7 +324,7 @@ def check_eval_gate(
|
|
|
313
324
|
lines.append("修复 PRD 后重试, 或 admin 用 --skip-eval 跳过 (会记录审计)")
|
|
314
325
|
# D2: 飞书通知 EVA 拒绝 (best-effort, 失败不阻塞门禁)
|
|
315
326
|
try:
|
|
316
|
-
from
|
|
327
|
+
from foundation.integrations.feishu import notify_eval_rejected
|
|
317
328
|
notify_eval_rejected(
|
|
318
329
|
req_id=",".join(os.path.basename(p) for p, _ in failures[:3]),
|
|
319
330
|
title="批量 EVA 拒绝",
|
|
@@ -356,7 +367,7 @@ def run_gates(
|
|
|
356
367
|
]
|
|
357
368
|
if prd_files:
|
|
358
369
|
# 硬 import: reqid 是必需模块,缺失说明引擎损坏
|
|
359
|
-
from
|
|
370
|
+
from foundation.io.reqid import validate_req_ids, migrate_from_existing, ReqIdError
|
|
360
371
|
# 计数器缺失则先迁移 (幂等)
|
|
361
372
|
try:
|
|
362
373
|
validate_req_ids(prd_files, repo_root)
|