@hupan56/wlkj 2.7.11 → 3.0.0
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 +375 -78
- package/package.json +29 -29
- package/templates/.qoder/.runtime/ctx-cache-5660152f1d6dd819.md +23 -0
- package/templates/.qoder/.runtime/ctx-cache-afdce0dac06b25b0.md +23 -0
- package/templates/.qoder/.runtime/search-cache-eae7644e7b122f35.txt +1 -0
- package/templates/.qoder/learning/eval-history.jsonl +28 -0
- package/templates/data/index/wiki-index.json +8 -0
- 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 +159 -161
- package/templates/qoder/commands/optional/wl-report.md +4 -4
- package/templates/qoder/commands/optional/wl-status.md +2 -2
- package/templates/qoder/commands/wl-code.md +2 -2
- package/templates/qoder/commands/wl-design.md +2 -2
- package/templates/qoder/commands/wl-init.md +3 -3
- package/templates/qoder/commands/wl-prd.md +2 -2
- package/templates/qoder/commands/wl-req.md +43 -0
- package/templates/qoder/commands/wl-search.md +8 -8
- package/templates/qoder/commands/wl-task.md +17 -17
- package/templates/qoder/commands/wl-test.md +41 -15
- package/templates/qoder/config.yaml +17 -1
- package/templates/qoder/hooks/session-start.py +12 -22
- package/templates/qoder/nul +4 -0
- package/templates/qoder/rules/wl-pipeline.md +22 -48
- package/templates/qoder/scripts/README.md +139 -0
- package/templates/qoder/scripts/common/autotest_auth.py +109 -0
- package/templates/qoder/scripts/common/bootstrap.py +145 -0
- package/templates/qoder/scripts/common/check_publish.py +98 -0
- package/templates/qoder/scripts/common/cmd_registry.py +112 -0
- package/templates/qoder/scripts/common/config.py +187 -0
- package/templates/qoder/scripts/common/contract.py +317 -0
- package/templates/qoder/scripts/common/developer.py +2 -1
- package/templates/qoder/scripts/common/feishu.py +10 -9
- package/templates/qoder/scripts/common/guard.py +159 -0
- package/templates/qoder/scripts/common/identity.py +121 -2
- package/templates/qoder/scripts/common/kg_capabilities.py +182 -0
- package/templates/qoder/scripts/common/mcp_base.py +268 -0
- package/templates/qoder/scripts/common/paths.py +187 -1
- package/templates/qoder/scripts/common/result.py +223 -0
- package/templates/qoder/scripts/common/roles.py +60 -0
- package/templates/qoder/scripts/common/task_utils.py +21 -9
- package/templates/qoder/scripts/common/test_extract.py +115 -0
- package/templates/qoder/scripts/kg/__init__.py +11 -0
- package/templates/qoder/scripts/kg/build_entity_registry.py +196 -0
- package/templates/qoder/scripts/kg/build_relations.py +127 -0
- package/templates/qoder/scripts/{build_style_index.py → kg/build_style_index.py} +39 -10
- package/templates/qoder/scripts/kg/build_workflows.py +144 -0
- package/templates/qoder/scripts/{context_pack.py → kg/context_pack.py} +18 -11
- package/templates/qoder/scripts/{enrich_prompt.py → kg/enrich_prompt.py} +232 -226
- package/templates/qoder/scripts/{extract_api_params.py → kg/extract.py} +398 -246
- package/templates/qoder/scripts/{kg.py → kg/kg.py} +638 -708
- package/templates/qoder/scripts/{kg_build.py → kg/kg_build.py} +618 -612
- package/templates/qoder/scripts/{kg_build_db.py → kg/kg_build_db.py} +333 -327
- package/templates/qoder/scripts/{kg_duckdb.py → kg/kg_duckdb.py} +38 -37
- package/templates/qoder/scripts/{kg_incremental.py → kg/kg_incremental.py} +420 -393
- package/templates/qoder/scripts/{kg_link_db.py → kg/kg_link_db.py} +230 -224
- package/templates/qoder/scripts/{kg_semantic.py → kg/kg_semantic.py} +156 -150
- package/templates/qoder/scripts/kg/prefetch.py +359 -0
- package/templates/qoder/scripts/{search_index.py → kg/search_index.py} +70 -14
- package/templates/qoder/scripts/mcp/__init__.py +11 -0
- package/templates/qoder/scripts/{kg_mcp_server.py → mcp/kg_mcp_server.py} +77 -272
- package/templates/qoder/scripts/{lanhu_stdio_wrapper.py → mcp/lanhu_stdio_wrapper.py} +125 -119
- package/templates/qoder/scripts/{check_mcp.py → mcp/mcp_doctor.py} +515 -298
- package/templates/qoder/scripts/{mcp_launcher.py → mcp/mcp_launcher.py} +442 -414
- package/templates/qoder/scripts/{mysql_mcp_server.py → mcp/mysql_mcp_server.py} +347 -396
- package/templates/qoder/scripts/{zentao_mcp_server.py → mcp/zentao_mcp_server.py} +384 -424
- package/templates/qoder/scripts/report/__init__.py +11 -0
- package/templates/qoder/scripts/{add_session.py → report/add_session.py} +250 -244
- package/templates/qoder/scripts/{archive_prd.py → report/archive_prd.py} +383 -377
- package/templates/qoder/scripts/{eval_prd.py → report/eval_prd.py} +73 -11
- package/templates/qoder/scripts/{export.py → report/export.py} +63 -0
- package/templates/qoder/scripts/{fill_prototype.py → report/fill_prototype.py} +6 -0
- package/templates/qoder/scripts/{gen_design_doc.py → report/gen_design_doc.py} +400 -394
- package/templates/qoder/scripts/{learn.py → report/learn.py} +152 -146
- package/templates/qoder/scripts/{learn_aggregate.py → report/learn_aggregate.py} +207 -201
- package/templates/qoder/scripts/{report.py → report/report.py} +287 -281
- package/templates/qoder/scripts/report/req.py +222 -0
- package/templates/qoder/scripts/report/role.py +33 -0
- package/templates/qoder/scripts/{status.py → report/status.py} +634 -628
- package/templates/qoder/scripts/setup/__init__.py +11 -0
- package/templates/qoder/scripts/setup/carriers.py +662 -0
- package/templates/qoder/scripts/{init_doctor.py → setup/init_doctor.py} +63 -26
- package/templates/qoder/scripts/{install_qoderwork.py → setup/install_qoderwork.py} +36 -26
- package/templates/qoder/scripts/{platform_doctor.py → setup/platform_doctor.py} +265 -259
- package/templates/qoder/scripts/{repo_root.py → setup/repo_root.py} +112 -106
- package/templates/qoder/scripts/{setup.py → setup/setup.py} +147 -0
- package/templates/qoder/scripts/{setup_lanhu.py → setup/setup_lanhu.py} +973 -963
- package/templates/qoder/scripts/task/__init__.py +11 -0
- package/templates/qoder/scripts/{git_sync.py → task/git_sync.py} +52 -31
- package/templates/qoder/scripts/{syncgate.py → task/syncgate.py} +6 -0
- package/templates/qoder/scripts/task/task.py +221 -0
- package/templates/qoder/scripts/task/task_lifecycle.py +596 -0
- package/templates/qoder/scripts/task/task_query.py +161 -0
- package/templates/qoder/scripts/task/task_relations.py +424 -0
- package/templates/qoder/scripts/{team_sync.py → task/team_sync.py} +93 -20
- package/templates/qoder/scripts/test/__init__.py +11 -0
- package/templates/qoder/scripts/{autotest.py → test/autotest.py} +1174 -1751
- package/templates/qoder/scripts/{autotest_batch.py → test/autotest_batch.py} +242 -224
- package/templates/qoder/scripts/test/autotest_data.py +675 -0
- package/templates/qoder/scripts/{autotest_run.py → test/autotest_run.py} +309 -297
- package/templates/qoder/scripts/{benchmark.py → test/benchmark.py} +6 -0
- package/templates/qoder/scripts/{kg_auto_login.py → test/kg_auto_login.py} +202 -196
- package/templates/qoder/scripts/{kg_test_runner.py → test/kg_test_runner.py} +7 -1
- package/templates/qoder/scripts/{page_probe.py → test/page_probe.py} +465 -459
- package/templates/qoder/scripts/wlkj.py +116 -0
- package/templates/qoder/settings.json +1 -10
- package/templates/qoder/skills/design-import/SKILL.md +226 -226
- package/templates/qoder/skills/design-review/SKILL.md +82 -82
- package/templates/qoder/skills/prd-generator/SKILL.md +26 -16
- package/templates/qoder/skills/prd-review/SKILL.md +5 -5
- package/templates/qoder/skills/prototype-generator/SKILL.md +256 -256
- package/templates/qoder/skills/spec-coder/SKILL.md +4 -4
- package/templates/qoder/skills/spec-generator/SKILL.md +4 -4
- package/templates/qoder/skills/test-generator/SKILL.md +5 -5
- package/templates/qoder/skills/wl-code/SKILL.md +4 -4
- package/templates/qoder/skills/wl-commit/SKILL.md +4 -4
- package/templates/qoder/skills/wl-design/SKILL.md +3 -3
- package/templates/qoder/skills/wl-init/SKILL.md +8 -8
- package/templates/qoder/skills/wl-insight/SKILL.md +5 -5
- package/templates/qoder/skills/wl-prd-full/SKILL.md +6 -6
- package/templates/qoder/skills/wl-prd-quick/SKILL.md +6 -6
- package/templates/qoder/skills/wl-prd-review/SKILL.md +4 -4
- package/templates/qoder/skills/wl-report/SKILL.md +7 -7
- package/templates/qoder/skills/wl-search/SKILL.md +13 -13
- package/templates/qoder/skills/wl-spec/SKILL.md +5 -5
- package/templates/qoder/skills/wl-status/SKILL.md +5 -5
- package/templates/qoder/skills/wl-task/SKILL.md +6 -6
- package/templates/qoder/skills/wl-test/SKILL.md +102 -39
- package/templates/root/AGENTS.md +39 -40
- 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_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_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/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/skills/prompt-enrich/SKILL.md +0 -90
- package/templates/qoder/skills/prototype-generator/SKILL.md.zcode-79180-2af4721f-f9a6-412c-88db-c0af680d211b.tmp +0 -0
- /package/templates/qoder/scripts/{secure-ls.js → test/secure-ls.js} +0 -0
|
@@ -1,708 +1,638 @@
|
|
|
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
|
-
python .qoder/scripts/kg.py
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
sys.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
return _capture(S.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
def
|
|
98
|
-
"""搜
|
|
99
|
-
if not args:
|
|
100
|
-
return "用法: kg.py
|
|
101
|
-
import search_index as S
|
|
102
|
-
return _capture(S.
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
def
|
|
106
|
-
"""
|
|
107
|
-
if not args:
|
|
108
|
-
return "用法: kg.py
|
|
109
|
-
import search_index as S
|
|
110
|
-
return _capture(S.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
def
|
|
114
|
-
"""
|
|
115
|
-
if not args:
|
|
116
|
-
return "用法: kg.py
|
|
117
|
-
import search_index as S
|
|
118
|
-
return _capture(S.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
def
|
|
122
|
-
"""
|
|
123
|
-
if not args:
|
|
124
|
-
return
|
|
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
|
-
if
|
|
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
|
-
else
|
|
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
|
-
out.append('
|
|
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
|
-
|
|
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
|
-
('context360', cmd_context360, '符号 360 度视图(端点/函数/处理器)'),
|
|
640
|
-
('context', cmd_context, '上下文打包(代码+页面+字段+PRD+API,可 --role 角色裁剪)'),
|
|
641
|
-
('coverage', cmd_coverage, '功能×测试覆盖矩阵(标出回归盲区)'),
|
|
642
|
-
('feature', cmd_feature, '功能画像(端点+按钮+测试+页面)'),
|
|
643
|
-
('workflow', cmd_workflow, '业务流程链(query→create→audit→...)'),
|
|
644
|
-
('hop', cmd_hop, '多跳遍历(知识图谱核心,从一符号递归 N 跳)'),
|
|
645
|
-
('wiki', cmd_wiki, 'Repo Wiki 搜索(模块语义文档)'),
|
|
646
|
-
('fill-prototype', cmd_fill_prototype, '原型预填(真实数据填模板,输出 80% HTML)'),
|
|
647
|
-
('design-system', cmd_design_system, '设计系统查询(布局指纹/颜色token/组件表)'),
|
|
648
|
-
]
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
def _usage():
|
|
652
|
-
"""打印用法清单。"""
|
|
653
|
-
lines = [
|
|
654
|
-
'kg.py — QODER 知识图谱统一 CLI(全系列通用)',
|
|
655
|
-
'',
|
|
656
|
-
'用法: python .qoder/scripts/kg.py <子命令> [参数]',
|
|
657
|
-
'',
|
|
658
|
-
'13 个子命令:',
|
|
659
|
-
]
|
|
660
|
-
for name, _, desc in COMMANDS:
|
|
661
|
-
lines.append(' %-16s %s' % (name, desc))
|
|
662
|
-
lines += [
|
|
663
|
-
'',
|
|
664
|
-
'示例:',
|
|
665
|
-
' kg.py search 考勤 --platform web',
|
|
666
|
-
' kg.py impact /asset # 改 /asset 影响谁',
|
|
667
|
-
' kg.py coverage # 哪些功能没测试',
|
|
668
|
-
' kg.py feature 资产管理 # 资产管理功能画像',
|
|
669
|
-
' kg.py workflow assets # 资产模块业务流程',
|
|
670
|
-
' kg.py hop 资产管理 --depth 3 # 从资产管理出发 3 跳遍历',
|
|
671
|
-
' kg.py context 车辆 --role pm # PM 视角的上下文打包',
|
|
672
|
-
'',
|
|
673
|
-
'QoderWork 环境: AI 也会直接调 MCP 工具(mcp__qoder-knowledge-graph__*),功能等价。',
|
|
674
|
-
]
|
|
675
|
-
return '\n'.join(lines)
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
def main(argv=None):
|
|
679
|
-
argv = argv if argv is not None else sys.argv[1:]
|
|
680
|
-
if not argv or argv[0] in ('-h', '--help', 'help'):
|
|
681
|
-
print(_usage())
|
|
682
|
-
return 0
|
|
683
|
-
cmd = argv[0]
|
|
684
|
-
rest = argv[1:]
|
|
685
|
-
# 查找子命令
|
|
686
|
-
handler = None
|
|
687
|
-
for name, fn, _ in COMMANDS:
|
|
688
|
-
if name == cmd:
|
|
689
|
-
handler = fn
|
|
690
|
-
break
|
|
691
|
-
if handler is None:
|
|
692
|
-
print("未知子命令: %s" % cmd)
|
|
693
|
-
print(_usage())
|
|
694
|
-
return 1
|
|
695
|
-
# 执行(全容错,绝不抛栈)
|
|
696
|
-
try:
|
|
697
|
-
result = handler(rest)
|
|
698
|
-
if result:
|
|
699
|
-
print(result)
|
|
700
|
-
except Exception as e:
|
|
701
|
-
print("[error] %s 执行失败: %s" % (cmd, str(e)))
|
|
702
|
-
print("如果是索引/图谱问题,尝试: python .qoder/scripts/init_doctor.py --fix")
|
|
703
|
-
return 1
|
|
704
|
-
return 0
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
if __name__ == '__main__':
|
|
708
|
-
sys.exit(main())
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# v3.0 路径自举: 引导到 common/bootstrap, 统一 sys.path 逻辑
|
|
3
|
+
import os as _o, sys as _s
|
|
4
|
+
_cp = _o.path.join(_o.path.dirname(_o.path.dirname(_o.path.abspath(__file__))), 'common')
|
|
5
|
+
if _cp not in _s.path: _s.path.insert(0, _cp)
|
|
6
|
+
from bootstrap import setup; setup()
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
kg.py — QODER 知识图谱统一 CLI 入口(全系列通用)
|
|
10
|
+
|
|
11
|
+
把原本只在 QoderWork MCP (kg_mcp_server.py) 里的知识图谱能力,
|
|
12
|
+
统一成一套命令行子命令。Qoder IDE / Quest / CLI 直接跑;QoderWork 仍可
|
|
13
|
+
用 MCP,本脚本是新增的并列通道(功能等价)。
|
|
14
|
+
|
|
15
|
+
设计原则:
|
|
16
|
+
1. 不复制逻辑 — 调底层: search_index / context_pack / graph_traverse /
|
|
17
|
+
kg_duckdb / repowiki / fill_prototype / gen_design_doc
|
|
18
|
+
2. 全容错 — DuckDB/索引缺失优雅降级,给修复提示,绝不抛栈给用户
|
|
19
|
+
3. 参数统一 — 13 个子命令风格一致,对标 MCP 工具名
|
|
20
|
+
4. 纯文本输出 — UTF-8,无日志噪音,AI 直接读
|
|
21
|
+
|
|
22
|
+
用法:
|
|
23
|
+
python .qoder/scripts/kg.py <子命令> [参数]
|
|
24
|
+
|
|
25
|
+
python .qoder/scripts/kg.py search 考勤 --platform web
|
|
26
|
+
python .qoder/scripts/kg.py impact /asset
|
|
27
|
+
python .qoder/scripts/kg.py coverage
|
|
28
|
+
python .qoder/scripts/kg.py feature 资产管理
|
|
29
|
+
python .qoder/scripts/kg.py workflow assets
|
|
30
|
+
python .qoder/scripts/kg.py hop 资产管理 --depth 3
|
|
31
|
+
"""
|
|
32
|
+
import io
|
|
33
|
+
import os
|
|
34
|
+
import re
|
|
35
|
+
import sys
|
|
36
|
+
|
|
37
|
+
# UTF-8 输出(Windows 终端默认 GBK 会乱码)
|
|
38
|
+
try:
|
|
39
|
+
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
|
|
40
|
+
except Exception:
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
SCRIPTS_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # 子包→scripts/根
|
|
44
|
+
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(SCRIPTS_DIR))) # 多一层: 子包→scripts→repo # repo root
|
|
45
|
+
sys.path.insert(0, SCRIPTS_DIR)
|
|
46
|
+
_KG_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
47
|
+
if _KG_DIR not in sys.path: sys.path.insert(0, _KG_DIR) # 同包import
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# ── 路径与索引新鲜度(与底层一致)──────────────────────────────────────
|
|
51
|
+
def _index_dir():
|
|
52
|
+
"""获取 data/index 目录(延迟导入,避免顶层崩)。"""
|
|
53
|
+
try:
|
|
54
|
+
from common.paths import DATA_INDEX_DIR
|
|
55
|
+
return str(DATA_INDEX_DIR)
|
|
56
|
+
except Exception:
|
|
57
|
+
# 兜底: 常规布局
|
|
58
|
+
return os.path.join(BASE_DIR, 'data', 'index')
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _kg_db_path():
|
|
62
|
+
return os.path.join(_index_dir(), 'kg.duckdb')
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _has_duckdb():
|
|
66
|
+
"""知识图谱 DuckDB 是否已构建。"""
|
|
67
|
+
return os.path.isfile(_kg_db_path())
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _hint_rebuild(action_cn='知识图谱'):
|
|
71
|
+
"""DuckDB/索引缺失时的统一修复提示。"""
|
|
72
|
+
return (
|
|
73
|
+
"⚠️ %s未构建或缺失。\n"
|
|
74
|
+
"修复方法(任选其一):\n"
|
|
75
|
+
" 1. 对 AI 说「初始化」(/wl-init) — 自动刷新索引\n"
|
|
76
|
+
" 2. 手动构建: python .qoder/scripts/kg_build.py\n"
|
|
77
|
+
" 3. 自检修复: python .qoder/scripts/init_doctor.py --fix\n"
|
|
78
|
+
"本次查询无法继续,但其他子命令(不依赖图谱的)仍可用。"
|
|
79
|
+
% action_cn
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# ── 子命令实现 ────────────────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
def cmd_search(args):
|
|
86
|
+
"""搜代码: 按关键词找代码文件。"""
|
|
87
|
+
if not args:
|
|
88
|
+
return "用法: kg.py search <关键词> [--platform web|app]"
|
|
89
|
+
kw, plat = _split_kw_platform(args)
|
|
90
|
+
if not kw:
|
|
91
|
+
return "请提供搜索关键词"
|
|
92
|
+
import search_index as S
|
|
93
|
+
# search_keywords(query, platform=None) 自带中文扩展,不需外层 expand
|
|
94
|
+
return _capture(S.search_keywords, kw, plat) or "无匹配结果"
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def cmd_api(args):
|
|
98
|
+
"""搜 API 端点。"""
|
|
99
|
+
if not args:
|
|
100
|
+
return "用法: kg.py api <关键词>"
|
|
101
|
+
import search_index as S
|
|
102
|
+
return _capture(S.search_api, args[0]) or "无匹配 API"
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def cmd_prd(args):
|
|
106
|
+
"""搜 PRD(防重复造轮子)。"""
|
|
107
|
+
if not args:
|
|
108
|
+
return "用法: kg.py prd <关键词>"
|
|
109
|
+
import search_index as S
|
|
110
|
+
return _capture(S.search_prds, args[0]) or "无匹配 PRD"
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def cmd_impact(args):
|
|
114
|
+
"""影响分析: 改某接口影响哪些前端页面/按钮。"""
|
|
115
|
+
if not args:
|
|
116
|
+
return "用法: kg.py impact <端点或关键词> 例: /asset 或 export"
|
|
117
|
+
import search_index as S
|
|
118
|
+
return _capture(S.search_impact, args[0]) or "未找到匹配端点"
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def cmd_context360(args):
|
|
122
|
+
"""符号 360 度视图: 端点/函数/处理器的全部关联。"""
|
|
123
|
+
if not args:
|
|
124
|
+
return "用法: kg.py context360 <符号> 例: handleExport 或 /system/role"
|
|
125
|
+
import search_index as S
|
|
126
|
+
return _capture(S.search_context360, args[0]) or "未找到匹配符号"
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def cmd_context(args):
|
|
130
|
+
"""上下文打包: 一次返回关键词相关的全部上下文。支持 --role 角色裁剪。"""
|
|
131
|
+
if not args:
|
|
132
|
+
return ("用法: kg.py context <关键词> [--platform web|app] "
|
|
133
|
+
"[--role pm|design|dev|test|admin]")
|
|
134
|
+
rest = list(args)
|
|
135
|
+
role = ''
|
|
136
|
+
plat = ''
|
|
137
|
+
# 解析 --role / --platform
|
|
138
|
+
rest2 = []
|
|
139
|
+
i = 0
|
|
140
|
+
while i < len(rest):
|
|
141
|
+
a = rest[i]
|
|
142
|
+
if a == '--role' and i + 1 < len(rest):
|
|
143
|
+
role = rest[i + 1]; i += 2; continue
|
|
144
|
+
if a == '--platform' and i + 1 < len(rest):
|
|
145
|
+
plat = rest[i + 1]; i += 2; continue
|
|
146
|
+
rest2.append(a); i += 1
|
|
147
|
+
kw = rest2[0] if rest2 else ''
|
|
148
|
+
if not kw:
|
|
149
|
+
return "请提供业务关键词"
|
|
150
|
+
import context_pack as C
|
|
151
|
+
full = _capture(C._compute_and_print, kw, plat, '')
|
|
152
|
+
if not full:
|
|
153
|
+
return "无匹配上下文"
|
|
154
|
+
if role:
|
|
155
|
+
return _filter_by_role(full, role)
|
|
156
|
+
return full
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def cmd_prefetch(args):
|
|
160
|
+
"""需求上下文预测 + 批量预取: 输入一句需求描述, 一次返回多词合并的上下文。
|
|
161
|
+
|
|
162
|
+
治"串行试探式搜索"(查一个词不命中 → 换词再查 6+ 次)的轮次爆炸。
|
|
163
|
+
支持 --platform。
|
|
164
|
+
"""
|
|
165
|
+
if not args:
|
|
166
|
+
return ("用法: kg.py prefetch \"<需求描述>\" [--platform web|app]\n"
|
|
167
|
+
"例: kg.py prefetch \"保险单OCR识别标注对比图\" --platform web")
|
|
168
|
+
rest = list(args)
|
|
169
|
+
plat = ''
|
|
170
|
+
rest2 = []
|
|
171
|
+
i = 0
|
|
172
|
+
while i < len(rest):
|
|
173
|
+
a = rest[i]
|
|
174
|
+
if a == '--platform' and i + 1 < len(rest):
|
|
175
|
+
plat = rest[i + 1]; i += 2; continue
|
|
176
|
+
rest2.append(a); i += 1
|
|
177
|
+
query = ' '.join(rest2) if rest2 else ''
|
|
178
|
+
if not query:
|
|
179
|
+
return "请提供需求描述(一句话)"
|
|
180
|
+
import prefetch as P
|
|
181
|
+
primary_cn, en_words = P.predict_words(query)
|
|
182
|
+
if not en_words:
|
|
183
|
+
# 降级: 预测不出词 → 单词 context_pack
|
|
184
|
+
import context_pack as C
|
|
185
|
+
return _capture(C._compute_and_print, query, plat, '') or "无匹配上下文"
|
|
186
|
+
targets = []
|
|
187
|
+
if plat and plat.lower() not in ('both', '两端'):
|
|
188
|
+
targets = [P.PLATFORM_MAP.get(plat.lower(), plat)]
|
|
189
|
+
elif plat:
|
|
190
|
+
targets = sorted(set(P.PLATFORM_MAP.values()))
|
|
191
|
+
collected = P._collect(targets, en_words)
|
|
192
|
+
return _capture(P._print_merged, query, plat, primary_cn, en_words, collected) or "无匹配上下文"
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def cmd_coverage(args):
|
|
196
|
+
"""功能×测试覆盖矩阵: 哪些功能有测试,哪些是盲区。"""
|
|
197
|
+
from common.graph_traverse import CodeGraph
|
|
198
|
+
g = CodeGraph(_index_dir())
|
|
199
|
+
return g.coverage_matrix() or "无法生成覆盖矩阵(索引可能为空)"
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def cmd_feature(args):
|
|
203
|
+
"""功能画像: 一个功能的完整画像(端点+按钮+测试)。
|
|
204
|
+
|
|
205
|
+
DuckDB 优先(DuckDB 数据最全);DuckDB 缺失时回退到 CodeGraph 的 JSON 路径。
|
|
206
|
+
"""
|
|
207
|
+
if not args:
|
|
208
|
+
return "用法: kg.py feature <功能名> 例: 资产管理 或 asset 或 system"
|
|
209
|
+
feat = args[0]
|
|
210
|
+
# DuckDB 优先(数据最全,coverage 已验证可用)
|
|
211
|
+
if _has_duckdb():
|
|
212
|
+
try:
|
|
213
|
+
import duckdb
|
|
214
|
+
except ImportError:
|
|
215
|
+
return "未安装 duckdb。请运行: pip install duckdb"
|
|
216
|
+
con = duckdb.connect(_kg_db_path(), read_only=True)
|
|
217
|
+
try:
|
|
218
|
+
result = _feature_duckdb(con, feat)
|
|
219
|
+
if result:
|
|
220
|
+
return result
|
|
221
|
+
finally:
|
|
222
|
+
try: con.close()
|
|
223
|
+
except Exception: pass
|
|
224
|
+
# 回退: CodeGraph 的 JSON 实现(entity-registry.json 通常不存在)
|
|
225
|
+
from common.graph_traverse import CodeGraph
|
|
226
|
+
g = CodeGraph(_index_dir())
|
|
227
|
+
return g.feature_overview(feat) or "未找到功能 '%s'" % feat
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _feature_duckdb(con, query):
|
|
231
|
+
"""从 DuckDB 查功能画像(feature → endpoints/buttons/tests)。
|
|
232
|
+
|
|
233
|
+
别名解析: aliases 精确 → canonical/cn 模糊 → entity_id 含关键词。
|
|
234
|
+
"""
|
|
235
|
+
q = (query or '').strip()
|
|
236
|
+
# 1) 解析为 FEATURE entity_id
|
|
237
|
+
target = None
|
|
238
|
+
resolved = con.execute(
|
|
239
|
+
"SELECT entity_id FROM aliases WHERE alias = ? "
|
|
240
|
+
"AND entity_id IN (SELECT id FROM entities WHERE type='FEATURE')",
|
|
241
|
+
[q.lower()]
|
|
242
|
+
).fetchone()
|
|
243
|
+
if resolved:
|
|
244
|
+
target = resolved[0]
|
|
245
|
+
else:
|
|
246
|
+
# canonical / cn 模糊
|
|
247
|
+
row = con.execute(
|
|
248
|
+
"SELECT id FROM entities WHERE type='FEATURE' "
|
|
249
|
+
"AND (LOWER(canonical)=? OR cn=? OR LOWER(canonical) LIKE '%'||?||'%' OR cn LIKE '%'||?||'%') "
|
|
250
|
+
"LIMIT 1",
|
|
251
|
+
[q.lower(), q, q.lower(), q]
|
|
252
|
+
).fetchone()
|
|
253
|
+
if row:
|
|
254
|
+
target = row[0]
|
|
255
|
+
if not target:
|
|
256
|
+
# 列出可用 feature 帮用户选
|
|
257
|
+
feats = con.execute(
|
|
258
|
+
"SELECT canonical, cn FROM entities WHERE type='FEATURE' AND cn<>'' ORDER BY cn LIMIT 30"
|
|
259
|
+
).fetchall()
|
|
260
|
+
if not feats:
|
|
261
|
+
# entities 表为空 — 语义层未构建 (需要 build_entity_registry 等脚本, 非数据缺失)
|
|
262
|
+
total = con.execute("SELECT COUNT(*) FROM entities").fetchone()[0]
|
|
263
|
+
if total == 0:
|
|
264
|
+
return ("未找到功能 '%s'。知识图谱语义层 (entities) 为空 — "
|
|
265
|
+
"需要管理员构建: npx @hupan56/wlkj run build_style_index.py "
|
|
266
|
+
"(生成 entity-registry 后由 kg_duckdb 导入)。" % q)
|
|
267
|
+
cands = ', '.join('%s(%s)' % (c, cn) if cn and cn != c else c for c, cn in feats)
|
|
268
|
+
return "未找到功能 '%s'。可用功能: %s" % (q, cands)
|
|
269
|
+
|
|
270
|
+
# 2) 取 feature 基本信息
|
|
271
|
+
info = con.execute(
|
|
272
|
+
"SELECT canonical, cn FROM entities WHERE id=?", [target]
|
|
273
|
+
).fetchone()
|
|
274
|
+
canon, cn = info if info else (target, target)
|
|
275
|
+
|
|
276
|
+
# 3) 统计端点 / 按钮
|
|
277
|
+
endpoints = con.execute(
|
|
278
|
+
"SELECT to_id FROM edges WHERE from_id=? AND edge_type='has_endpoint'", [target]
|
|
279
|
+
).fetchall()
|
|
280
|
+
buttons = con.execute(
|
|
281
|
+
"SELECT to_id FROM edges WHERE from_id=? AND edge_type='has_button'", [target]
|
|
282
|
+
).fetchall()
|
|
283
|
+
|
|
284
|
+
# 4) 关联测试: feature 的端点 -(handled_by)-> controller -(tested_by)-> test
|
|
285
|
+
try:
|
|
286
|
+
tests = con.execute("""
|
|
287
|
+
SELECT DISTINCT tb.to_id FROM edges he
|
|
288
|
+
JOIN edges tb ON tb.from_id = he.to_id AND tb.edge_type='tested_by'
|
|
289
|
+
WHERE he.from_id=? AND he.edge_type='handled_by'
|
|
290
|
+
""", [target]).fetchall()
|
|
291
|
+
except Exception:
|
|
292
|
+
tests = []
|
|
293
|
+
|
|
294
|
+
ep_ids = [e[0] for e in endpoints]
|
|
295
|
+
btn_ids = [b[0] for b in buttons]
|
|
296
|
+
test_ids = [t[0] for t in tests]
|
|
297
|
+
|
|
298
|
+
# 5) 组装画像
|
|
299
|
+
out = ['## 功能画像: %s (%s)' % (cn, canon)]
|
|
300
|
+
out.append(' 端点 %d 个 | 按钮操作 %d 个 | 关联测试 %d 个' % (
|
|
301
|
+
len(ep_ids), len(btn_ids), len(test_ids)))
|
|
302
|
+
out.append('')
|
|
303
|
+
|
|
304
|
+
out.append('### 端点 (%d)' % len(ep_ids))
|
|
305
|
+
for eid in ep_ids[:10]:
|
|
306
|
+
out.append(' %s' % eid)
|
|
307
|
+
if len(ep_ids) > 10:
|
|
308
|
+
out.append(' ... 还有 %d 个' % (len(ep_ids) - 10))
|
|
309
|
+
out.append('')
|
|
310
|
+
|
|
311
|
+
out.append('### 按钮操作 (%d)' % len(btn_ids))
|
|
312
|
+
# 提取按钮的操作动词(handleAdd/handleDel 等)
|
|
313
|
+
verbs = {}
|
|
314
|
+
for bid in btn_ids:
|
|
315
|
+
# bid 形如 B:path/to/file.vue:handleAdd
|
|
316
|
+
if ':' in bid:
|
|
317
|
+
handler = bid.rsplit(':', 1)[-1]
|
|
318
|
+
else:
|
|
319
|
+
handler = bid
|
|
320
|
+
verbs[handler] = verbs.get(handler, 0) + 1
|
|
321
|
+
top_verbs = sorted(verbs.items(), key=lambda x: -x[1])[:15]
|
|
322
|
+
out.append(' 操作分布: %s' % ', '.join('%s×%d' % (v, n) for v, n in top_verbs))
|
|
323
|
+
out.append('')
|
|
324
|
+
|
|
325
|
+
out.append('### 关联测试 (%d)' % len(test_ids))
|
|
326
|
+
shown = 0
|
|
327
|
+
for tid in test_ids:
|
|
328
|
+
if shown >= 6:
|
|
329
|
+
out.append(' ... 还有 %d 个' % (len(test_ids) - 6))
|
|
330
|
+
break
|
|
331
|
+
# tid 形如 T:ClassName.method,查 tests 表拿断言
|
|
332
|
+
if tid.startswith('T:'):
|
|
333
|
+
ref = tid[2:]
|
|
334
|
+
cls = method = ''
|
|
335
|
+
if '.' in ref:
|
|
336
|
+
cls, method = ref.rsplit('.', 1)
|
|
337
|
+
trow = con.execute(
|
|
338
|
+
"SELECT display_name, assertions FROM tests WHERE class=? AND method=? LIMIT 1",
|
|
339
|
+
[cls, method]
|
|
340
|
+
).fetchone()
|
|
341
|
+
if trow:
|
|
342
|
+
dn, asserts = trow
|
|
343
|
+
import json as _json
|
|
344
|
+
try:
|
|
345
|
+
a_list = _json.loads(asserts) if isinstance(asserts, str) else asserts
|
|
346
|
+
a_str = ', '.join(a_list[:3]) if a_list else ''
|
|
347
|
+
except Exception:
|
|
348
|
+
a_str = ''
|
|
349
|
+
out.append(' %s — %s%s' % (ref, dn or '', (' [断言: %s]' % a_str) if a_str else ''))
|
|
350
|
+
else:
|
|
351
|
+
out.append(' %s' % ref)
|
|
352
|
+
else:
|
|
353
|
+
out.append(' %s' % tid)
|
|
354
|
+
shown += 1
|
|
355
|
+
|
|
356
|
+
if len(test_ids) == 0:
|
|
357
|
+
out.append(' ⚠️ 该功能无关联测试,是回归测试盲区')
|
|
358
|
+
|
|
359
|
+
return '\n'.join(out)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def cmd_workflow(args):
|
|
363
|
+
"""业务流程链: 一个模块的完整操作链(query→create→audit→...)。
|
|
364
|
+
|
|
365
|
+
优先匹配英文模块名(assets/cost/hr/...),中文输入返回候选。
|
|
366
|
+
数据源: DuckDB workflows 表。
|
|
367
|
+
"""
|
|
368
|
+
if not args:
|
|
369
|
+
return ("用法: kg.py workflow <模块名> 例: assets / cost / hr\n"
|
|
370
|
+
"可选模块: assets/cost/hr/maintenance/operation/"
|
|
371
|
+
"qualityAndSafety/veh/settings/dashboard/flow/systems/monitor/quasafe")
|
|
372
|
+
module = args[0]
|
|
373
|
+
if not _has_duckdb():
|
|
374
|
+
return _hint_rebuild('业务流程数据(DuckDB)')
|
|
375
|
+
try:
|
|
376
|
+
import duckdb
|
|
377
|
+
except ImportError:
|
|
378
|
+
return ("未安装 duckdb。请运行: pip install duckdb")
|
|
379
|
+
con = duckdb.connect(_kg_db_path(), read_only=True)
|
|
380
|
+
try:
|
|
381
|
+
return _workflow_query(con, module)
|
|
382
|
+
finally:
|
|
383
|
+
try: con.close()
|
|
384
|
+
except Exception: pass
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def _workflow_query(con, q):
|
|
388
|
+
"""workflows 表查询 — v3.0 f3: 转发到 common.kg_capabilities (单一真源, CLI/MCP共用)。"""
|
|
389
|
+
from common.kg_capabilities import workflow_query
|
|
390
|
+
return workflow_query(con, q)
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def cmd_hop(args):
|
|
394
|
+
"""多跳遍历: 从一个符号出发,递归 N 跳找全部可达实体。
|
|
395
|
+
|
|
396
|
+
知识图谱核心能力。支持别名解析(传中文/端点/函数名自动转 entity_id)。
|
|
397
|
+
"""
|
|
398
|
+
if not args:
|
|
399
|
+
return "用法: kg.py hop <符号> [--depth 1-5] 例: 资产管理 或 /asset/list"
|
|
400
|
+
symbol, depth = _split_symbol_depth(args)
|
|
401
|
+
if not _has_duckdb():
|
|
402
|
+
return _hint_rebuild('知识图谱(DuckDB)')
|
|
403
|
+
try:
|
|
404
|
+
import duckdb
|
|
405
|
+
except ImportError:
|
|
406
|
+
return "未安装 duckdb。请运行: pip install duckdb"
|
|
407
|
+
import kg_duckdb as K
|
|
408
|
+
con = duckdb.connect(_kg_db_path(), read_only=True)
|
|
409
|
+
try:
|
|
410
|
+
# v3.0 f3: 核心查询转发到 common.kg_capabilities.hop_query (CLI/MCP共用)
|
|
411
|
+
from common.kg_capabilities import hop_query
|
|
412
|
+
return hop_query(con, symbol, depth, multi_hop_fn=K.multi_hop)
|
|
413
|
+
finally:
|
|
414
|
+
try: con.close()
|
|
415
|
+
except Exception: pass
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def cmd_wiki(args):
|
|
419
|
+
"""Repo Wiki 搜索 — v3.0 f3: 转发到 common.kg_capabilities.wiki_query (CLI/MCP共用)。"""
|
|
420
|
+
if not args:
|
|
421
|
+
return "用法: kg.py wiki <关键词>"
|
|
422
|
+
from common.kg_capabilities import wiki_query
|
|
423
|
+
result = wiki_query(args[0])
|
|
424
|
+
# CLI 额外提示
|
|
425
|
+
if 'matching' in result:
|
|
426
|
+
result += '\n\n 提示: 读 wiki 文档(带行号引用)比读源码更省 token'
|
|
427
|
+
return result
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def cmd_fill_prototype(args):
|
|
431
|
+
"""原型预填: 用真实代码数据自动填模板,输出 80% 完成的 HTML 草稿。"""
|
|
432
|
+
if not args:
|
|
433
|
+
return "用法: kg.py fill-prototype <关键词> [--platform web|app]"
|
|
434
|
+
kw, plat = _split_kw_platform(args)
|
|
435
|
+
plat = plat or 'web'
|
|
436
|
+
import subprocess as _sp
|
|
437
|
+
script = os.path.join(SCRIPTS_DIR, 'fill_prototype.py')
|
|
438
|
+
if not os.path.isfile(script):
|
|
439
|
+
return "fill_prototype.py 不存在"
|
|
440
|
+
try:
|
|
441
|
+
r = _sp.run([sys.executable, script, kw, '--platform', plat],
|
|
442
|
+
capture_output=True, text=True, encoding='utf-8',
|
|
443
|
+
errors='replace', timeout=30, cwd=SCRIPTS_DIR)
|
|
444
|
+
return r.stdout or "fill_prototype 无输出(可能索引缺失)"
|
|
445
|
+
except Exception as e:
|
|
446
|
+
return "[error] fill_prototype: %s" % str(e)
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def cmd_design_system(args):
|
|
450
|
+
"""设计系统查询: 返回项目完整设计规范(布局指纹/颜色token/组件表/图标)。"""
|
|
451
|
+
plat = ''
|
|
452
|
+
if args and args[0] in ('--platform', '-p') and len(args) > 1:
|
|
453
|
+
plat = args[1]
|
|
454
|
+
import subprocess as _sp
|
|
455
|
+
design_md = os.path.join(BASE_DIR, 'data', 'design', 'DESIGN.md')
|
|
456
|
+
if not os.path.isfile(design_md):
|
|
457
|
+
gen_script = os.path.join(SCRIPTS_DIR, 'gen_design_doc.py')
|
|
458
|
+
if os.path.isfile(gen_script):
|
|
459
|
+
_sp.run([sys.executable, gen_script], capture_output=True, timeout=30)
|
|
460
|
+
if os.path.isfile(design_md):
|
|
461
|
+
content = open(design_md, encoding='utf-8').read()
|
|
462
|
+
if plat:
|
|
463
|
+
lines = content.split('\n')
|
|
464
|
+
result = []
|
|
465
|
+
in_section = False
|
|
466
|
+
for line in lines:
|
|
467
|
+
if line.startswith('### ') and plat.lower() in line.lower():
|
|
468
|
+
in_section = True
|
|
469
|
+
elif line.startswith('### ') and in_section:
|
|
470
|
+
break
|
|
471
|
+
if in_section:
|
|
472
|
+
result.append(line)
|
|
473
|
+
return '\n'.join(result) if result else content
|
|
474
|
+
return content
|
|
475
|
+
return "DESIGN.md 不存在且生成失败。请手动跑: python .qoder/scripts/gen_design_doc.py"
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
# ── 工具函数 ──────────────────────────────────────────────────────────
|
|
479
|
+
|
|
480
|
+
def _capture(func, *args, **kwargs):
|
|
481
|
+
"""运行函数,捕获其 stdout 输出(底层脚本用 print 输出)。"""
|
|
482
|
+
old = sys.stdout
|
|
483
|
+
buf = io.StringIO()
|
|
484
|
+
sys.stdout = buf
|
|
485
|
+
try:
|
|
486
|
+
func(*args, **kwargs)
|
|
487
|
+
except Exception as e:
|
|
488
|
+
buf.write('\n[error] %s' % str(e))
|
|
489
|
+
finally:
|
|
490
|
+
sys.stdout = old
|
|
491
|
+
return buf.getvalue().strip()
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
def _split_kw_platform(args):
|
|
495
|
+
"""从参数里拆出 keyword 和 --platform 值。"""
|
|
496
|
+
rest = list(args)
|
|
497
|
+
plat = ''
|
|
498
|
+
out = []
|
|
499
|
+
i = 0
|
|
500
|
+
while i < len(rest):
|
|
501
|
+
a = rest[i]
|
|
502
|
+
if a == '--platform' and i + 1 < len(rest):
|
|
503
|
+
plat = rest[i + 1]; i += 2; continue
|
|
504
|
+
out.append(a); i += 1
|
|
505
|
+
kw = out[0] if out else ''
|
|
506
|
+
return kw, plat
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
def _split_symbol_depth(args):
|
|
510
|
+
"""从参数里拆出 symbol 和 --depth 值(默认 3)。"""
|
|
511
|
+
rest = list(args)
|
|
512
|
+
depth = 3
|
|
513
|
+
out = []
|
|
514
|
+
i = 0
|
|
515
|
+
while i < len(rest):
|
|
516
|
+
a = rest[i]
|
|
517
|
+
if a == '--depth' and i + 1 < len(rest):
|
|
518
|
+
try: depth = int(rest[i + 1])
|
|
519
|
+
except (ValueError, TypeError): depth = 3
|
|
520
|
+
i += 2; continue
|
|
521
|
+
out.append(a); i += 1
|
|
522
|
+
return (out[0] if out else ''), max(1, min(5, depth))
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
# Role-based section filtering for context_pack output
|
|
526
|
+
# v3.0: 单一真源移到 common/roles.py, 消除与 kg_mcp_server.py 的逐字重复
|
|
527
|
+
try:
|
|
528
|
+
from common.roles import ROLE_SECTIONS, filter_by_role as _filter_by_role_imported
|
|
529
|
+
_filter_by_role = _filter_by_role_imported # 保持旧调用名 _filter_by_role 兼容
|
|
530
|
+
except Exception:
|
|
531
|
+
# 回退: 内联定义 (common 不可用时, 与 common/roles.py 保持一致)
|
|
532
|
+
ROLE_SECTIONS = {
|
|
533
|
+
'pm': {'1', '2', '2b', '3', '4', '5', '7'},
|
|
534
|
+
'design': {'2', '2b', '3', '6'},
|
|
535
|
+
'dev': {'1', '3', '5', '7'},
|
|
536
|
+
'test': {'1', '4', '5'},
|
|
537
|
+
'admin': {'4', '5'},
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
def _filter_by_role(text, role):
|
|
541
|
+
visible = ROLE_SECTIONS.get((role or '').lower())
|
|
542
|
+
if not visible:
|
|
543
|
+
return text
|
|
544
|
+
sections = re.split(r'(\n## \d+[ab]?\.)', text)
|
|
545
|
+
result = sections[0]
|
|
546
|
+
i = 1
|
|
547
|
+
while i < len(sections):
|
|
548
|
+
header = sections[i]
|
|
549
|
+
num_m = re.match(r'\n## (\d+[ab]?)\.', header)
|
|
550
|
+
if num_m and num_m.group(1) in visible:
|
|
551
|
+
result += header
|
|
552
|
+
if i + 1 < len(sections):
|
|
553
|
+
result += sections[i + 1]
|
|
554
|
+
i += 2
|
|
555
|
+
else:
|
|
556
|
+
i += 2
|
|
557
|
+
return result.strip()
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
# ── 子命令注册表 ──────────────────────────────────────────────────────
|
|
561
|
+
# (子命令名, 处理函数, 中文说明)
|
|
562
|
+
COMMANDS = [
|
|
563
|
+
('search', cmd_search, '搜代码: 按关键词找代码文件'),
|
|
564
|
+
('api', cmd_api, '搜 API 端点'),
|
|
565
|
+
('prd', cmd_prd, '搜 PRD(防重复造轮子)'),
|
|
566
|
+
('impact', cmd_impact, '影响分析: 改某接口影响哪些页面/按钮'),
|
|
567
|
+
('context360', cmd_context360, '符号 360 度视图(端点/函数/处理器)'),
|
|
568
|
+
('context', cmd_context, '上下文打包(代码+页面+字段+PRD+API,可 --role 角色裁剪)'),
|
|
569
|
+
('prefetch', cmd_prefetch, '需求上下文预测+批量预取(一句需求→多词合并,治串行搜索)'),
|
|
570
|
+
('coverage', cmd_coverage, '功能×测试覆盖矩阵(标出回归盲区)'),
|
|
571
|
+
('feature', cmd_feature, '功能画像(端点+按钮+测试+页面)'),
|
|
572
|
+
('workflow', cmd_workflow, '业务流程链(query→create→audit→...)'),
|
|
573
|
+
('hop', cmd_hop, '多跳遍历(知识图谱核心,从一符号递归 N 跳)'),
|
|
574
|
+
('wiki', cmd_wiki, 'Repo Wiki 搜索(模块语义文档)'),
|
|
575
|
+
('fill-prototype', cmd_fill_prototype, '原型预填(真实数据填模板,输出 80% HTML)'),
|
|
576
|
+
('design-system', cmd_design_system, '设计系统查询(布局指纹/颜色token/组件表)'),
|
|
577
|
+
]
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
def _usage():
|
|
581
|
+
"""打印用法清单。"""
|
|
582
|
+
lines = [
|
|
583
|
+
'kg.py — QODER 知识图谱统一 CLI(全系列通用)',
|
|
584
|
+
'',
|
|
585
|
+
'用法: python .qoder/scripts/kg.py <子命令> [参数]',
|
|
586
|
+
'',
|
|
587
|
+
'14 个子命令:',
|
|
588
|
+
]
|
|
589
|
+
for name, _, desc in COMMANDS:
|
|
590
|
+
lines.append(' %-16s %s' % (name, desc))
|
|
591
|
+
lines += [
|
|
592
|
+
'',
|
|
593
|
+
'示例:',
|
|
594
|
+
' kg.py search 考勤 --platform web',
|
|
595
|
+
' kg.py prefetch "保险单OCR识别" --platform web # 写PRD前先批量预取',
|
|
596
|
+
' kg.py impact /asset # 改 /asset 影响谁',
|
|
597
|
+
' kg.py coverage # 哪些功能没测试',
|
|
598
|
+
' kg.py feature 资产管理 # 资产管理功能画像',
|
|
599
|
+
' kg.py workflow assets # 资产模块业务流程',
|
|
600
|
+
' kg.py hop 资产管理 --depth 3 # 从资产管理出发 3 跳遍历',
|
|
601
|
+
' kg.py context 车辆 --role pm # PM 视角的上下文打包',
|
|
602
|
+
'',
|
|
603
|
+
'QoderWork 环境: AI 也会直接调 MCP 工具(mcp__qoder-knowledge-graph__*),功能等价。',
|
|
604
|
+
]
|
|
605
|
+
return '\n'.join(lines)
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
def main(argv=None):
|
|
609
|
+
argv = argv if argv is not None else sys.argv[1:]
|
|
610
|
+
if not argv or argv[0] in ('-h', '--help', 'help'):
|
|
611
|
+
print(_usage())
|
|
612
|
+
return 0
|
|
613
|
+
cmd = argv[0]
|
|
614
|
+
rest = argv[1:]
|
|
615
|
+
# 查找子命令
|
|
616
|
+
handler = None
|
|
617
|
+
for name, fn, _ in COMMANDS:
|
|
618
|
+
if name == cmd:
|
|
619
|
+
handler = fn
|
|
620
|
+
break
|
|
621
|
+
if handler is None:
|
|
622
|
+
print("未知子命令: %s" % cmd)
|
|
623
|
+
print(_usage())
|
|
624
|
+
return 1
|
|
625
|
+
# 执行(全容错,绝不抛栈)
|
|
626
|
+
try:
|
|
627
|
+
result = handler(rest)
|
|
628
|
+
if result:
|
|
629
|
+
print(result)
|
|
630
|
+
except Exception as e:
|
|
631
|
+
print("[error] %s 执行失败: %s" % (cmd, str(e)))
|
|
632
|
+
print("如果是索引/图谱问题,尝试: python .qoder/scripts/init_doctor.py --fix")
|
|
633
|
+
return 1
|
|
634
|
+
return 0
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
if __name__ == '__main__':
|
|
638
|
+
sys.exit(main())
|