@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
|
@@ -1,708 +1,871 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def
|
|
58
|
-
"""
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
except Exception
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
#
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
'
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
'
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
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
|
+
from foundation.core.paths import get_repo_root
|
|
13
|
+
|
|
14
|
+
"""
|
|
15
|
+
kg.py — QODER 知识图谱统一 CLI 入口(全系列通用)
|
|
16
|
+
|
|
17
|
+
把原本只在 QoderWork MCP (kg_mcp_server.py) 里的知识图谱能力,
|
|
18
|
+
统一成一套命令行子命令。Qoder IDE / Quest / CLI 直接跑;QoderWork 仍可
|
|
19
|
+
用 MCP,本脚本是新增的并列通道(功能等价)。
|
|
20
|
+
|
|
21
|
+
设计原则:
|
|
22
|
+
1. 不复制逻辑 — 调底层: search_index / context_pack / graph_traverse /
|
|
23
|
+
kg_duckdb / repowiki / fill_prototype / gen_design_doc
|
|
24
|
+
2. 全容错 — DuckDB/索引缺失优雅降级,给修复提示,绝不抛栈给用户
|
|
25
|
+
3. 参数统一 — 15 个子命令风格一致,对标 MCP 工具名
|
|
26
|
+
4. 纯文本输出 — UTF-8,无日志噪音,AI 直接读
|
|
27
|
+
|
|
28
|
+
用法:
|
|
29
|
+
python .qoder/scripts/kg.py <子命令> [参数]
|
|
30
|
+
|
|
31
|
+
python .qoder/scripts/kg.py search 考勤 --platform web
|
|
32
|
+
python .qoder/scripts/kg.py impact /asset
|
|
33
|
+
python .qoder/scripts/kg.py coverage
|
|
34
|
+
python .qoder/scripts/kg.py feature 资产管理
|
|
35
|
+
python .qoder/scripts/kg.py workflow assets
|
|
36
|
+
python .qoder/scripts/kg.py hop 资产管理 --depth 3
|
|
37
|
+
"""
|
|
38
|
+
import io
|
|
39
|
+
import os
|
|
40
|
+
import re
|
|
41
|
+
import sys
|
|
42
|
+
|
|
43
|
+
# UTF-8 输出(Windows 终端默认 GBK 会乱码)
|
|
44
|
+
try:
|
|
45
|
+
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
|
|
46
|
+
except Exception:
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
SCRIPTS_DIR = get_repo_root()
|
|
50
|
+
BASE_DIR = get_repo_root()
|
|
51
|
+
sys.path.insert(0, SCRIPTS_DIR)
|
|
52
|
+
_KG_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
53
|
+
if _KG_DIR not in sys.path: sys.path.insert(0, _KG_DIR) # 同包import
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# ── 路径与索引新鲜度(与底层一致)──────────────────────────────────────
|
|
57
|
+
def _index_dir():
|
|
58
|
+
"""获取 data/index 目录(延迟导入,避免顶层崩)。"""
|
|
59
|
+
try:
|
|
60
|
+
from foundation.core.paths import DATA_INDEX_DIR
|
|
61
|
+
return str(DATA_INDEX_DIR)
|
|
62
|
+
except Exception:
|
|
63
|
+
# 兜底: 常规布局
|
|
64
|
+
return os.path.join(BASE_DIR, 'data', 'index')
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _kg_db_path():
|
|
68
|
+
return os.path.join(_index_dir(), 'kg.duckdb')
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _has_duckdb():
|
|
72
|
+
"""知识图谱 DuckDB 是否已构建。"""
|
|
73
|
+
return os.path.isfile(_kg_db_path())
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _hint_rebuild(action_cn='知识图谱'):
|
|
77
|
+
"""DuckDB/索引缺失时的统一修复提示。"""
|
|
78
|
+
return (
|
|
79
|
+
"⚠️ %s未构建或缺失。\n"
|
|
80
|
+
"修复方法(任选其一):\n"
|
|
81
|
+
" 1. 对 AI 说「初始化」(/wl-init) — 自动刷新索引\n"
|
|
82
|
+
" 2. 手动构建: python .qoder/scripts/kg_build.py\n"
|
|
83
|
+
" 3. 自检修复: python .qoder/scripts/init_doctor.py --fix\n"
|
|
84
|
+
"本次查询无法继续,但其他子命令(不依赖图谱的)仍可用。"
|
|
85
|
+
% action_cn
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# ── 子命令实现 ────────────────────────────────────────────────────────
|
|
90
|
+
|
|
91
|
+
def cmd_search(args):
|
|
92
|
+
"""搜代码: 按关键词找代码文件。"""
|
|
93
|
+
if not args:
|
|
94
|
+
return "用法: kg.py search <关键词> [--platform web|app]"
|
|
95
|
+
kw, plat = _split_kw_platform(args)
|
|
96
|
+
if not kw:
|
|
97
|
+
return "请提供搜索关键词"
|
|
98
|
+
from domain.kg.search import search_index as S
|
|
99
|
+
# search_keywords(query, platform=None) 自带中文扩展,不需外层 expand
|
|
100
|
+
return _capture(S.search_keywords, kw, plat) or "无匹配结果"
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def cmd_api(args):
|
|
104
|
+
"""搜 API 端点。"""
|
|
105
|
+
if not args:
|
|
106
|
+
return "用法: kg.py api <关键词>"
|
|
107
|
+
from domain.kg.search import search_index as S
|
|
108
|
+
return _capture(S.search_api, args[0]) or "无匹配 API"
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def cmd_prd(args):
|
|
112
|
+
"""搜 PRD(防重复造轮子)。"""
|
|
113
|
+
if not args:
|
|
114
|
+
return "用法: kg.py prd <关键词>"
|
|
115
|
+
from domain.kg.search import search_index as S
|
|
116
|
+
return _capture(S.search_prds, args[0]) or "无匹配 PRD"
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def cmd_impact(args):
|
|
120
|
+
"""影响分析: 改某接口影响哪些前端页面/按钮。"""
|
|
121
|
+
if not args:
|
|
122
|
+
return "用法: kg.py impact <端点或关键词> 例: /asset 或 export"
|
|
123
|
+
from domain.kg.search import search_index as S
|
|
124
|
+
return _capture(S.search_impact, args[0]) or "未找到匹配端点"
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def cmd_context360(args):
|
|
128
|
+
"""符号 360 度视图: 端点/函数/处理器的全部关联。"""
|
|
129
|
+
if not args:
|
|
130
|
+
return "用法: kg.py context360 <符号> 例: handleExport 或 /system/role"
|
|
131
|
+
from domain.kg.search import search_index as S
|
|
132
|
+
return _capture(S.search_context360, args[0]) or "未找到匹配符号"
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def cmd_context(args):
|
|
136
|
+
"""上下文打包: 一次返回关键词相关的全部上下文。支持 --role 角色裁剪。"""
|
|
137
|
+
if not args:
|
|
138
|
+
return ("用法: kg.py context <关键词> [--platform web|app] "
|
|
139
|
+
"[--role pm|design|dev|test|admin]")
|
|
140
|
+
rest = list(args)
|
|
141
|
+
role = ''
|
|
142
|
+
plat = ''
|
|
143
|
+
# 解析 --role / --platform
|
|
144
|
+
rest2 = []
|
|
145
|
+
i = 0
|
|
146
|
+
while i < len(rest):
|
|
147
|
+
a = rest[i]
|
|
148
|
+
if a == '--role' and i + 1 < len(rest):
|
|
149
|
+
role = rest[i + 1]; i += 2; continue
|
|
150
|
+
if a == '--platform' and i + 1 < len(rest):
|
|
151
|
+
plat = rest[i + 1]; i += 2; continue
|
|
152
|
+
rest2.append(a); i += 1
|
|
153
|
+
kw = rest2[0] if rest2 else ''
|
|
154
|
+
if not kw:
|
|
155
|
+
return "请提供业务关键词"
|
|
156
|
+
from domain.kg.search import context_pack as C
|
|
157
|
+
full = _capture(C._compute_and_print, kw, plat, '')
|
|
158
|
+
if not full:
|
|
159
|
+
return "无匹配上下文"
|
|
160
|
+
if role:
|
|
161
|
+
return _filter_by_role(full, role)
|
|
162
|
+
return full
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def cmd_prefetch(args):
|
|
166
|
+
"""需求上下文预测 + 批量预取: 输入一句需求描述, 一次返回多词合并的上下文。
|
|
167
|
+
|
|
168
|
+
治"串行试探式搜索"(查一个词不命中 → 换词再查 6+ 次)的轮次爆炸。
|
|
169
|
+
支持 --platform。
|
|
170
|
+
"""
|
|
171
|
+
if not args:
|
|
172
|
+
return ("用法: kg.py prefetch \"<需求描述>\" [--platform web|app]\n"
|
|
173
|
+
"例: kg.py prefetch \"保险单OCR识别标注对比图\" --platform web")
|
|
174
|
+
rest = list(args)
|
|
175
|
+
plat = ''
|
|
176
|
+
rest2 = []
|
|
177
|
+
i = 0
|
|
178
|
+
while i < len(rest):
|
|
179
|
+
a = rest[i]
|
|
180
|
+
if a == '--platform' and i + 1 < len(rest):
|
|
181
|
+
plat = rest[i + 1]; i += 2; continue
|
|
182
|
+
rest2.append(a); i += 1
|
|
183
|
+
query = ' '.join(rest2) if rest2 else ''
|
|
184
|
+
if not query:
|
|
185
|
+
return "请提供需求描述(一句话)"
|
|
186
|
+
from domain.kg.search import prefetch as P
|
|
187
|
+
primary_cn, en_words = P.predict_words(query)
|
|
188
|
+
if not en_words:
|
|
189
|
+
# 降级: 预测不出词 → 单词 context_pack
|
|
190
|
+
from domain.kg.search import context_pack as C
|
|
191
|
+
return _capture(C._compute_and_print, query, plat, '') or "无匹配上下文"
|
|
192
|
+
targets = []
|
|
193
|
+
if plat and plat.lower() not in ('both', '两端'):
|
|
194
|
+
targets = [P.PLATFORM_MAP.get(plat.lower(), plat)]
|
|
195
|
+
elif plat:
|
|
196
|
+
targets = sorted(set(P.PLATFORM_MAP.values()))
|
|
197
|
+
collected = P._collect(targets, en_words)
|
|
198
|
+
return _capture(P._print_merged, query, plat, primary_cn, en_words, collected) or "无匹配上下文"
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def cmd_coverage(args):
|
|
202
|
+
"""功能×测试覆盖矩阵: 哪些功能有测试,哪些是盲区。"""
|
|
203
|
+
from domain.kg.graph.graph_traverse import CodeGraph
|
|
204
|
+
g = CodeGraph(_index_dir())
|
|
205
|
+
return g.coverage_matrix() or "无法生成覆盖矩阵(索引可能为空)"
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def cmd_feature(args):
|
|
209
|
+
"""功能画像: 一个功能的完整画像(端点+按钮+测试)。
|
|
210
|
+
|
|
211
|
+
DuckDB 优先(DuckDB 数据最全);DuckDB 缺失时回退到 CodeGraph 的 JSON 路径。
|
|
212
|
+
"""
|
|
213
|
+
if not args:
|
|
214
|
+
return "用法: kg.py feature <功能名> 例: 资产管理 或 asset 或 system"
|
|
215
|
+
feat = args[0]
|
|
216
|
+
# DuckDB 优先(数据最全,coverage 已验证可用)
|
|
217
|
+
if _has_duckdb():
|
|
218
|
+
try:
|
|
219
|
+
import duckdb
|
|
220
|
+
except ImportError:
|
|
221
|
+
return "未安装 duckdb。请运行: pip install duckdb"
|
|
222
|
+
con = duckdb.connect(_kg_db_path(), read_only=True)
|
|
223
|
+
try:
|
|
224
|
+
result = _feature_duckdb(con, feat)
|
|
225
|
+
if result:
|
|
226
|
+
return result
|
|
227
|
+
finally:
|
|
228
|
+
try: con.close()
|
|
229
|
+
except Exception: pass
|
|
230
|
+
# 回退: CodeGraph 的 JSON 实现(entity-registry.json 通常不存在)
|
|
231
|
+
from domain.kg.graph.graph_traverse import CodeGraph
|
|
232
|
+
g = CodeGraph(_index_dir())
|
|
233
|
+
return g.feature_overview(feat) or "未找到功能 '%s'" % feat
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _feature_duckdb(con, query):
|
|
237
|
+
"""从 DuckDB 查功能画像(feature → endpoints/buttons/tests)。
|
|
238
|
+
|
|
239
|
+
别名解析: aliases 精确 → canonical/cn 模糊 → entity_id 含关键词。
|
|
240
|
+
"""
|
|
241
|
+
q = (query or '').strip()
|
|
242
|
+
# 1) 解析为 FEATURE entity_id
|
|
243
|
+
target = None
|
|
244
|
+
resolved = con.execute(
|
|
245
|
+
"SELECT entity_id FROM aliases WHERE alias = ? "
|
|
246
|
+
"AND entity_id IN (SELECT id FROM entities WHERE type='FEATURE')",
|
|
247
|
+
[q.lower()]
|
|
248
|
+
).fetchone()
|
|
249
|
+
if resolved:
|
|
250
|
+
target = resolved[0]
|
|
251
|
+
else:
|
|
252
|
+
# canonical / cn 模糊
|
|
253
|
+
row = con.execute(
|
|
254
|
+
"SELECT id FROM entities WHERE type='FEATURE' "
|
|
255
|
+
"AND (LOWER(canonical)=? OR cn=? OR LOWER(canonical) LIKE '%'||?||'%' OR cn LIKE '%'||?||'%') "
|
|
256
|
+
"LIMIT 1",
|
|
257
|
+
[q.lower(), q, q.lower(), q]
|
|
258
|
+
).fetchone()
|
|
259
|
+
if row:
|
|
260
|
+
target = row[0]
|
|
261
|
+
if not target:
|
|
262
|
+
# 列出可用 feature 帮用户选
|
|
263
|
+
feats = con.execute(
|
|
264
|
+
"SELECT canonical, cn FROM entities WHERE type='FEATURE' AND cn<>'' ORDER BY cn LIMIT 30"
|
|
265
|
+
).fetchall()
|
|
266
|
+
if not feats:
|
|
267
|
+
# entities 表为空 — 语义层未构建 (需要 build_entity_registry 等脚本, 非数据缺失)
|
|
268
|
+
total = con.execute("SELECT COUNT(*) FROM entities").fetchone()[0]
|
|
269
|
+
if total == 0:
|
|
270
|
+
return ("未找到功能 '%s'。知识图谱语义层 (entities) 为空 — "
|
|
271
|
+
"需要管理员构建: npx @hupan56/wlkj run build_style_index.py "
|
|
272
|
+
"(生成 entity-registry 后由 kg_duckdb 导入)。" % q)
|
|
273
|
+
cands = ', '.join('%s(%s)' % (c, cn) if cn and cn != c else c for c, cn in feats)
|
|
274
|
+
return "未找到功能 '%s'。可用功能: %s" % (q, cands)
|
|
275
|
+
|
|
276
|
+
# 2) 取 feature 基本信息
|
|
277
|
+
info = con.execute(
|
|
278
|
+
"SELECT canonical, cn FROM entities WHERE id=?", [target]
|
|
279
|
+
).fetchone()
|
|
280
|
+
canon, cn = info if info else (target, target)
|
|
281
|
+
|
|
282
|
+
# 3) 统计端点 / 按钮
|
|
283
|
+
endpoints = con.execute(
|
|
284
|
+
"SELECT to_id FROM edges WHERE from_id=? AND edge_type='has_endpoint'", [target]
|
|
285
|
+
).fetchall()
|
|
286
|
+
buttons = con.execute(
|
|
287
|
+
"SELECT to_id FROM edges WHERE from_id=? AND edge_type='has_button'", [target]
|
|
288
|
+
).fetchall()
|
|
289
|
+
|
|
290
|
+
# 4) 关联测试: feature 的端点 -(handled_by)-> controller -(tested_by)-> test
|
|
291
|
+
try:
|
|
292
|
+
tests = con.execute("""
|
|
293
|
+
SELECT DISTINCT tb.to_id FROM edges he
|
|
294
|
+
JOIN edges tb ON tb.from_id = he.to_id AND tb.edge_type='tested_by'
|
|
295
|
+
WHERE he.from_id=? AND he.edge_type='handled_by'
|
|
296
|
+
""", [target]).fetchall()
|
|
297
|
+
except Exception:
|
|
298
|
+
tests = []
|
|
299
|
+
|
|
300
|
+
ep_ids = [e[0] for e in endpoints]
|
|
301
|
+
btn_ids = [b[0] for b in buttons]
|
|
302
|
+
test_ids = [t[0] for t in tests]
|
|
303
|
+
|
|
304
|
+
# 5) 组装画像
|
|
305
|
+
out = ['## 功能画像: %s (%s)' % (cn, canon)]
|
|
306
|
+
out.append(' 端点 %d 个 | 按钮操作 %d 个 | 关联测试 %d 个' % (
|
|
307
|
+
len(ep_ids), len(btn_ids), len(test_ids)))
|
|
308
|
+
out.append('')
|
|
309
|
+
|
|
310
|
+
out.append('### 端点 (%d)' % len(ep_ids))
|
|
311
|
+
for eid in ep_ids[:10]:
|
|
312
|
+
out.append(' %s' % eid)
|
|
313
|
+
if len(ep_ids) > 10:
|
|
314
|
+
out.append(' ... 还有 %d 个' % (len(ep_ids) - 10))
|
|
315
|
+
out.append('')
|
|
316
|
+
|
|
317
|
+
out.append('### 按钮操作 (%d)' % len(btn_ids))
|
|
318
|
+
# 提取按钮的操作动词(handleAdd/handleDel 等)
|
|
319
|
+
verbs = {}
|
|
320
|
+
for bid in btn_ids:
|
|
321
|
+
# bid 形如 B:path/to/file.vue:handleAdd
|
|
322
|
+
if ':' in bid:
|
|
323
|
+
handler = bid.rsplit(':', 1)[-1]
|
|
324
|
+
else:
|
|
325
|
+
handler = bid
|
|
326
|
+
verbs[handler] = verbs.get(handler, 0) + 1
|
|
327
|
+
top_verbs = sorted(verbs.items(), key=lambda x: -x[1])[:15]
|
|
328
|
+
out.append(' 操作分布: %s' % ', '.join('%s×%d' % (v, n) for v, n in top_verbs))
|
|
329
|
+
out.append('')
|
|
330
|
+
|
|
331
|
+
out.append('### 关联测试 (%d)' % len(test_ids))
|
|
332
|
+
shown = 0
|
|
333
|
+
for tid in test_ids:
|
|
334
|
+
if shown >= 6:
|
|
335
|
+
out.append(' ... 还有 %d 个' % (len(test_ids) - 6))
|
|
336
|
+
break
|
|
337
|
+
# tid 形如 T:ClassName.method,查 tests 表拿断言
|
|
338
|
+
if tid.startswith('T:'):
|
|
339
|
+
ref = tid[2:]
|
|
340
|
+
cls = method = ''
|
|
341
|
+
if '.' in ref:
|
|
342
|
+
cls, method = ref.rsplit('.', 1)
|
|
343
|
+
trow = con.execute(
|
|
344
|
+
"SELECT display_name, assertions FROM tests WHERE class=? AND method=? LIMIT 1",
|
|
345
|
+
[cls, method]
|
|
346
|
+
).fetchone()
|
|
347
|
+
if trow:
|
|
348
|
+
dn, asserts = trow
|
|
349
|
+
import json as _json
|
|
350
|
+
try:
|
|
351
|
+
a_list = _json.loads(asserts) if isinstance(asserts, str) else asserts
|
|
352
|
+
a_str = ', '.join(a_list[:3]) if a_list else ''
|
|
353
|
+
except Exception:
|
|
354
|
+
a_str = ''
|
|
355
|
+
out.append(' %s — %s%s' % (ref, dn or '', (' [断言: %s]' % a_str) if a_str else ''))
|
|
356
|
+
else:
|
|
357
|
+
out.append(' %s' % ref)
|
|
358
|
+
else:
|
|
359
|
+
out.append(' %s' % tid)
|
|
360
|
+
shown += 1
|
|
361
|
+
|
|
362
|
+
if len(test_ids) == 0:
|
|
363
|
+
out.append(' ⚠️ 该功能无关联测试,是回归测试盲区')
|
|
364
|
+
|
|
365
|
+
return '\n'.join(out)
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def cmd_workflow(args):
|
|
369
|
+
"""业务流程链: 一个模块的完整操作链(query→create→audit→...)。
|
|
370
|
+
|
|
371
|
+
优先匹配英文模块名(assets/cost/hr/...),中文输入返回候选。
|
|
372
|
+
数据源: DuckDB workflows 表。
|
|
373
|
+
"""
|
|
374
|
+
if not args:
|
|
375
|
+
return ("用法: kg.py workflow <模块名> 例: assets / cost / hr\n"
|
|
376
|
+
"可选模块: assets/cost/hr/maintenance/operation/"
|
|
377
|
+
"qualityAndSafety/veh/settings/dashboard/flow/systems/monitor/quasafe")
|
|
378
|
+
module = args[0]
|
|
379
|
+
if not _has_duckdb():
|
|
380
|
+
return _hint_rebuild('业务流程数据(DuckDB)')
|
|
381
|
+
try:
|
|
382
|
+
import duckdb
|
|
383
|
+
except ImportError:
|
|
384
|
+
return ("未安装 duckdb。请运行: pip install duckdb")
|
|
385
|
+
con = duckdb.connect(_kg_db_path(), read_only=True)
|
|
386
|
+
try:
|
|
387
|
+
return _workflow_query(con, module)
|
|
388
|
+
finally:
|
|
389
|
+
try: con.close()
|
|
390
|
+
except Exception: pass
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def _workflow_query(con, q):
|
|
394
|
+
"""workflows 表查询 — v3.0 f3: 转发到 common.kg_capabilities (单一真源, CLI/MCP共用)。"""
|
|
395
|
+
from domain.kg.kg_capabilities import workflow_query
|
|
396
|
+
return workflow_query(con, q)
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def cmd_hop(args):
|
|
400
|
+
"""多跳遍历: 从一个符号出发,递归 N 跳找全部可达实体。
|
|
401
|
+
|
|
402
|
+
知识图谱核心能力。支持别名解析(传中文/端点/函数名自动转 entity_id)。
|
|
403
|
+
"""
|
|
404
|
+
if not args:
|
|
405
|
+
return "用法: kg.py hop <符号> [--depth 1-5] 例: 资产管理 或 /asset/list"
|
|
406
|
+
symbol, depth = _split_symbol_depth(args)
|
|
407
|
+
if not _has_duckdb():
|
|
408
|
+
return _hint_rebuild('知识图谱(DuckDB)')
|
|
409
|
+
try:
|
|
410
|
+
import duckdb
|
|
411
|
+
except ImportError:
|
|
412
|
+
return "未安装 duckdb。请运行: pip install duckdb"
|
|
413
|
+
from domain.kg.storage import kg_duckdb as K
|
|
414
|
+
con = duckdb.connect(_kg_db_path(), read_only=True)
|
|
415
|
+
try:
|
|
416
|
+
# v3.0 f3: 核心查询转发到 common.kg_capabilities.hop_query (CLI/MCP共用)
|
|
417
|
+
from domain.kg.kg_capabilities import hop_query
|
|
418
|
+
return hop_query(con, symbol, depth, multi_hop_fn=K.multi_hop)
|
|
419
|
+
finally:
|
|
420
|
+
try: con.close()
|
|
421
|
+
except Exception: pass
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def cmd_wiki(args):
|
|
425
|
+
"""Repo Wiki 搜索 — v3.0 f3: 转发到 common.kg_capabilities.wiki_query (CLI/MCP共用)。"""
|
|
426
|
+
if not args:
|
|
427
|
+
return "用法: kg.py wiki <关键词>"
|
|
428
|
+
from domain.kg.kg_capabilities import wiki_query
|
|
429
|
+
result = wiki_query(args[0])
|
|
430
|
+
# CLI 额外提示
|
|
431
|
+
if 'matching' in result:
|
|
432
|
+
result += '\n\n 提示: 读 wiki 文档(带行号引用)比读源码更省 token'
|
|
433
|
+
return result
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def cmd_fill_prototype(args):
|
|
437
|
+
"""原型预填: 用真实代码数据自动填模板,输出 80% 完成的 HTML 草稿。"""
|
|
438
|
+
if not args:
|
|
439
|
+
return "用法: kg.py fill-prototype <关键词> [--platform web|app]"
|
|
440
|
+
kw, plat = _split_kw_platform(args)
|
|
441
|
+
plat = plat or 'web'
|
|
442
|
+
import subprocess as _sp
|
|
443
|
+
from foundation.core.paths import find_script
|
|
444
|
+
script = find_script('fill_prototype.py')
|
|
445
|
+
if script is None:
|
|
446
|
+
return "fill_prototype.py 未找到 (脚本迁移后路径未更新)"
|
|
447
|
+
try:
|
|
448
|
+
r = _sp.run([sys.executable, str(script), kw, '--platform', plat],
|
|
449
|
+
capture_output=True, text=True, encoding='utf-8',
|
|
450
|
+
errors='replace', timeout=30, cwd=str(script.parent))
|
|
451
|
+
return r.stdout or "fill_prototype 无输出(可能索引缺失)"
|
|
452
|
+
except Exception as e:
|
|
453
|
+
return "[error] fill_prototype: %s" % str(e)
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def cmd_design_system(args):
|
|
457
|
+
"""设计系统查询: 返回项目完整设计规范(布局指纹/颜色token/组件表/图标)。"""
|
|
458
|
+
plat = ''
|
|
459
|
+
if args and args[0] in ('--platform', '-p') and len(args) > 1:
|
|
460
|
+
plat = args[1]
|
|
461
|
+
import subprocess as _sp
|
|
462
|
+
design_md = os.path.join(BASE_DIR, 'data', 'design', 'DESIGN.md')
|
|
463
|
+
if not os.path.isfile(design_md):
|
|
464
|
+
from foundation.core.paths import find_script
|
|
465
|
+
gen_script = find_script('gen_design_doc.py')
|
|
466
|
+
if gen_script is not None:
|
|
467
|
+
_sp.run([sys.executable, str(gen_script)], capture_output=True, timeout=30)
|
|
468
|
+
if os.path.isfile(design_md):
|
|
469
|
+
content = open(design_md, encoding='utf-8').read()
|
|
470
|
+
if plat:
|
|
471
|
+
lines = content.split('\n')
|
|
472
|
+
result = []
|
|
473
|
+
in_section = False
|
|
474
|
+
for line in lines:
|
|
475
|
+
if line.startswith('### ') and plat.lower() in line.lower():
|
|
476
|
+
in_section = True
|
|
477
|
+
elif line.startswith('### ') and in_section:
|
|
478
|
+
break
|
|
479
|
+
if in_section:
|
|
480
|
+
result.append(line)
|
|
481
|
+
return '\n'.join(result) if result else content
|
|
482
|
+
return content
|
|
483
|
+
return "DESIGN.md 不存在且生成失败。请手动跑: python .qoder/scripts/design/gen_design_doc.py"
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def cmd_semantic(args):
|
|
487
|
+
"""语义搜索: 用自然语言找代码/API (概念检索, 不依赖精确关键词)。
|
|
488
|
+
|
|
489
|
+
需先构建 embedding 向量 (管理员): kg_semantic.py build-embeddings。
|
|
490
|
+
模型未装时给出安装提示, 不崩溃。
|
|
491
|
+
"""
|
|
492
|
+
if not args:
|
|
493
|
+
return ("用法: kg.py semantic <自然语言查询> [--top N]\n"
|
|
494
|
+
" 语义检索: 查'报销'能命中 expense/reimburse\n"
|
|
495
|
+
" 需先构建: python .qoder/scripts/domain/kg/graph/kg_semantic.py build-embeddings")
|
|
496
|
+
query = args[0]
|
|
497
|
+
top_k = 10
|
|
498
|
+
if '--top' in args:
|
|
499
|
+
idx = args.index('--top')
|
|
500
|
+
if idx + 1 < len(args):
|
|
501
|
+
try:
|
|
502
|
+
top_k = int(args[idx + 1])
|
|
503
|
+
except ValueError:
|
|
504
|
+
pass
|
|
505
|
+
from domain.kg.graph.kg_semantic import semantic_search, embedding_status
|
|
506
|
+
results = semantic_search(query, top_k=top_k)
|
|
507
|
+
if results is None:
|
|
508
|
+
st = embedding_status()
|
|
509
|
+
if not st['available']:
|
|
510
|
+
return ("语义检索未启用: sentence-transformers 未安装。\n"
|
|
511
|
+
"启用: pip install sentence-transformers (95MB模型首次自动下载)")
|
|
512
|
+
elif st['embeddings_count'] == 0:
|
|
513
|
+
return ("语义检索未构建向量。请管理员运行:\n"
|
|
514
|
+
" python .qoder/scripts/domain/kg/graph/kg_semantic.py build-embeddings")
|
|
515
|
+
if not results:
|
|
516
|
+
return "无匹配结果"
|
|
517
|
+
out = ["语义搜索: %s (top %d)" % (query, len(results))]
|
|
518
|
+
for eid, text, score in results:
|
|
519
|
+
out.append(" %.3f %s" % (score, text[:70]))
|
|
520
|
+
return '\n'.join(out)
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
# ── 中文文案/报错溯源 (grep-text): 单进程安全兜底, 替代 findstr/s 全盘扫描 ──
|
|
524
|
+
# 设计动机: 用户查"车辆不存在""项目车号"这类中文文案时, kg.py 的代码标识符索引
|
|
525
|
+
# 天然查不到 (索引按英文标识符建)。AI 若兜底去 findstr /s 或 os.walk 全库
|
|
526
|
+
# (8973+ 文件) → Windows 卡死 + 疯狂弹 cmd 窗口。本命令是这类需求的唯一安全通道:
|
|
527
|
+
# 单进程、限定 data/code、限定扩展名、有行数上限, 绝不递归弹子进程。
|
|
528
|
+
_GREP_EXTS = ('.java', '.vue', '.ts', '.tsx', '.js', '.jsx', '.xml', '.html',
|
|
529
|
+
'.json', '.sql', '.properties', '.yml', '.yaml', '.ftl', '.txt')
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
def cmd_grep_text(args):
|
|
533
|
+
"""中文文案/报错溯源: 在 data/code 里搜中文短语 (报错文案/字段标签/业务词)。
|
|
534
|
+
|
|
535
|
+
单进程 Python, 限定扩展名, 上限 200 命中 + 30 文件。绝不弹子进程窗口。
|
|
536
|
+
用法: kg.py grep-text <中文短语> [--ext java,vue] [--limit N] [--platform web|app]
|
|
537
|
+
"""
|
|
538
|
+
if not args:
|
|
539
|
+
return ("用法: kg.py grep-text <中文短语> [选项]\n"
|
|
540
|
+
" 在 data/code 里搜中文文案/报错/字段标签 (kg.py search 查不到中文时用这个)\n"
|
|
541
|
+
" --ext java,vue 限定文件类型 (默认: java/vue/ts/xml/json...)\n"
|
|
542
|
+
" --limit N 最多返回 N 条命中 (默认 50)\n"
|
|
543
|
+
" --platform web|app 只搜某平台源码\n"
|
|
544
|
+
"示例: kg.py grep-text 车辆不存在")
|
|
545
|
+
import os as _os
|
|
546
|
+
# kg.py 在 .qoder/scripts/domain/kg/ 下, 往上 4 级到 repo 根
|
|
547
|
+
here = _os.path.dirname(_os.path.abspath(__file__)) # .../domain/kg/
|
|
548
|
+
base = _os.path.dirname(_os.path.dirname(_os.path.dirname(_os.path.dirname(here)))) # repo 根
|
|
549
|
+
code_root = _os.path.join(base, 'data', 'code')
|
|
550
|
+
if not _os.path.isdir(code_root):
|
|
551
|
+
return ('data/code 不存在 (源码未克隆)。跑 python .qoder/scripts/deployment/setup/init_doctor.py --fix')
|
|
552
|
+
|
|
553
|
+
query = args[0]
|
|
554
|
+
# 解析选项
|
|
555
|
+
exts = set(_GREP_EXTS)
|
|
556
|
+
limit = 50
|
|
557
|
+
user_limit = None # 记录用户是否显式传了 --limit (决定是否启用 early_stop)
|
|
558
|
+
plat = ''
|
|
559
|
+
i = 1
|
|
560
|
+
while i < len(args):
|
|
561
|
+
a = args[i]
|
|
562
|
+
if a == '--ext' and i + 1 < len(args):
|
|
563
|
+
exts = set('.' + e.lstrip('.') for e in args[i + 1].split(',') if e.strip())
|
|
564
|
+
i += 2; continue
|
|
565
|
+
if a == '--limit' and i + 1 < len(args):
|
|
566
|
+
try:
|
|
567
|
+
limit = max(1, int(args[i + 1]))
|
|
568
|
+
user_limit = limit # 用户显式指定了, early_stop 不再介入
|
|
569
|
+
except ValueError: pass
|
|
570
|
+
i += 2; continue
|
|
571
|
+
if a == '--platform' and i + 1 < len(args):
|
|
572
|
+
plat = args[i + 1]; i += 2; continue
|
|
573
|
+
i += 1
|
|
574
|
+
|
|
575
|
+
# 平台 → 子目录过滤
|
|
576
|
+
proj_dirs = []
|
|
577
|
+
if not plat:
|
|
578
|
+
for d in sorted(_os.listdir(code_root)):
|
|
579
|
+
if _os.path.isdir(_os.path.join(code_root, d)) and not d.startswith('.'):
|
|
580
|
+
proj_dirs.append(d)
|
|
581
|
+
elif plat in ('web', 'pc'):
|
|
582
|
+
proj_dirs = [d for d in ('fywl-ui',) if _os.path.isdir(_os.path.join(code_root, d))]
|
|
583
|
+
elif plat in ('app', 'h5'):
|
|
584
|
+
proj_dirs = [d for d in ('Carmg-H5',) if _os.path.isdir(_os.path.join(code_root, d))]
|
|
585
|
+
else:
|
|
586
|
+
proj_dirs = [d for d in sorted(_os.listdir(code_root))
|
|
587
|
+
if _os.path.isdir(_os.path.join(code_root, d)) and not d.startswith('.')]
|
|
588
|
+
|
|
589
|
+
hits = []
|
|
590
|
+
files_hit = 0
|
|
591
|
+
scanned = 0
|
|
592
|
+
import time as _time
|
|
593
|
+
deadline = _time.monotonic() + 15 # 硬超时 15s: data/code 大目录上避免 30s+ 卡死
|
|
594
|
+
timed_out = False
|
|
595
|
+
# 够用即停阈值: 仅当用户没显式指定 --limit 时启用 (默认场景快速溯源)。
|
|
596
|
+
# 用户传了 --limit 就完全尊重它, 不被 early_stop 截断 (原 max(10,min(limit,30))
|
|
597
|
+
# 会把 --limit 50 截成 30、把 --limit 5 抬到 10, 违背用户契约)。
|
|
598
|
+
early_stop = 30 if user_limit is None else None
|
|
599
|
+
for proj in proj_dirs:
|
|
600
|
+
proj_path = _os.path.join(code_root, proj)
|
|
601
|
+
for dirpath, dirnames, filenames in _os.walk(proj_path):
|
|
602
|
+
# 跳过明显的依赖/构建目录, 加速
|
|
603
|
+
dirnames[:] = [d for d in dirnames if d not in
|
|
604
|
+
('node_modules', '.git', 'target', 'dist', 'build', '.idea', '__pycache__')]
|
|
605
|
+
for fn in filenames:
|
|
606
|
+
# 文件级超时检查: 避免单个大文件卡死目录内循环 (15s 硬超时在目录级
|
|
607
|
+
# 检查时形同虚设)。已找到结果才超时退出, 没找到继续扫。
|
|
608
|
+
if hits and _time.monotonic() > deadline:
|
|
609
|
+
timed_out = True
|
|
610
|
+
break
|
|
611
|
+
ext = _os.path.splitext(fn)[1].lower()
|
|
612
|
+
if ext not in exts:
|
|
613
|
+
continue
|
|
614
|
+
fp = _os.path.join(dirpath, fn)
|
|
615
|
+
# 跳过大文件 (>500KB 多半是生成/压缩文件, 拖慢扫描)
|
|
616
|
+
try:
|
|
617
|
+
if _os.path.getsize(fp) > 500_000:
|
|
618
|
+
continue
|
|
619
|
+
except OSError:
|
|
620
|
+
continue
|
|
621
|
+
scanned += 1
|
|
622
|
+
done = False
|
|
623
|
+
try:
|
|
624
|
+
with open(fp, 'r', encoding='utf-8', errors='ignore') as fh:
|
|
625
|
+
for lineno, line in enumerate(fh, 1):
|
|
626
|
+
# 逐文件行级超时检查: 极大文件逐行读也能及时退出
|
|
627
|
+
if lineno % 20000 == 0 and hits and _time.monotonic() > deadline:
|
|
628
|
+
timed_out = True
|
|
629
|
+
done = True
|
|
630
|
+
break
|
|
631
|
+
if query in line:
|
|
632
|
+
rel = _os.path.relpath(fp, code_root).replace('\\', '/')
|
|
633
|
+
snippet = line.strip()[:120]
|
|
634
|
+
hits.append((rel, lineno, snippet))
|
|
635
|
+
if len(hits) >= limit:
|
|
636
|
+
done = True
|
|
637
|
+
break
|
|
638
|
+
# 够用即停 (仅默认场景): 与 limit 同级行内检查,
|
|
639
|
+
# 避免单文件多行命中跨过 early_stop (原34>30问题)
|
|
640
|
+
if early_stop is not None and len(hits) >= early_stop:
|
|
641
|
+
done = True
|
|
642
|
+
break
|
|
643
|
+
except (OSError, UnicodeDecodeError):
|
|
644
|
+
pass
|
|
645
|
+
if done or len(hits) >= limit:
|
|
646
|
+
break
|
|
647
|
+
# 够用即停 (仅默认场景; 用户显式 limit 时 early_stop=None 跳过)
|
|
648
|
+
if early_stop is not None and len(hits) >= early_stop:
|
|
649
|
+
break
|
|
650
|
+
if timed_out or len(hits) >= limit or (early_stop is not None and len(hits) >= early_stop):
|
|
651
|
+
break
|
|
652
|
+
if timed_out or len(hits) >= limit or (early_stop is not None and len(hits) >= early_stop):
|
|
653
|
+
break
|
|
654
|
+
|
|
655
|
+
# 统计命中文件数 (去重)
|
|
656
|
+
files_hit = len(set(h[0] for h in hits))
|
|
657
|
+
if not hits:
|
|
658
|
+
return ('未找到 "%s" (扫描 %d 个文件, %s)\n'
|
|
659
|
+
'提示: 换近义词, 或用 kg.py semantic "%s" 语义检索' % (
|
|
660
|
+
query, scanned, '/'.join(proj_dirs) or 'data/code', query))
|
|
661
|
+
out = ['grep-text: "%s" → %d 命中 / %d 文件 (扫描 %d 文件%s)' % (
|
|
662
|
+
query, len(hits), files_hit, scanned, ', ⏱15s超时已返回部分结果' if timed_out else '')]
|
|
663
|
+
last_file = ''
|
|
664
|
+
for rel, lineno, snippet in hits:
|
|
665
|
+
if rel != last_file:
|
|
666
|
+
out.append('\n■ %s' % rel)
|
|
667
|
+
last_file = rel
|
|
668
|
+
out.append(' L%d: %s' % (lineno, snippet))
|
|
669
|
+
return '\n'.join(out)
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
# ── 工具函数 ──────────────────────────────────────────────────────────
|
|
673
|
+
|
|
674
|
+
def _capture(func, *args, **kwargs):
|
|
675
|
+
"""运行函数,捕获其 stdout 输出(底层脚本用 print 输出)。"""
|
|
676
|
+
old = sys.stdout
|
|
677
|
+
buf = io.StringIO()
|
|
678
|
+
sys.stdout = buf
|
|
679
|
+
try:
|
|
680
|
+
func(*args, **kwargs)
|
|
681
|
+
except Exception as e:
|
|
682
|
+
buf.write('\n[error] %s' % str(e))
|
|
683
|
+
finally:
|
|
684
|
+
sys.stdout = old
|
|
685
|
+
return buf.getvalue().strip()
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
def _split_kw_platform(args):
|
|
689
|
+
"""从参数里拆出 keyword 和 --platform 值。"""
|
|
690
|
+
rest = list(args)
|
|
691
|
+
plat = ''
|
|
692
|
+
out = []
|
|
693
|
+
i = 0
|
|
694
|
+
while i < len(rest):
|
|
695
|
+
a = rest[i]
|
|
696
|
+
if a == '--platform' and i + 1 < len(rest):
|
|
697
|
+
plat = rest[i + 1]; i += 2; continue
|
|
698
|
+
out.append(a); i += 1
|
|
699
|
+
kw = out[0] if out else ''
|
|
700
|
+
return kw, plat
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
def _split_symbol_depth(args):
|
|
704
|
+
"""从参数里拆出 symbol 和 --depth 值(默认 3)。"""
|
|
705
|
+
rest = list(args)
|
|
706
|
+
depth = 3
|
|
707
|
+
out = []
|
|
708
|
+
i = 0
|
|
709
|
+
while i < len(rest):
|
|
710
|
+
a = rest[i]
|
|
711
|
+
if a == '--depth' and i + 1 < len(rest):
|
|
712
|
+
try: depth = int(rest[i + 1])
|
|
713
|
+
except (ValueError, TypeError): depth = 3
|
|
714
|
+
i += 2; continue
|
|
715
|
+
out.append(a); i += 1
|
|
716
|
+
return (out[0] if out else ''), max(1, min(5, depth))
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
# Role-based section filtering for context_pack output
|
|
720
|
+
# v3.0: 单一真源移到 common/roles.py, 消除与 kg_mcp_server.py 的逐字重复
|
|
721
|
+
try:
|
|
722
|
+
from foundation.identity.roles import ROLE_SECTIONS, filter_by_role as _filter_by_role_imported
|
|
723
|
+
_filter_by_role = _filter_by_role_imported # 保持旧调用名 _filter_by_role 兼容
|
|
724
|
+
except Exception:
|
|
725
|
+
# 回退: 内联定义 (common 不可用时, 与 common/roles.py 保持一致)
|
|
726
|
+
ROLE_SECTIONS = {
|
|
727
|
+
'pm': {'1', '2', '2b', '3', '4', '5', '7'},
|
|
728
|
+
'design': {'2', '2b', '3', '6'},
|
|
729
|
+
'dev': {'1', '3', '5', '7'},
|
|
730
|
+
'test': {'1', '4', '5'},
|
|
731
|
+
'admin': {'4', '5'},
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
def _filter_by_role(text, role):
|
|
735
|
+
visible = ROLE_SECTIONS.get((role or '').lower())
|
|
736
|
+
if not visible:
|
|
737
|
+
return text
|
|
738
|
+
sections = re.split(r'(\n## \d+[ab]?\.)', text)
|
|
739
|
+
result = sections[0]
|
|
740
|
+
i = 1
|
|
741
|
+
while i < len(sections):
|
|
742
|
+
header = sections[i]
|
|
743
|
+
num_m = re.match(r'\n## (\d+[ab]?)\.', header)
|
|
744
|
+
if num_m and num_m.group(1) in visible:
|
|
745
|
+
result += header
|
|
746
|
+
if i + 1 < len(sections):
|
|
747
|
+
result += sections[i + 1]
|
|
748
|
+
i += 2
|
|
749
|
+
else:
|
|
750
|
+
i += 2
|
|
751
|
+
return result.strip()
|
|
752
|
+
|
|
753
|
+
|
|
754
|
+
# ── 子命令注册表 ──────────────────────────────────────────────────────
|
|
755
|
+
# (子命令名, 处理函数, 中文说明)
|
|
756
|
+
COMMANDS = [
|
|
757
|
+
('search', cmd_search, '搜代码: 按关键词找代码文件'),
|
|
758
|
+
('api', cmd_api, '搜 API 端点'),
|
|
759
|
+
('prd', cmd_prd, '搜 PRD(防重复造轮子)'),
|
|
760
|
+
('impact', cmd_impact, '影响分析: 改某接口影响哪些页面/按钮'),
|
|
761
|
+
('context360', cmd_context360, '符号 360 度视图(端点/函数/处理器)'),
|
|
762
|
+
('context', cmd_context, '上下文打包(代码+页面+字段+PRD+API,可 --role 角色裁剪)'),
|
|
763
|
+
('prefetch', cmd_prefetch, '需求上下文预测+批量预取(一句需求→多词合并,治串行搜索)'),
|
|
764
|
+
('coverage', cmd_coverage, '功能×测试覆盖矩阵(标出回归盲区)'),
|
|
765
|
+
('feature', cmd_feature, '功能画像(端点+按钮+测试+页面)'),
|
|
766
|
+
('workflow', cmd_workflow, '业务流程链(query→create→audit→...)'),
|
|
767
|
+
('hop', cmd_hop, '多跳遍历(知识图谱核心,从一符号递归 N 跳)'),
|
|
768
|
+
('wiki', cmd_wiki, 'Repo Wiki 搜索(模块语义文档)'),
|
|
769
|
+
('fill-prototype', cmd_fill_prototype, '原型预填(真实数据填模板,输出 80% HTML)'),
|
|
770
|
+
('design-system', cmd_design_system, '设计系统查询(布局指纹/颜色token/组件表)'),
|
|
771
|
+
('semantic', cmd_semantic, '语义搜索(概念检索:查"报销"命中 expense)'),
|
|
772
|
+
('grep-text', cmd_grep_text, '中文文案/报错溯源(findstr 替代,单进程不弹窗)'),
|
|
773
|
+
]
|
|
774
|
+
|
|
775
|
+
|
|
776
|
+
def _usage():
|
|
777
|
+
"""打印用法清单。"""
|
|
778
|
+
lines = [
|
|
779
|
+
'kg.py — QODER 知识图谱统一 CLI(全系列通用)',
|
|
780
|
+
'',
|
|
781
|
+
'用法: python .qoder/scripts/kg.py <子命令> [参数]',
|
|
782
|
+
'',
|
|
783
|
+
'16 个子命令:',
|
|
784
|
+
]
|
|
785
|
+
for name, _, desc in COMMANDS:
|
|
786
|
+
lines.append(' %-16s %s' % (name, desc))
|
|
787
|
+
lines += [
|
|
788
|
+
'',
|
|
789
|
+
'示例:',
|
|
790
|
+
' kg.py search 考勤 --platform web',
|
|
791
|
+
' kg.py prefetch "保险单OCR识别" --platform web # 写PRD前先批量预取',
|
|
792
|
+
' kg.py impact /asset # 改 /asset 影响谁',
|
|
793
|
+
' kg.py coverage # 哪些功能没测试',
|
|
794
|
+
' kg.py feature 资产管理 # 资产管理功能画像',
|
|
795
|
+
' kg.py workflow assets # 资产模块业务流程',
|
|
796
|
+
' kg.py hop 资产管理 --depth 3 # 从资产管理出发 3 跳遍历',
|
|
797
|
+
' kg.py context 车辆 --role pm # PM 视角的上下文打包',
|
|
798
|
+
' kg.py grep-text 车辆不存在 # 中文报错文案溯源 (findstr 安全替代)',
|
|
799
|
+
'',
|
|
800
|
+
'QoderWork 环境: AI 也会直接调 MCP 工具(mcp__qoder-knowledge-graph__*),功能等价。',
|
|
801
|
+
]
|
|
802
|
+
return '\n'.join(lines)
|
|
803
|
+
|
|
804
|
+
|
|
805
|
+
def _try_daemon(cmd, rest):
|
|
806
|
+
"""尝试走 daemon 热路径。命中返回 True (已输出结果), 未命中返回 False (回退冷路径)。
|
|
807
|
+
|
|
808
|
+
daemon 常驻模型+连接, semantic 26.8s→0.08s。连不上/出错则透明回退。
|
|
809
|
+
"""
|
|
810
|
+
try:
|
|
811
|
+
from kgd import kgd_client, DAEMON_COMMANDS
|
|
812
|
+
except Exception:
|
|
813
|
+
return False # kgd 模块不可用 → 回退
|
|
814
|
+
if cmd not in DAEMON_COMMANDS:
|
|
815
|
+
return False # 该命令不走 daemon
|
|
816
|
+
res = kgd_client.call(cmd, rest)
|
|
817
|
+
if res is None:
|
|
818
|
+
return False # daemon 不在线/出错 → 回退
|
|
819
|
+
text, _secs = res
|
|
820
|
+
print(text)
|
|
821
|
+
return True
|
|
822
|
+
|
|
823
|
+
|
|
824
|
+
def main(argv=None):
|
|
825
|
+
argv = argv if argv is not None else sys.argv[1:]
|
|
826
|
+
|
|
827
|
+
# 解析 --no-daemon: 强制冷路径 (调试用)。剥离后不影响后续参数解析。
|
|
828
|
+
use_daemon = True
|
|
829
|
+
if '--no-daemon' in argv:
|
|
830
|
+
use_daemon = False
|
|
831
|
+
argv = [a for a in argv if a != '--no-daemon']
|
|
832
|
+
|
|
833
|
+
if not argv or argv[0] in ('-h', '--help', 'help'):
|
|
834
|
+
print(_usage())
|
|
835
|
+
return 0
|
|
836
|
+
cmd = argv[0]
|
|
837
|
+
rest = argv[1:]
|
|
838
|
+
# 查找子命令
|
|
839
|
+
handler = None
|
|
840
|
+
for name, fn, _ in COMMANDS:
|
|
841
|
+
if name == cmd:
|
|
842
|
+
handler = fn
|
|
843
|
+
break
|
|
844
|
+
if handler is None:
|
|
845
|
+
print("未知子命令: %s" % cmd)
|
|
846
|
+
print(_usage())
|
|
847
|
+
return 1
|
|
848
|
+
|
|
849
|
+
# ⚡ 优先走 daemon 热路径 (模型/连接已常驻, 秒回)
|
|
850
|
+
if use_daemon and _try_daemon(cmd, rest):
|
|
851
|
+
return 0
|
|
852
|
+
|
|
853
|
+
# 冷路径: 直接执行 handler (daemon 不在线 / --no-daemon / 不支持的命令)
|
|
854
|
+
# semantic 在 daemon 不在线时给加速提示 (它单次要 ~27s)
|
|
855
|
+
if cmd == 'semantic' and use_daemon:
|
|
856
|
+
print('[提示] daemon 未运行, semantic 将冷启动 (~27s)。')
|
|
857
|
+
print(' 加速: python .qoder/scripts/domain/kg/server/kgd.py start (常驻后 semantic → 0.08s)')
|
|
858
|
+
# 执行(全容错,绝不抛栈)
|
|
859
|
+
try:
|
|
860
|
+
result = handler(rest)
|
|
861
|
+
if result:
|
|
862
|
+
print(result)
|
|
863
|
+
except Exception as e:
|
|
864
|
+
print("[error] %s 执行失败: %s" % (cmd, str(e)))
|
|
865
|
+
print("如果是索引/图谱问题,尝试: python .qoder/scripts/init_doctor.py --fix")
|
|
866
|
+
return 1
|
|
867
|
+
return 0
|
|
868
|
+
|
|
869
|
+
|
|
870
|
+
if __name__ == '__main__':
|
|
871
|
+
sys.exit(main())
|