@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,384 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
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
|
+
|
|
14
|
+
"""
|
|
15
|
+
QODER Prefetch — 需求上下文"预测 + 批量预取"
|
|
16
|
+
|
|
17
|
+
治的病: 写 PRD 时串行试探式搜索 (查一个词不命中 → 换词再查 → 再换...),
|
|
18
|
+
一次需求 6+ 次往返, 又慢又可能违反"禁止 grep 全库"的规则。
|
|
19
|
+
|
|
20
|
+
做法 (用户原话): "开始前先预测需要查哪些, 一批查完, 不够再补。"
|
|
21
|
+
|
|
22
|
+
输入一句需求描述, 输出一份合并去重的 context_pack (8 段), 等价于把多个
|
|
23
|
+
业务词的查询结果一次性合并。一次调用替代 6+ 次串行 search/find/grep。
|
|
24
|
+
|
|
25
|
+
Usage:
|
|
26
|
+
python prefetch.py "保险单OCR识别标注对比图" --platform web
|
|
27
|
+
python prefetch.py "考勤导出按钮" --platform app
|
|
28
|
+
|
|
29
|
+
设计:
|
|
30
|
+
- 不重新实现 context_pack 的 8 段, 复用其底层函数 (load/match_kw/plat_filter)
|
|
31
|
+
- 词预测: 从需求句抽业务实体 + expand_chinese_query 分词 + 缩写直通
|
|
32
|
+
- 多词命中合并去重, 按"被几个词命中"排序 (多词命中的文件排前面)
|
|
33
|
+
- 降级: 词预测失败 → 回退单词 context_pack
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
import os
|
|
37
|
+
import sys
|
|
38
|
+
|
|
39
|
+
# UTF-8 stdio (Windows 终端默认 GBK)
|
|
40
|
+
try:
|
|
41
|
+
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
|
|
42
|
+
except (AttributeError, TypeError, OSError, IOError):
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
46
|
+
|
|
47
|
+
# 复用 context_pack 的底层 (单一数据源, 不重复造轮子)
|
|
48
|
+
from domain.kg.search.context_pack import (
|
|
49
|
+
load, match_kw, plat_filter, PLATFORM_MAP, MIN_FUZZY_LEN,
|
|
50
|
+
BASE, INDEX_DIR, BUILD_CACHE_DIR,
|
|
51
|
+
)
|
|
52
|
+
from foundation.integrations.terms import expand_chinese_query, get_cn_map_with_auto, BUSINESS_PATH_MAP
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# ── 词预测 ──────────────────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
# 常见缩写/英文技术词, 不在 CN_MAP 里, 直接作为英文词参与子串匹配
|
|
58
|
+
# (OCR 这种缩写原来直接原样返回 [ocr], 在 code-keyword 里 0 命中)
|
|
59
|
+
_EN_ACRONYMS = {
|
|
60
|
+
'ocr', 'api', 'gps', 'sms', 'qrcode', 'qr', 'pdf', 'excel',
|
|
61
|
+
'barcode', 'rfid', 'nfc', 'ai', 'etl', 'crm', 'erp', 'oa',
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def predict_words(sentence):
|
|
66
|
+
"""从一句需求描述预测出要查的业务词集合。
|
|
67
|
+
|
|
68
|
+
三级抽取:
|
|
69
|
+
1. 直接命中 CN_MAP/BUSINESS_PATH_MAP 的中文词 (保险/考勤/导出...)
|
|
70
|
+
2. 英文缩写/技术词 (OCR/API/GPS...) 直通
|
|
71
|
+
3. 句子里其它 >=2 字的片段, 兜底参与匹配
|
|
72
|
+
|
|
73
|
+
返回 (主词列表, 用于命中的英文小写词列表)。主词是中文业务词,
|
|
74
|
+
英文词用于索引子串匹配。
|
|
75
|
+
"""
|
|
76
|
+
sentence = sentence.strip()
|
|
77
|
+
if not sentence:
|
|
78
|
+
return [], []
|
|
79
|
+
|
|
80
|
+
cn_map = get_cn_map_with_auto()
|
|
81
|
+
primary_cn = [] # 命中的中文业务词 (用于展示)
|
|
82
|
+
en_words = set() # 所有英文搜索词 (用于命中匹配)
|
|
83
|
+
|
|
84
|
+
# 1. 贪心最长匹配中文业务词 (与 expand_chinese_query 同思路, 但保留原始中文词)
|
|
85
|
+
remaining = sentence
|
|
86
|
+
for cn in sorted(cn_map.keys(), key=len, reverse=True):
|
|
87
|
+
if cn in remaining:
|
|
88
|
+
primary_cn.append(cn)
|
|
89
|
+
# 用 expand_chinese_query 拿这个词的英文展开
|
|
90
|
+
for w in expand_chinese_query(cn):
|
|
91
|
+
en_words.add(w.lower())
|
|
92
|
+
remaining = remaining.replace(cn, ' ')
|
|
93
|
+
|
|
94
|
+
# 2. 英文缩写/技术词直通 (OCR 不在任何词典里也照常参与匹配)
|
|
95
|
+
low = sentence.lower()
|
|
96
|
+
for acr in _EN_ACRONYMS:
|
|
97
|
+
if acr in low:
|
|
98
|
+
en_words.add(acr)
|
|
99
|
+
# 也带上常见变体 (ocr → ocr; 不强行猜其它)
|
|
100
|
+
# 句子里其它纯英文片段 (>=3 字母) 也加入, 让索引做子串匹配
|
|
101
|
+
for frag in remaining.split():
|
|
102
|
+
frag = frag.strip()
|
|
103
|
+
if frag and frag.isascii() and len(frag) >= 3:
|
|
104
|
+
en_words.add(frag.lower())
|
|
105
|
+
|
|
106
|
+
# 3. expand_chinese_query 对整句做一次 (捕获中文分词能拆出的英文)
|
|
107
|
+
for w in expand_chinese_query(sentence):
|
|
108
|
+
en_words.add(w.lower())
|
|
109
|
+
|
|
110
|
+
# 兜底: 整句小写也作为一个匹配候选 (若全是英文/数字)
|
|
111
|
+
if sentence.isascii():
|
|
112
|
+
en_words.add(sentence.lower())
|
|
113
|
+
|
|
114
|
+
return primary_cn, sorted(en_words)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# ── 批量合并取上下文 ──────────────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
def _collect(targets, en_words, query=None):
|
|
120
|
+
"""对预测出的所有词, 批量收集 1/3/4/5 段命中, 合并去重。
|
|
121
|
+
|
|
122
|
+
返回 dict: {段名: 合并去重 + 排序后的列表}。
|
|
123
|
+
排序: 多词命中的项排前面 (被越多词命中越相关)。
|
|
124
|
+
query: 原始需求句 (用于语义补充检索, 中文句比拆词后的英文更准)。
|
|
125
|
+
"""
|
|
126
|
+
ki = load('code-keyword.json') or {}
|
|
127
|
+
si = load('ui-style.json') or {}
|
|
128
|
+
prds = load('prd-index.json') or {}
|
|
129
|
+
apis = load('code-api.json') or {}
|
|
130
|
+
# frontend-routes: 前端路由 + view 路径 (前端需求的主要命中源,
|
|
131
|
+
# code-keyword 虽有 vue 但靠 kw 键匹配, 路由页直接命中靠这里)
|
|
132
|
+
fr = load('frontend-routes.json') or {}
|
|
133
|
+
|
|
134
|
+
# ---- 1. 相关代码文件: 文件 -> {命中的词} ----
|
|
135
|
+
file_hits = {} # f -> set(命中的词)
|
|
136
|
+
# 1a. code-keyword: 扩展词匹配 keyword 键 + 匹配文件路径
|
|
137
|
+
for word in en_words:
|
|
138
|
+
for kw, files in ki.items():
|
|
139
|
+
if match_kw(word, kw):
|
|
140
|
+
fs = files if not targets else [f for t in targets for f in plat_filter(files, t)]
|
|
141
|
+
for f in fs:
|
|
142
|
+
file_hits.setdefault(f, set()).add(word)
|
|
143
|
+
# 1b. 前端 view 路径直接匹配 (前端需求不再 0 命中代码文件的根因修复:
|
|
144
|
+
# view 路径如 views/cost/contractSubmission/index 含 contract 就命中)
|
|
145
|
+
# 用「路径段包含」而非裸子串, 避免 digital-clock 被考勤(clock)误命中。
|
|
146
|
+
def _seg_match(word, path_lower):
|
|
147
|
+
"""word 作为一个完整路径段命中 path (前后是 / 开头/结尾/分隔)。
|
|
148
|
+
例: 'clock' 命中 'time/clock/index' (clock 是独立段), 不命中 'digital-clock'。"""
|
|
149
|
+
if not word or len(word) < MIN_FUZZY_LEN:
|
|
150
|
+
return False
|
|
151
|
+
# 标准化: 前后加 / 让首尾段也能匹配
|
|
152
|
+
norm = '/' + path_lower + '/'
|
|
153
|
+
# word 含 / 时 (如 time/clock) 直接子串; 否则要求前后是边界
|
|
154
|
+
if '/' in word:
|
|
155
|
+
return word in norm
|
|
156
|
+
return ('/' + word + '/') in norm or ('/' + word + '.') in norm or ('-' + word + '-') in norm
|
|
157
|
+
views = fr.get('views') or []
|
|
158
|
+
for vp in views:
|
|
159
|
+
vpl = vp.lower()
|
|
160
|
+
for word in en_words:
|
|
161
|
+
if _seg_match(word, vpl):
|
|
162
|
+
# 还原成 fywl-ui/... 形式 (views 里是相对 src/views 的路径)
|
|
163
|
+
f = 'fywl-ui/apps/ics/src/views/' + vp
|
|
164
|
+
file_hits.setdefault(f, set()).add(word)
|
|
165
|
+
|
|
166
|
+
# 1c. 语义补充: 若关键词命中稀疏 (<=2 文件), 用 bge-small-zh 向量检索补一轮。
|
|
167
|
+
# 解决"中文业务词→英文代码"的跨语言漏召 (查"报销"命中 reimburse/expense)。
|
|
168
|
+
# 把语义命中的文件按低权重并入 file_hits, 不喧宾夺主 (关键词命中优先)。
|
|
169
|
+
if len(file_hits) <= 2:
|
|
170
|
+
try:
|
|
171
|
+
from domain.kg.graph.kg_semantic import semantic_search_files
|
|
172
|
+
sem = semantic_search_files(query, top_k=8)
|
|
173
|
+
if sem:
|
|
174
|
+
for fp, eid, cn, score in sem:
|
|
175
|
+
if targets:
|
|
176
|
+
matched = any(t.lower() in fp.lower() for t in targets)
|
|
177
|
+
if not matched:
|
|
178
|
+
continue
|
|
179
|
+
# 语义命中标 'sem' 词, 不计入 en_words 命中数 (排序靠后)
|
|
180
|
+
file_hits.setdefault(fp, set()).add('sem')
|
|
181
|
+
except Exception:
|
|
182
|
+
pass # 模型未装/无 embedding → 静默跳过
|
|
183
|
+
|
|
184
|
+
# 按命中词数排序, 取前 12
|
|
185
|
+
files_ranked = sorted(file_hits.items(), key=lambda x: len(x[1]), reverse=True)[:12]
|
|
186
|
+
|
|
187
|
+
# ---- 3. 相关字段: field -> (count, info, {命中的词}) ----
|
|
188
|
+
fm = si.get('field_map') or {}
|
|
189
|
+
field_hits = [] # (count, field, info, 命中词数)
|
|
190
|
+
for field, info in fm.items():
|
|
191
|
+
fl = field.lower()
|
|
192
|
+
hit_words = [w for w in en_words if len(w) >= MIN_FUZZY_LEN and w in fl]
|
|
193
|
+
if hit_words:
|
|
194
|
+
field_hits.append((info.get('count', 0), field, info, len(hit_words)))
|
|
195
|
+
field_hits.sort(key=lambda x: (x[3], x[0]), reverse=True)
|
|
196
|
+
|
|
197
|
+
# ---- 4. 相关历史 PRD ----
|
|
198
|
+
prd_hits = []
|
|
199
|
+
for pf, prd in prds.items():
|
|
200
|
+
text = (prd.get('title', '') + ' ' + ' '.join(prd.get('keywords', []))).lower()
|
|
201
|
+
hit_words = [w for w in en_words if w in text]
|
|
202
|
+
if hit_words:
|
|
203
|
+
score = sum(2 for _ in hit_words)
|
|
204
|
+
prd_hits.append((score, pf, prd.get('title', pf)))
|
|
205
|
+
prd_hits.sort(reverse=True)
|
|
206
|
+
|
|
207
|
+
# ---- 5. 相关 API ----
|
|
208
|
+
api_hits = [(ep, f, len([w for w in en_words if len(w) >= MIN_FUZZY_LEN
|
|
209
|
+
and (w in ep.lower() or w in f.lower())]))
|
|
210
|
+
for ep, f in apis.items()
|
|
211
|
+
if any(len(w) >= MIN_FUZZY_LEN and (w in ep.lower() or w in f.lower())
|
|
212
|
+
for w in en_words)]
|
|
213
|
+
api_hits.sort(key=lambda x: x[2], reverse=True)
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
'files': files_ranked,
|
|
217
|
+
'fields': field_hits,
|
|
218
|
+
'prds': prd_hits,
|
|
219
|
+
'apis': api_hits,
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _print_merged(query, platform, primary_cn, en_words, collected):
|
|
224
|
+
"""打印合并后的 8 段 (与 context_pack 格式兼容)。"""
|
|
225
|
+
targets = []
|
|
226
|
+
if platform and platform.lower() not in ('both', '两端'):
|
|
227
|
+
targets = [PLATFORM_MAP.get(platform.lower(), platform)]
|
|
228
|
+
elif platform:
|
|
229
|
+
targets = sorted(set(PLATFORM_MAP.values()))
|
|
230
|
+
|
|
231
|
+
print('# Prefetch: {} (platform: {})'.format(query, platform or 'all'))
|
|
232
|
+
print('预测主词 (中文业务词): ' + ', '.join(primary_cn) if primary_cn else '(无精确中文业务词)')
|
|
233
|
+
print('批量搜索词: ' + ' '.join(en_words))
|
|
234
|
+
print('提示: 这是一次性批量预取, 已合并多词命中。prefetch 之后不要再串行 search/find/grep。')
|
|
235
|
+
|
|
236
|
+
# 1. 相关代码文件
|
|
237
|
+
files_ranked = collected['files']
|
|
238
|
+
print('\n## 1. 相关代码文件 ({} 个, 取前 12, 多词命中排前)'.format(len(files_ranked)))
|
|
239
|
+
for f, ws in files_ranked:
|
|
240
|
+
full = 'data/code/' + f.replace('\\', '/')
|
|
241
|
+
print('- {} ({}词命中: {}) -> {}'.format(f, len(ws), ','.join(sorted(ws)[:3]), full))
|
|
242
|
+
if not files_ranked:
|
|
243
|
+
print('- (无命中 — 该功能可能为全新, 或换业务词重跑 prefetch)')
|
|
244
|
+
|
|
245
|
+
# 2. 同类页面示例 (固定信息, 直接列)
|
|
246
|
+
si = load('ui-style.json') or {}
|
|
247
|
+
print('\n## 2. 同类页面示例 (风格锚点, 读 1-2 个)')
|
|
248
|
+
shown = 0
|
|
249
|
+
for proj, pdata in sorted((si.get('projects') or {}).items()):
|
|
250
|
+
if targets and proj not in targets:
|
|
251
|
+
continue
|
|
252
|
+
if shown >= 4:
|
|
253
|
+
break
|
|
254
|
+
examples = pdata.get('page_examples', {})
|
|
255
|
+
for pt, fs in sorted(examples.items()):
|
|
256
|
+
if pt == 'unknown':
|
|
257
|
+
continue
|
|
258
|
+
fs_files = [item['file'] if isinstance(item, dict) else item for item in fs[:2]]
|
|
259
|
+
print('- [{}] {}: {}'.format(proj, pt, ' | '.join(fs_files)))
|
|
260
|
+
shown += 1
|
|
261
|
+
if shown >= 4:
|
|
262
|
+
break
|
|
263
|
+
|
|
264
|
+
# 3. 相关字段
|
|
265
|
+
field_hits = collected['fields']
|
|
266
|
+
print('\n## 3. 相关字段 (真实字段名->中文标题, 多词命中排前)')
|
|
267
|
+
for cnt, field, info, n in field_hits[:10]:
|
|
268
|
+
print('- {} -> {} ({}x, {}词命中)'.format(field, '/'.join(info.get('titles', [])[:3]), cnt, n))
|
|
269
|
+
if not field_hits:
|
|
270
|
+
print('- (无直接相关字段)')
|
|
271
|
+
|
|
272
|
+
# 4. 相关历史 PRD
|
|
273
|
+
prd_hits = collected['prds']
|
|
274
|
+
print('\n## 4. 相关历史 PRD (data/docs/prd/)')
|
|
275
|
+
for s, pf, title in prd_hits[:5]:
|
|
276
|
+
print('- {} - {}'.format(pf, title[:60]))
|
|
277
|
+
if not prd_hits:
|
|
278
|
+
print('- (无相关 PRD)')
|
|
279
|
+
|
|
280
|
+
# 5. 相关 API
|
|
281
|
+
api_hits = collected['apis']
|
|
282
|
+
print('\n## 5. 相关 API (取前 8 / 共 {}, 多词命中排前)'.format(len(api_hits)))
|
|
283
|
+
for ep, f, n in sorted(api_hits, key=lambda x: x[2], reverse=True)[:8]:
|
|
284
|
+
print('- {} -> {} ({}词命中)'.format(ep, f.split('/')[-1], n))
|
|
285
|
+
if not api_hits:
|
|
286
|
+
print('- (无相关 API)')
|
|
287
|
+
|
|
288
|
+
# 6. 风格/图标/模板速查 (固定)
|
|
289
|
+
print('\n## 6. 风格真源速查')
|
|
290
|
+
print('- Web Token: data/index/ref-vben-style.json (primary=hsl(214.58 86.34% 59.8%))')
|
|
291
|
+
print('- 图标 (禁 emoji): data/index/ref-icon.json (Web=antd 内联SVG, APP=vant 字体)')
|
|
292
|
+
print('- 模板: .qoder/templates/prototype-web.html / prototype-app.html')
|
|
293
|
+
print('- PRD 模板: .qoder/templates/prd-full-template.md / prd-quick-template.md')
|
|
294
|
+
|
|
295
|
+
# 7. Repo Wiki
|
|
296
|
+
code_dir = os.path.join(BASE, 'data', 'code')
|
|
297
|
+
wiki_results = []
|
|
298
|
+
try:
|
|
299
|
+
from repowiki import search_wiki, build_wiki_index
|
|
300
|
+
wiki_idx_path = os.path.join(BUILD_CACHE_DIR, 'wiki-index.json')
|
|
301
|
+
if not os.path.isfile(wiki_idx_path):
|
|
302
|
+
build_wiki_index(code_dir, wiki_idx_path)
|
|
303
|
+
cn_map = get_cn_map_with_auto()
|
|
304
|
+
# Wiki 用主词 + 整句各查一次, 合并 (Wiki 命中往往最精准)
|
|
305
|
+
wiki_q = primary_cn[0] if primary_cn else query
|
|
306
|
+
wiki_results = search_wiki(wiki_q, INDEX_DIR, code_dir, cn_map)
|
|
307
|
+
except Exception:
|
|
308
|
+
pass
|
|
309
|
+
if wiki_results:
|
|
310
|
+
print('\n## 7. Repo Wiki (语义级模块文档, 比源码更精准)')
|
|
311
|
+
print(' 相关模块文档 ({} 条, 带行号引用, 优先读这些):'.format(len(wiki_results)))
|
|
312
|
+
for i, wr in enumerate(wiki_results[:5], 1):
|
|
313
|
+
print(' {}. [{}] {}'.format(i, wr.get('project', '?'), wr.get('title', '')))
|
|
314
|
+
if wr.get('dependent_files'):
|
|
315
|
+
print(' 核心文件: ' + ', '.join(wr['dependent_files'][:2]))
|
|
316
|
+
if wr.get('md_path'):
|
|
317
|
+
print(' 文档: ' + os.path.relpath(wr['md_path'], BASE))
|
|
318
|
+
|
|
319
|
+
# 8. 数据库表结构 (主词查一次即可)
|
|
320
|
+
try:
|
|
321
|
+
from domain.kg.search.search_index import _get_kg_db
|
|
322
|
+
con = _get_kg_db()
|
|
323
|
+
if con is not None:
|
|
324
|
+
tbl_q = primary_cn[0] if primary_cn else query
|
|
325
|
+
kw_lower = tbl_q.lower()
|
|
326
|
+
import re as _re
|
|
327
|
+
snake = _re.sub(r'([a-z0-9])([A-Z])', r'\1_\2', tbl_q).lower()
|
|
328
|
+
tbl_hits = con.execute(
|
|
329
|
+
"SELECT DISTINCT table_name, db FROM db_tables "
|
|
330
|
+
"WHERE lower(table_name) LIKE ? OR lower(table_name) LIKE ? LIMIT 3",
|
|
331
|
+
['%' + kw_lower + '%', '%' + snake + '%']
|
|
332
|
+
).fetchall()
|
|
333
|
+
if tbl_hits:
|
|
334
|
+
print('\n## 8. 数据库表结构 (kg_db.duckdb, QAS测试环境)')
|
|
335
|
+
for tn, dbn in tbl_hits[:3]:
|
|
336
|
+
cols = con.execute(
|
|
337
|
+
"SELECT column_name, data_type FROM db_columns WHERE table_name = ? LIMIT 6",
|
|
338
|
+
[tn]
|
|
339
|
+
).fetchall()
|
|
340
|
+
print(' [{}.{}] {}'.format(dbn, tn, len(cols)))
|
|
341
|
+
for cn, dt in cols:
|
|
342
|
+
print(' {} ({})'.format(cn, dt))
|
|
343
|
+
except Exception:
|
|
344
|
+
pass # kg_db 不存在 → 跳过
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
# ── 入口 ──────────────────────────────────────────────────────────
|
|
348
|
+
|
|
349
|
+
def main():
|
|
350
|
+
args = sys.argv[1:]
|
|
351
|
+
platform = None
|
|
352
|
+
if '--platform' in args:
|
|
353
|
+
i = args.index('--platform')
|
|
354
|
+
platform = args[i + 1] if i + 1 < len(args) else None
|
|
355
|
+
args = args[:i] + args[i + 2:]
|
|
356
|
+
if not args:
|
|
357
|
+
print(__doc__)
|
|
358
|
+
return 1
|
|
359
|
+
query = args[0]
|
|
360
|
+
|
|
361
|
+
# 词预测
|
|
362
|
+
primary_cn, en_words = predict_words(query)
|
|
363
|
+
|
|
364
|
+
# 降级: 预测不出任何英文词 → 回退单词 context_pack (保持兼容)
|
|
365
|
+
if not en_words:
|
|
366
|
+
from domain.kg.search import context_pack as _cp
|
|
367
|
+
# 直接调 context_pack 的计算逻辑 (它会自己 print)
|
|
368
|
+
_cp._compute_and_print(query, platform, None)
|
|
369
|
+
return 0
|
|
370
|
+
|
|
371
|
+
targets = []
|
|
372
|
+
if platform and platform.lower() not in ('both', '两端'):
|
|
373
|
+
targets = [PLATFORM_MAP.get(platform.lower(), platform)]
|
|
374
|
+
elif platform:
|
|
375
|
+
targets = sorted(set(PLATFORM_MAP.values()))
|
|
376
|
+
|
|
377
|
+
# 批量合并取上下文
|
|
378
|
+
collected = _collect(targets, en_words, query)
|
|
379
|
+
_print_merged(query, platform, primary_cn, en_words, collected)
|
|
380
|
+
return 0
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
if __name__ == '__main__':
|
|
384
|
+
sys.exit(main())
|