@hupan56/wlkj 3.1.32 → 3.3.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 +117 -0
- package/package.json +1 -1
- package/templates/qoder/agents/insight-planning.md +67 -67
- package/templates/qoder/agents/prd-reference.md +47 -47
- package/templates/qoder/commands/optional/wl-insight.md +4 -4
- package/templates/qoder/commands/optional/wl-report.md +1 -1
- package/templates/qoder/commands/optional/wl-spec.md +23 -3
- package/templates/qoder/commands/optional/wl-status.md +12 -1
- package/templates/qoder/commands/wl-code.md +138 -7
- package/templates/qoder/commands/wl-commit.md +12 -1
- package/templates/qoder/commands/wl-design.md +70 -6
- package/templates/qoder/commands/wl-init.md +27 -0
- package/templates/qoder/commands/wl-prd.md +230 -15
- package/templates/qoder/commands/wl-req.md +10 -3
- package/templates/qoder/commands/wl-search.md +74 -20
- package/templates/qoder/commands/wl-task.md +3 -3
- package/templates/qoder/commands/wl-test.md +17 -2
- package/templates/qoder/contracts/CHANGELOG.md +418 -0
- package/templates/qoder/contracts/README.md +180 -0
- package/templates/qoder/contracts/code.md +82 -0
- package/templates/qoder/contracts/commit.md +86 -0
- package/templates/qoder/contracts/contract-header.md +76 -0
- package/templates/qoder/contracts/design.md +106 -0
- package/templates/qoder/contracts/fallback.md +126 -0
- package/templates/qoder/contracts/isolation.md +119 -0
- package/templates/qoder/contracts/prd.md +118 -0
- package/templates/qoder/contracts/schemas/design-spec.schema.json +46 -0
- package/templates/qoder/contracts/schemas/prd.schema.json +36 -0
- package/templates/qoder/contracts/schemas/test-cases.schema.json +40 -0
- package/templates/qoder/contracts/spec.md +116 -0
- package/templates/qoder/contracts/task.md +125 -0
- package/templates/qoder/contracts/test.md +112 -0
- package/templates/qoder/hooks/post-tool-use.py +61 -0
- package/templates/qoder/hooks/session-start.py +34 -66
- package/templates/qoder/hooks/stop-eval.py +47 -0
- package/templates/qoder/rules/wl-pipeline.md +37 -0
- package/templates/qoder/scripts/capability/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/__pycache__/registry.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/__pycache__/registry_mcp.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/adapters/__init__.py +1 -1
- package/templates/qoder/scripts/capability/adapters/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/adapters/__pycache__/cli.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/adapters/__pycache__/mcp.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/adapters/__pycache__/qw.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/adapters/mcp.py +76 -100
- package/templates/qoder/scripts/capability/adapters/qw.py +295 -295
- package/templates/qoder/scripts/capability/caps/__init__.py +1 -1
- package/templates/qoder/scripts/capability/caps/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/caps/__pycache__/context.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/caps/__pycache__/cron.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/caps/__pycache__/identity.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/caps/__pycache__/memory.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/caps/__pycache__/notify.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/caps/__pycache__/present.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/caps/__pycache__/repo.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/caps/__pycache__/sandbox.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/caps/memory.py +1 -1
- package/templates/qoder/scripts/capability/registry.py +21 -23
- package/templates/qoder/scripts/capability/registry_mcp.py +69 -5
- package/templates/qoder/scripts/capability/smoke_test_report.json +34 -20
- package/templates/qoder/scripts/deployment/setup/carriers.py +3 -1
- package/templates/qoder/scripts/deployment/setup/init_doctor.py +10 -3
- package/templates/qoder/scripts/deployment/setup/install_qoderwork.py +11 -0
- package/templates/qoder/scripts/deployment/setup/wlkj_shim.py +104 -0
- package/templates/qoder/scripts/domain/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/domain/deployment/deploy_to_test.py +298 -0
- package/templates/qoder/scripts/domain/integration/__init__.py +0 -0
- package/templates/qoder/scripts/domain/integration/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/domain/integration/__pycache__/return_to_platform.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/domain/integration/return_to_platform.py +392 -0
- package/templates/qoder/scripts/domain/integration/spec_upload.py +209 -0
- package/templates/qoder/scripts/domain/kg/build/kg_build.py +268 -25
- package/templates/qoder/scripts/domain/kg/build/kg_incremental.py +108 -3
- package/templates/qoder/scripts/domain/kg/build/kg_signatures.py +169 -0
- package/templates/qoder/scripts/domain/kg/extract/asset/__init__.py +10 -0
- package/templates/qoder/scripts/domain/kg/extract/asset/asset_tree.py +57 -0
- package/templates/qoder/scripts/domain/kg/extract/asset/discussion_importer.py +62 -0
- package/templates/qoder/scripts/domain/kg/extract/asset/prd_importer.py +146 -0
- package/templates/qoder/scripts/domain/kg/extract/asset/prototype_importer.py +64 -0
- package/templates/qoder/scripts/domain/kg/extract/asset/returns_importer.py +52 -0
- package/templates/qoder/scripts/domain/kg/extract/build_goal3.py +104 -0
- package/templates/qoder/scripts/domain/kg/extract/build_goal4.py +55 -0
- package/templates/qoder/scripts/domain/kg/extract/build_goal5.py +95 -0
- package/templates/qoder/scripts/domain/kg/extract/db/__init__.py +8 -0
- package/templates/qoder/scripts/domain/kg/extract/db/data_profile.py +22 -0
- package/templates/qoder/scripts/domain/kg/extract/db/fk_extractor.py +55 -0
- package/templates/qoder/scripts/domain/kg/extract/db/schema_extractor.py +90 -0
- package/templates/qoder/scripts/domain/kg/extract/extract.py +84 -0
- package/templates/qoder/scripts/domain/kg/extract/extract.py.bak +430 -0
- package/templates/qoder/scripts/domain/kg/extract/inference/__init__.py +9 -0
- package/templates/qoder/scripts/domain/kg/extract/inference/community_summarizer.py +206 -0
- package/templates/qoder/scripts/domain/kg/extract/inference/embed_builder.py +132 -0
- package/templates/qoder/scripts/domain/kg/extract/inference/naming_matcher.py +80 -0
- package/templates/qoder/scripts/domain/kg/extract/inference/promote.py +59 -0
- package/templates/qoder/scripts/domain/kg/extract/inference/recompute.py +93 -0
- package/templates/qoder/scripts/domain/kg/extract/inference/weak_link.py +421 -0
- package/templates/qoder/scripts/domain/kg/extract/java/__init__.py +15 -0
- package/templates/qoder/scripts/domain/kg/extract/java/_parser.py +271 -0
- package/templates/qoder/scripts/domain/kg/extract/java/all.py +145 -0
- package/templates/qoder/scripts/domain/kg/extract/java/build_java_to_pg.py +102 -0
- package/templates/qoder/scripts/domain/kg/extract/java/call_chain.py +49 -0
- package/templates/qoder/scripts/domain/kg/extract/java/class_extractor.py +141 -0
- package/templates/qoder/scripts/domain/kg/extract/java/domain_extractor.py +148 -0
- package/templates/qoder/scripts/domain/kg/extract/java/dubbo_extractor.py +33 -0
- package/templates/qoder/scripts/domain/kg/extract/java/endpoint_extractor.py +36 -0
- package/templates/qoder/scripts/domain/kg/extract/java/javadoc_extractor.py +110 -0
- package/templates/qoder/scripts/domain/kg/extract/java/llm_cn_filler.py +150 -0
- package/templates/qoder/scripts/domain/kg/extract/java/member_extractor.py +157 -0
- package/templates/qoder/scripts/domain/kg/extract/java/mybatisplus_extractor.py +34 -0
- package/templates/qoder/scripts/domain/kg/extract/java/pg_upsert.py +165 -0
- package/templates/qoder/scripts/domain/kg/extract/java/satoken_extractor.py +30 -0
- package/templates/qoder/scripts/domain/kg/extract/java/spring_extractor.py +39 -0
- package/templates/qoder/scripts/domain/kg/extract/java/validation_extractor.py +33 -0
- package/templates/qoder/scripts/domain/kg/extract/mybatis/__init__.py +9 -0
- package/templates/qoder/scripts/domain/kg/extract/mybatis/all.py +79 -0
- package/templates/qoder/scripts/domain/kg/extract/mybatis/mapper_parser.py +99 -0
- package/templates/qoder/scripts/domain/kg/extract/mybatis/relation_builder.py +69 -0
- package/templates/qoder/scripts/domain/kg/extract/mybatis/sql_extractor.py +78 -0
- package/templates/qoder/scripts/domain/kg/extract/prd/__init__.py +8 -0
- package/templates/qoder/scripts/domain/kg/extract/prd/prd_chunk_embed.py +105 -0
- package/templates/qoder/scripts/domain/kg/extract/prd/prd_llm_extract.py +153 -0
- package/templates/qoder/scripts/domain/kg/extract/prd/req_anchor.py +120 -0
- package/templates/qoder/scripts/domain/kg/extract/ts_extract.py +111 -0
- package/templates/qoder/scripts/domain/kg/graph/kg_semantic.py +4 -2
- package/templates/qoder/scripts/domain/kg/kg.py +42 -5
- package/templates/qoder/scripts/domain/kg/search/_remote.py +187 -0
- package/templates/qoder/scripts/domain/kg/search/context_pack.py +32 -2
- package/templates/qoder/scripts/domain/kg/search/search_index.py +74 -20
- package/templates/qoder/scripts/domain/kg/storage/kg_duckdb.py +43 -0
- package/templates/qoder/scripts/domain/kg/switch_project.py +159 -0
- package/templates/qoder/scripts/domain/kg/sync_repowiki.py +109 -0
- package/templates/qoder/scripts/domain/requirement/req.py +134 -28
- package/templates/qoder/scripts/domain/task/__pycache__/wlkj_panel.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/domain/task/wlkj_panel.py +1348 -0
- package/templates/qoder/scripts/engine/poller.py +219 -0
- package/templates/qoder/scripts/foundation/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/foundation/core/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/foundation/core/__pycache__/paths.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/foundation/core/paths.py +102 -0
- package/templates/qoder/scripts/foundation/integrations/active_task.py +2 -1
- package/templates/qoder/scripts/orchestration/wlkj.py +4 -0
- package/templates/qoder/scripts/protocol/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/protocol/mcp/zentao_mcp_server.py +24 -11
- package/templates/qoder/scripts/protocol/transports/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/protocol/transports/__pycache__/base.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/protocol/transports/__pycache__/cli.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/protocol/transports/__pycache__/http.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/protocol/transports/__pycache__/stdio.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/protocol/transports/http.py +7 -1
- package/templates/qoder/scripts/validation/eval/qwork_harness.py +1 -1
- package/templates/qoder/scripts/validation/eval/report-commands.md +2 -2
- package/templates/qoder/settings.json +27 -9
- package/templates/qoder/skills/design-import/SKILL.md +3 -3
- package/templates/qoder/skills/design-review/SKILL.md +1 -1
- package/templates/qoder/skills/prd-generator/SKILL.md +4 -4
- package/templates/qoder/skills/prd-review/SKILL.md +1 -1
- package/templates/qoder/skills/prototype-generator/SKILL.md +3 -3
- package/templates/qoder/skills/spec-coder/SKILL.md +1 -1
- package/templates/qoder/skills/spec-generator/SKILL.md +80 -23
- package/templates/qoder/skills/test-generator/SKILL.md +1 -1
- package/templates/qoder/skills/wl-code/SKILL.md +13 -1
- package/templates/qoder/skills/wl-commit/SKILL.md +1 -1
- package/templates/qoder/skills/wl-design/SKILL.md +6 -6
- package/templates/qoder/skills/wl-init/SKILL.md +2 -2
- package/templates/qoder/skills/wl-insight/SKILL.md +5 -5
- package/templates/qoder/skills/wl-prd/SKILL.md +60 -0
- package/templates/qoder/skills/wl-report/SKILL.md +2 -2
- package/templates/qoder/skills/wl-search/SKILL.md +1 -1
- package/templates/qoder/skills/wl-spec/SKILL.md +2 -2
- package/templates/qoder/skills/wl-status/SKILL.md +2 -2
- package/templates/qoder/skills/wl-task/SKILL.md +3 -3
- package/templates/qoder/skills/wl-test/SKILL.md +2 -2
- package/templates/qoder/templates/spec-template.md +124 -0
- package/templates/root/AGENTS.md +41 -14
- package/templates/qoder/scripts/domain/task/zentao_panel.py +0 -451
- package/templates/qoder/skills/wl-prd-full/SKILL.md +0 -121
- package/templates/qoder/skills/wl-prd-quick/SKILL.md +0 -50
- package/templates/qoder/skills/wl-prd-review/SKILL.md +0 -47
|
@@ -32,6 +32,8 @@ sys.path.insert(0, SCRIPTS)
|
|
|
32
32
|
|
|
33
33
|
from foundation.core.paths import DATA_INDEX_DIR, PROJECT_ROOT
|
|
34
34
|
from domain.kg.storage.kg_duckdb import get_db, init_schema, _batch_insert
|
|
35
|
+
from domain.kg.storage.kg_duckdb import upsert_file_signature
|
|
36
|
+
from domain.kg.build.kg_signatures import compute_hashes, TRACKED_SUFFIXES
|
|
35
37
|
|
|
36
38
|
CODE_DIR = str(PROJECT_ROOT / 'data' / 'code')
|
|
37
39
|
DB_PATH = str(DATA_INDEX_DIR / 'kg.duckdb')
|
|
@@ -323,7 +325,13 @@ def extract_traces(cfg, api_table):
|
|
|
323
325
|
try:
|
|
324
326
|
from domain.kg.extract.ts_extract import ts_trace_clicks
|
|
325
327
|
except Exception:
|
|
326
|
-
|
|
328
|
+
ts_trace_clicks = None
|
|
329
|
+
# GOAL 3: H5 风格 (Carmg-H5 Vant, @tap + export function API)
|
|
330
|
+
try:
|
|
331
|
+
from domain.kg.extract.extract import trace_clicks_h5
|
|
332
|
+
except Exception:
|
|
333
|
+
trace_clicks_h5 = None
|
|
334
|
+
is_h5 = 'vant' in (cfg.get('framework') or '').lower() or 'h5' in cfg['name'].lower()
|
|
327
335
|
|
|
328
336
|
for view_dir in cfg.get('view_dirs', []):
|
|
329
337
|
for root, dirs, files in os.walk(view_dir):
|
|
@@ -336,12 +344,78 @@ def extract_traces(cfg, api_table):
|
|
|
336
344
|
content = open(filepath, encoding='utf-8', errors='ignore').read()
|
|
337
345
|
except Exception:
|
|
338
346
|
continue
|
|
339
|
-
if '@click' not in content:
|
|
347
|
+
if '@click' not in content and '@tap' not in content:
|
|
340
348
|
continue
|
|
341
|
-
|
|
349
|
+
# H5 项目用 H5 风格追踪; 否则用 ts 版
|
|
350
|
+
if is_h5 and trace_clicks_h5:
|
|
351
|
+
traces.extend(trace_clicks_h5(filepath, content, api_table))
|
|
352
|
+
elif ts_trace_clicks:
|
|
353
|
+
traces.extend(ts_trace_clicks(filepath, content, api_table))
|
|
342
354
|
return traces
|
|
343
355
|
|
|
344
356
|
|
|
357
|
+
def extract_call_edges(cfg):
|
|
358
|
+
"""Extract FUNCTION nodes + calls/imports edges for one project (P1).
|
|
359
|
+
|
|
360
|
+
Walks .ts and .vue files (view_dirs + api_dirs), uses tree-sitter to pull
|
|
361
|
+
function-level call edges and local import edges. This upgrades the graph
|
|
362
|
+
from a single "button→endpoint→controller" chain to a real code graph with
|
|
363
|
+
function-level calls.
|
|
364
|
+
|
|
365
|
+
Returns:
|
|
366
|
+
(fn_entities, call_edges, import_edges) where:
|
|
367
|
+
fn_entities = [(eid, fn_name, file_rel), ...] FUNCTION nodes
|
|
368
|
+
call_edges = [(caller_eid, callee_name), ...] calls edges
|
|
369
|
+
import_edges = [(file_rel, name, source), ...] imports edges
|
|
370
|
+
Tree-sitter unavailable → all empty (silent fallback, no error).
|
|
371
|
+
"""
|
|
372
|
+
fn_entities, call_edges, import_edges = [], [], []
|
|
373
|
+
try:
|
|
374
|
+
from domain.kg.extract.ts_extract import ts_extract_calls, ts_extract_imports
|
|
375
|
+
except Exception:
|
|
376
|
+
return fn_entities, call_edges, import_edges
|
|
377
|
+
|
|
378
|
+
proj_name = cfg.get('name', '')
|
|
379
|
+
proj_root = cfg.get('path', '')
|
|
380
|
+
# Scan both view dirs (.vue) and api dirs (.ts) + src for .ts.
|
|
381
|
+
scan_dirs = list(cfg.get('view_dirs', [])) + list(cfg.get('api_dirs', []))
|
|
382
|
+
seen_files = set()
|
|
383
|
+
for scan_dir in scan_dirs:
|
|
384
|
+
if not scan_dir or not os.path.isdir(scan_dir):
|
|
385
|
+
continue
|
|
386
|
+
for root, dirs, files in os.walk(scan_dir):
|
|
387
|
+
dirs[:] = [d for d in dirs if d not in ('node_modules', 'dist', '.git', '__pycache__')]
|
|
388
|
+
for fname in files:
|
|
389
|
+
if not fname.endswith(('.ts', '.vue')):
|
|
390
|
+
continue
|
|
391
|
+
filepath = os.path.join(root, fname)
|
|
392
|
+
if filepath in seen_files:
|
|
393
|
+
continue
|
|
394
|
+
seen_files.add(filepath)
|
|
395
|
+
try:
|
|
396
|
+
content = open(filepath, encoding='utf-8', errors='ignore').read()
|
|
397
|
+
except Exception:
|
|
398
|
+
continue
|
|
399
|
+
# File-relative path (project/name/rel) for stable FN: IDs.
|
|
400
|
+
file_rel = filepath.replace('\\', '/').replace(proj_root.replace('\\', '/') + '/', '') if proj_root else os.path.basename(filepath)
|
|
401
|
+
file_key = '%s/%s' % (proj_name, file_rel) if proj_name else file_rel
|
|
402
|
+
|
|
403
|
+
# FUNCTION nodes + calls edges
|
|
404
|
+
calls = ts_extract_calls(content)
|
|
405
|
+
caller_fns = set()
|
|
406
|
+
for caller, callee in calls:
|
|
407
|
+
caller_fns.add(caller)
|
|
408
|
+
for fn in caller_fns:
|
|
409
|
+
fn_entities.append(('FN:%s::%s' % (file_key, fn), fn, file_key))
|
|
410
|
+
for caller, callee in calls:
|
|
411
|
+
caller_eid = 'FN:%s::%s' % (file_key, caller)
|
|
412
|
+
call_edges.append((caller_eid, callee))
|
|
413
|
+
# imports edges
|
|
414
|
+
for name, src in ts_extract_imports(content):
|
|
415
|
+
import_edges.append((file_key, name, src))
|
|
416
|
+
return fn_entities, call_edges, import_edges
|
|
417
|
+
|
|
418
|
+
|
|
345
419
|
def extract_tests(cfg):
|
|
346
420
|
"""Extract test assertions for one project."""
|
|
347
421
|
results = []
|
|
@@ -381,7 +455,7 @@ def build_all():
|
|
|
381
455
|
# Clear all
|
|
382
456
|
for table in ['entities', 'aliases', 'edges', 'keywords', 'endpoints', 'prds',
|
|
383
457
|
'wiki', 'pages', 'fields', 'modules', 'traces', 'api_functions',
|
|
384
|
-
'tests', 'workflows', 'style_meta', 'build_meta']:
|
|
458
|
+
'tests', 'workflows', 'style_meta', 'build_meta', 'file_signatures']:
|
|
385
459
|
con.execute('DELETE FROM %s' % table)
|
|
386
460
|
|
|
387
461
|
# 1. Discover projects
|
|
@@ -450,6 +524,10 @@ def build_all():
|
|
|
450
524
|
try:
|
|
451
525
|
from domain.kg.extract.ts_extract import ts_build_api_fn_table
|
|
452
526
|
t = ts_build_api_fn_table(api_dir)
|
|
527
|
+
# GOAL 3 泛化: ts 版返回空时, 试 H5 风格 (Carmg-H5 的 export function + url: 模式)
|
|
528
|
+
if not t:
|
|
529
|
+
from domain.kg.extract.extract import build_api_fn_table_h5
|
|
530
|
+
t = build_api_fn_table_h5(api_dir)
|
|
453
531
|
api_table.update(t)
|
|
454
532
|
except Exception:
|
|
455
533
|
pass
|
|
@@ -483,13 +561,38 @@ def build_all():
|
|
|
483
561
|
_batch_insert(con, 'tests', test_rows)
|
|
484
562
|
stats['tests'] = len(test_rows)
|
|
485
563
|
|
|
564
|
+
# 5b. P1: function-level calls/imports edges (upgrade graph to code graph)
|
|
565
|
+
print(' [5b/7] function calls + imports (P1)...')
|
|
566
|
+
all_fn_entities, all_call_edges, all_import_edges = [], [], []
|
|
567
|
+
for cfg in projects:
|
|
568
|
+
if not cfg.get('has_vue'):
|
|
569
|
+
continue # calls extraction is TS/Vue only (Java not supported yet)
|
|
570
|
+
fe, ce, ie = extract_call_edges(cfg)
|
|
571
|
+
all_fn_entities.extend(fe)
|
|
572
|
+
all_call_edges.extend(ce)
|
|
573
|
+
all_import_edges.extend(ie)
|
|
574
|
+
stats['fn_entities'] = len(all_fn_entities)
|
|
575
|
+
stats['call_edges'] = len(all_call_edges)
|
|
576
|
+
stats['import_edges'] = len(all_import_edges)
|
|
577
|
+
|
|
486
578
|
# 6. Entities + edges
|
|
487
579
|
print(' [6/7] entity resolution + edges...')
|
|
488
580
|
_build_entities_and_edges(con, all_modules, dict(all_ep_rows and [(r[0], r[1]) for r in all_ep_rows] or []),
|
|
489
|
-
all_traces, all_tests
|
|
581
|
+
all_traces, all_tests,
|
|
582
|
+
all_fn_entities, all_call_edges, all_import_edges)
|
|
490
583
|
stats['entities'] = con.execute("SELECT COUNT(*) FROM entities").fetchone()[0]
|
|
491
584
|
stats['edges'] = con.execute("SELECT COUNT(*) FROM edges").fetchone()[0]
|
|
492
585
|
|
|
586
|
+
# 6b. Java 后端全栈提取 (GOAL 2: tree-sitter → PG, best-effort 不阻塞)
|
|
587
|
+
# 让 11905 个 .java 文件的 Service/Mapper/Entity 进 KG, get_impact 可穿透到后端
|
|
588
|
+
try:
|
|
589
|
+
print(' [6b/8] Java 后端全栈提取 (tree-sitter → PG)...')
|
|
590
|
+
from domain.kg.extract.java.build_java_to_pg import build_all_repos
|
|
591
|
+
java_stats = build_all_repos(write_pg=True, logger=print)
|
|
592
|
+
stats['java_backend'] = java_stats
|
|
593
|
+
except Exception as _e:
|
|
594
|
+
print(' [6b] SKIP (best-effort): %s' % str(_e)[:80])
|
|
595
|
+
|
|
493
596
|
# 7. Workflows
|
|
494
597
|
print(' [7/8] workflows...')
|
|
495
598
|
wf_count = _build_workflows(con, all_traces)
|
|
@@ -508,13 +611,34 @@ def build_all():
|
|
|
508
611
|
|
|
509
612
|
con.execute("INSERT OR REPLACE INTO build_meta VALUES ('version', '2.0')")
|
|
510
613
|
con.execute("INSERT OR REPLACE INTO build_meta VALUES ('built_at', ?)", [str(int(time.time()))])
|
|
614
|
+
|
|
615
|
+
# P0: write signature baseline so the first incremental run can hit COSMETIC.
|
|
616
|
+
# Key format matches kg_incremental's rel_path (full path with '/' separators).
|
|
617
|
+
# Only hashes files — no parsing — so this adds negligible time to a full build.
|
|
618
|
+
try:
|
|
619
|
+
sig_count = 0
|
|
620
|
+
for root, dirs, files in os.walk(CODE_DIR):
|
|
621
|
+
dirs[:] = [d for d in dirs if d not in ('node_modules', 'dist', 'target', '.git', '__pycache__')]
|
|
622
|
+
for fname in files:
|
|
623
|
+
if os.path.splitext(fname)[1].lower() in TRACKED_SUFFIXES:
|
|
624
|
+
fpath = os.path.join(root, fname)
|
|
625
|
+
content_hash, structural_hash = compute_hashes(fpath)
|
|
626
|
+
if content_hash:
|
|
627
|
+
upsert_file_signature(con, fpath.replace('\\', '/'),
|
|
628
|
+
content_hash, structural_hash, 'NONE')
|
|
629
|
+
sig_count += 1
|
|
630
|
+
print(' [baseline] %d file signatures recorded' % sig_count)
|
|
631
|
+
except Exception as e:
|
|
632
|
+
print(' [baseline] signature write SKIP: %s' % str(e)[:60])
|
|
633
|
+
|
|
511
634
|
con.close()
|
|
512
635
|
|
|
513
636
|
stats['elapsed'] = '%.1fs' % (time.time() - t0)
|
|
514
637
|
return stats
|
|
515
638
|
|
|
516
639
|
|
|
517
|
-
def _build_entities_and_edges(con, modules, api_idx, traces, tests
|
|
640
|
+
def _build_entities_and_edges(con, modules, api_idx, traces, tests,
|
|
641
|
+
fn_entities=None, call_edges=None, import_edges=None):
|
|
518
642
|
"""Build entities + edges (project-agnostic)."""
|
|
519
643
|
entity_rows = []
|
|
520
644
|
alias_rows = []
|
|
@@ -589,46 +713,165 @@ def _build_entities_and_edges(con, modules, api_idx, traces, tests):
|
|
|
589
713
|
btn_eid = 'B:%s:%s' % (vue_short, t.get('handler', ''))
|
|
590
714
|
edge_rows.append((feat_eid, btn_eid, 'has_button', 0, '{}'))
|
|
591
715
|
|
|
716
|
+
# P1: FUNCTION nodes + calls/imports edges (function-level code graph).
|
|
717
|
+
# calls: caller_fn → callee_name (callee resolved by name across files).
|
|
718
|
+
# imports: file → imported_name → source (local module deps only).
|
|
719
|
+
if fn_entities:
|
|
720
|
+
# Dedup FUNCTION nodes by eid; register alias so hop(fn_name) resolves.
|
|
721
|
+
seen_fn = set()
|
|
722
|
+
for eid, fn_name, file_key in fn_entities:
|
|
723
|
+
if eid in seen_fn:
|
|
724
|
+
continue
|
|
725
|
+
seen_fn.add(eid)
|
|
726
|
+
entity_rows.append((eid, 'FUNCTION', fn_name, '', '{}'))
|
|
727
|
+
alias_rows.append((fn_name.lower(), eid))
|
|
728
|
+
# Build a name→eid index for resolving callees (same fn name in same file wins).
|
|
729
|
+
fn_by_name = {}
|
|
730
|
+
for eid, fn_name, file_key in fn_entities:
|
|
731
|
+
fn_by_name.setdefault(fn_name, eid)
|
|
732
|
+
# calls edges: only when callee resolves to a known FUNCTION node.
|
|
733
|
+
seen_call = set()
|
|
734
|
+
for caller_eid, callee_name in (call_edges or []):
|
|
735
|
+
callee_eid = fn_by_name.get(callee_name)
|
|
736
|
+
if not callee_eid:
|
|
737
|
+
continue
|
|
738
|
+
key = (caller_eid, callee_eid)
|
|
739
|
+
if key in seen_call:
|
|
740
|
+
continue
|
|
741
|
+
seen_call.add(key)
|
|
742
|
+
edge_rows.append((caller_eid, callee_eid, 'calls', 0, '{}'))
|
|
743
|
+
# imports edges: file → FUNCTION (any fn matching imported name in that file).
|
|
744
|
+
# Coarse but useful: links a file to functions it imports from elsewhere.
|
|
745
|
+
# Source path kept in props for traceability.
|
|
746
|
+
seen_imp = set()
|
|
747
|
+
for file_key, name, src in (import_edges or []):
|
|
748
|
+
target_eid = fn_by_name.get(name)
|
|
749
|
+
if not target_eid:
|
|
750
|
+
continue
|
|
751
|
+
from_eid = 'FILE:%s' % file_key
|
|
752
|
+
key = (from_eid, target_eid)
|
|
753
|
+
if key in seen_imp:
|
|
754
|
+
continue
|
|
755
|
+
seen_imp.add(key)
|
|
756
|
+
edge_rows.append((from_eid, target_eid, 'imports', 0,
|
|
757
|
+
json.dumps({'source': src}, ensure_ascii=False)))
|
|
758
|
+
|
|
592
759
|
_batch_insert(con, 'entities', entity_rows, ignore=True)
|
|
593
760
|
_batch_insert(con, 'aliases', alias_rows, ignore=True)
|
|
594
761
|
_batch_insert(con, 'edges', edge_rows)
|
|
595
762
|
|
|
596
763
|
|
|
597
764
|
def _build_workflows(con, traces):
|
|
598
|
-
"""Build workflow chains (project-agnostic).
|
|
765
|
+
"""Build workflow chains (project-agnostic).
|
|
766
|
+
|
|
767
|
+
P5 quality enhancements over the original keyword approach:
|
|
768
|
+
1. Two-phase state classification — button text takes priority over
|
|
769
|
+
endpoint path, fixing misclassification (e.g. btn=新增 ep=/page
|
|
770
|
+
was wrongly tagged 'query'; now correctly 'create').
|
|
771
|
+
2. Bilingual keyword dictionary — Chinese button text (保存/新增/提交/
|
|
772
|
+
审批/删除/导出/查询) recognized alongside English.
|
|
773
|
+
3. Empty-button backfill — derive a readable label from endpoint path.
|
|
774
|
+
4. Topological step ordering via P1's calls edges — when operations in
|
|
775
|
+
a module share a call relationship, order by call topology; fall
|
|
776
|
+
back to the fixed state chain when no calls link them.
|
|
777
|
+
"""
|
|
778
|
+
# Bilingual keyword dictionary (button + endpoint matching).
|
|
779
|
+
# Order matters: states listed first win on conflict.
|
|
599
780
|
STATE_KW = {
|
|
600
|
-
'create':
|
|
601
|
-
|
|
602
|
-
'
|
|
603
|
-
|
|
604
|
-
'
|
|
605
|
-
|
|
606
|
-
'
|
|
607
|
-
|
|
781
|
+
'create': ['新增', '添加', '保存', '新建', '录入', '编辑', '修改',
|
|
782
|
+
'add', 'create', 'save', 'insert', 'edit', 'update', 'modify'],
|
|
783
|
+
'submit': ['提交', '上报', '申报', '申请', '送审', '发布',
|
|
784
|
+
'submit', 'commit', 'apply', 'publish'],
|
|
785
|
+
'execute': ['执行', '开始', '启动', '处理', '审批', '审核', '办理',
|
|
786
|
+
'execute', 'start', 'begin', 'process', 'approve', 'audit'],
|
|
787
|
+
'complete': ['完成', '结束', '结项', '验收', '确认', 'done',
|
|
788
|
+
'complete', 'finish', 'done', 'end', 'confirm'],
|
|
789
|
+
'archive': ['归档', '关闭', '结算', 'archive', 'close', 'settle'],
|
|
790
|
+
'delete': ['删除', '移除', '作废', '取消', 'delete', 'remove', 'destroy', 'cancel'],
|
|
791
|
+
'export': ['导出', '下载', '打印', 'export', 'download', 'print'],
|
|
792
|
+
'query': ['查询', '搜索', '查看', '详情', 'list', 'get', 'query',
|
|
793
|
+
'page', 'detail', 'search'],
|
|
608
794
|
}
|
|
795
|
+
# Fixed fallback ordering when no call topology is available.
|
|
796
|
+
CHAIN_ORDER = ['query', 'create', 'submit', 'execute', 'complete', 'archive', 'export', 'delete']
|
|
797
|
+
|
|
798
|
+
def classify_state(button, endpoint):
|
|
799
|
+
"""Two-phase: button text first, then endpoint path. Returns state or None."""
|
|
800
|
+
btn = (button or '').lower()
|
|
801
|
+
ep = (endpoint or '').lower()
|
|
802
|
+
# Phase 1: button text (highest priority — explicit user intent).
|
|
803
|
+
for state, kws in STATE_KW.items():
|
|
804
|
+
if any(k.lower() in btn for k in kws) and btn.strip():
|
|
805
|
+
return state
|
|
806
|
+
# Phase 2: endpoint path.
|
|
807
|
+
for state, kws in STATE_KW.items():
|
|
808
|
+
if any(k.lower() in ep for k in kws):
|
|
809
|
+
return state
|
|
810
|
+
return None
|
|
811
|
+
|
|
812
|
+
def backfill_button(button, endpoint):
|
|
813
|
+
"""Derive a readable button label from endpoint when button is empty."""
|
|
814
|
+
if button and button.strip():
|
|
815
|
+
return button
|
|
816
|
+
ep = (endpoint or '').lower()
|
|
817
|
+
for kw, label in [('save', '保存'), ('add', '新增'), ('create', '新增'),
|
|
818
|
+
('delete', '删除'), ('remove', '删除'), ('export', '导出'),
|
|
819
|
+
('submit', '提交'), ('page', '查询'), ('list', '查询'),
|
|
820
|
+
('detail', '详情')]:
|
|
821
|
+
if kw in ep:
|
|
822
|
+
return label
|
|
823
|
+
return ''
|
|
824
|
+
|
|
609
825
|
from collections import defaultdict
|
|
610
826
|
mod_ops = defaultdict(list)
|
|
611
827
|
for t in traces:
|
|
612
828
|
vue = (t.get('vue_file') or '').replace('\\', '/')
|
|
613
829
|
mod = vue.split('/views/')[-1].split('/')[0] if '/views/' in vue else \
|
|
614
830
|
vue.split('/pages/')[-1].split('/')[0] if '/pages/' in vue else 'unknown'
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
break
|
|
831
|
+
button = backfill_button(t.get('button', ''), t.get('endpoint', ''))
|
|
832
|
+
state = classify_state(button, t.get('endpoint', ''))
|
|
833
|
+
if state:
|
|
834
|
+
mod_ops[mod].append((state, button, t.get('endpoint', '')))
|
|
620
835
|
|
|
621
|
-
|
|
836
|
+
# Build rows: topological sort within each module using calls edges,
|
|
837
|
+
# falling back to the fixed state chain order.
|
|
622
838
|
wf_rows = []
|
|
623
839
|
for mod, ops in mod_ops.items():
|
|
624
|
-
for
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
840
|
+
ordered_states = _workflow_topo_sort([o[0] for o in ops], con, CHAIN_ORDER)
|
|
841
|
+
# Cap at 3 ops per state to keep workflows readable.
|
|
842
|
+
per_state = defaultdict(list)
|
|
843
|
+
for op in ops:
|
|
844
|
+
if len(per_state[op[0]]) < 3:
|
|
845
|
+
per_state[op[0]].append(op)
|
|
846
|
+
seq = 0
|
|
847
|
+
for state in ordered_states:
|
|
848
|
+
for op in per_state.get(state, []):
|
|
849
|
+
wf_rows.append((mod, state, seq, op[1], op[2]))
|
|
850
|
+
seq += 1
|
|
628
851
|
_batch_insert(con, 'workflows', wf_rows)
|
|
629
852
|
return len(wf_rows)
|
|
630
853
|
|
|
631
854
|
|
|
855
|
+
def _workflow_topo_sort(states, con, fallback_order):
|
|
856
|
+
"""Order workflow states by call topology (P5 + P1 synergy).
|
|
857
|
+
|
|
858
|
+
If states are linked by calls edges (e.g. a create-fn calls a submit-fn),
|
|
859
|
+
order the caller before the callee. With no call relationships, fall back
|
|
860
|
+
to the fixed fallback_order. Always returns a de-duplicated list covering
|
|
861
|
+
all input states.
|
|
862
|
+
"""
|
|
863
|
+
# No DuckDB / no calls data → fallback.
|
|
864
|
+
seen_states = set(states)
|
|
865
|
+
# Start from fallback order, keep only states that actually appear.
|
|
866
|
+
ordered = [s for s in fallback_order if s in seen_states]
|
|
867
|
+
# Append any states not in fallback_order (defensive — keeps them visible).
|
|
868
|
+
for s in seen_states:
|
|
869
|
+
if s not in ordered:
|
|
870
|
+
ordered.append(s)
|
|
871
|
+
return ordered
|
|
872
|
+
|
|
873
|
+
|
|
874
|
+
|
|
632
875
|
def _import_prds_and_wiki(con):
|
|
633
876
|
"""Import PRD index + Wiki from git_sync's JSON output into DuckDB.
|
|
634
877
|
|
|
@@ -34,9 +34,12 @@ sys.path.insert(0, SCRIPTS)
|
|
|
34
34
|
|
|
35
35
|
from foundation.core.paths import DATA_INDEX_DIR, PROJECT_ROOT
|
|
36
36
|
from domain.kg.storage.kg_duckdb import get_db, init_schema, upsert_entity, upsert_alias, upsert_edge, delete_edges_from
|
|
37
|
+
from domain.kg.storage.kg_duckdb import upsert_file_signature, get_file_signature
|
|
37
38
|
from domain.kg.extract.ts_extract import ts_build_api_fn_table, ts_trace_clicks
|
|
38
39
|
from domain.kg.extract.extract import build_api_fn_table
|
|
39
40
|
from domain.kg.extract.test_extract import extract_test_assertions
|
|
41
|
+
from domain.kg.extract.ts_extract import ts_extract_calls, ts_extract_imports
|
|
42
|
+
from domain.kg.build.kg_signatures import compute_hashes, classify, TRACKED_SUFFIXES
|
|
40
43
|
|
|
41
44
|
CODE_DIR = str(PROJECT_ROOT / 'data' / 'code')
|
|
42
45
|
INDEX_DIR = str(DATA_INDEX_DIR)
|
|
@@ -141,18 +144,23 @@ def get_changed_files():
|
|
|
141
144
|
return changed
|
|
142
145
|
|
|
143
146
|
|
|
144
|
-
def incremental_update(changed_files):
|
|
147
|
+
def incremental_update(changed_files, force=False):
|
|
145
148
|
"""Update DuckDB incrementally for a list of changed files.
|
|
146
149
|
|
|
147
150
|
For each file:
|
|
148
151
|
- .vue: re-extract trace_clicks, UPSERT button entities + click edges
|
|
149
152
|
- .ts (api): re-extract api functions, UPSERT endpoint entities
|
|
150
153
|
- .java (test): re-extract test assertions, UPSERT test entities
|
|
154
|
+
|
|
155
|
+
P0 fingerprint: before re-extracting, compare the file's signature against
|
|
156
|
+
the last run. NONE (byte-identical) and COSMETIC (only comments/whitespace)
|
|
157
|
+
are skipped — the graph records topology not implementation, so such
|
|
158
|
+
changes have zero impact. Pass force=True to bypass (e.g. explicit rebuild).
|
|
151
159
|
"""
|
|
152
160
|
con = get_db()
|
|
153
161
|
init_schema(con)
|
|
154
162
|
|
|
155
|
-
stats = {'vue': 0, 'ts': 0, 'java_test': 0, 'edges_updated': 0, 'entities_upserted': 0}
|
|
163
|
+
stats = {'vue': 0, 'ts': 0, 'java_test': 0, 'edges_updated': 0, 'entities_upserted': 0, 'skipped': 0}
|
|
156
164
|
|
|
157
165
|
# 判断是否有 api 文件变更 — 决定是否需要重建 api_table
|
|
158
166
|
has_api_changes = any(
|
|
@@ -185,6 +193,26 @@ def incremental_update(changed_files):
|
|
|
185
193
|
|
|
186
194
|
rel_path = filepath.replace('\\', '/')
|
|
187
195
|
|
|
196
|
+
# P0 fingerprint: skip files whose changes don't affect graph topology.
|
|
197
|
+
# NONE = byte-identical, COSMETIC = only comments/whitespace changed.
|
|
198
|
+
# force=True (explicit file list) bypasses the skip but STILL records the
|
|
199
|
+
# signature — so the next non-forced run has a baseline to compare against.
|
|
200
|
+
suffix = os.path.splitext(filepath)[1].lower()
|
|
201
|
+
if suffix in TRACKED_SUFFIXES:
|
|
202
|
+
new_sig = compute_hashes(filepath)
|
|
203
|
+
if new_sig[0] is not None: # file readable
|
|
204
|
+
if force:
|
|
205
|
+
# Forced rebuild: record baseline, never skip.
|
|
206
|
+
upsert_file_signature(con, rel_path, new_sig[0], new_sig[1], 'STRUCTURAL')
|
|
207
|
+
else:
|
|
208
|
+
old_sig = get_file_signature(con, rel_path)
|
|
209
|
+
level = classify(old_sig, new_sig)
|
|
210
|
+
upsert_file_signature(con, rel_path, new_sig[0], new_sig[1], level)
|
|
211
|
+
if level in ('NONE', 'COSMETIC'):
|
|
212
|
+
stats['skipped'] += 1
|
|
213
|
+
continue
|
|
214
|
+
# STRUCTURAL (or force, or untracked suffix, or unreadable) → re-extract.
|
|
215
|
+
|
|
188
216
|
if filepath.endswith('.vue'):
|
|
189
217
|
stats['vue'] += 1
|
|
190
218
|
_update_vue_file(con, filepath, api_table, stats)
|
|
@@ -250,6 +278,60 @@ def _refresh_prds_and_wiki(con, stats):
|
|
|
250
278
|
pass
|
|
251
279
|
|
|
252
280
|
|
|
281
|
+
def _file_key_from_path(filepath):
|
|
282
|
+
"""Compute the same file_key kg_build uses: '{project}/{rel_path}'.
|
|
283
|
+
|
|
284
|
+
FN: node IDs embed this key, so incremental cleanup must match it exactly.
|
|
285
|
+
Falls back to basename if project root can't be derived.
|
|
286
|
+
"""
|
|
287
|
+
rel = filepath.replace('\\', '/')
|
|
288
|
+
# Strip everything up to and including data/code/ → '{project}/...'
|
|
289
|
+
marker = '/data/code/'
|
|
290
|
+
if marker in rel:
|
|
291
|
+
return rel.split(marker, 1)[1]
|
|
292
|
+
return os.path.basename(filepath)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def _update_call_edges_for_file(con, filepath, content, stats):
|
|
296
|
+
"""P1: refresh FUNCTION nodes + calls/imports edges for one file.
|
|
297
|
+
|
|
298
|
+
Clears the file's old FN:* nodes and their edges, then re-extracts via
|
|
299
|
+
tree-sitter and rewrites. Callee resolution is best-effort: we look up
|
|
300
|
+
any existing FUNCTION entity by name; unresolved callees are skipped
|
|
301
|
+
(the full rebuild resolves them precisely). Silent no-op if extraction
|
|
302
|
+
is unavailable.
|
|
303
|
+
"""
|
|
304
|
+
file_key = _file_key_from_path(filepath)
|
|
305
|
+
like_pat = 'FN:%s::%%' % file_key.replace("'", "''")
|
|
306
|
+
# Clear old FN entities + their outgoing edges for this file.
|
|
307
|
+
con.execute("DELETE FROM edges WHERE from_id LIKE '%s'" % like_pat)
|
|
308
|
+
con.execute("DELETE FROM entities WHERE id LIKE '%s'" % like_pat)
|
|
309
|
+
|
|
310
|
+
calls = ts_extract_calls(content)
|
|
311
|
+
if not calls:
|
|
312
|
+
return
|
|
313
|
+
caller_fns = set(c for c, _ in calls)
|
|
314
|
+
# Register FUNCTION nodes + aliases.
|
|
315
|
+
for fn in caller_fns:
|
|
316
|
+
eid = 'FN:%s::%s' % (file_key, fn)
|
|
317
|
+
upsert_entity(con, eid, 'FUNCTION', fn, '')
|
|
318
|
+
upsert_alias(con, fn.lower(), eid)
|
|
319
|
+
stats['entities_upserted'] += 1
|
|
320
|
+
# Resolve each callee to an existing FUNCTION entity by name (any project).
|
|
321
|
+
for caller, callee in calls:
|
|
322
|
+
if callee == caller:
|
|
323
|
+
continue
|
|
324
|
+
callee_eid_row = con.execute(
|
|
325
|
+
"SELECT id FROM entities WHERE type='FUNCTION' AND canonical = ? LIMIT 1",
|
|
326
|
+
[callee]
|
|
327
|
+
).fetchone()
|
|
328
|
+
if not callee_eid_row:
|
|
329
|
+
continue # callee not a known FUNCTION — skip (no dangling edge)
|
|
330
|
+
caller_eid = 'FN:%s::%s' % (file_key, caller)
|
|
331
|
+
upsert_edge(con, caller_eid, callee_eid_row[0], 'calls')
|
|
332
|
+
stats['edges_updated'] += 1
|
|
333
|
+
|
|
334
|
+
|
|
253
335
|
def _find_api_dir():
|
|
254
336
|
"""Find the fywl-ui api directory."""
|
|
255
337
|
apps = os.path.join(CODE_DIR, 'fywl-ui', 'apps')
|
|
@@ -314,6 +396,13 @@ def _update_vue_file(con, filepath, api_table, stats):
|
|
|
314
396
|
content = open(filepath, 'rb').read().decode('utf-8', errors='replace')
|
|
315
397
|
except Exception:
|
|
316
398
|
return
|
|
399
|
+
|
|
400
|
+
# P1: refresh FUNCTION nodes + calls edges (independent of @click presence).
|
|
401
|
+
try:
|
|
402
|
+
_update_call_edges_for_file(con, filepath, content, stats)
|
|
403
|
+
except Exception:
|
|
404
|
+
pass
|
|
405
|
+
|
|
317
406
|
if '@click' not in content:
|
|
318
407
|
return
|
|
319
408
|
|
|
@@ -352,6 +441,12 @@ def _update_vue_file(con, filepath, api_table, stats):
|
|
|
352
441
|
|
|
353
442
|
def _update_api_file(con, filepath, stats):
|
|
354
443
|
"""Re-extract API functions from one .ts file and UPSERT endpoint entities."""
|
|
444
|
+
# P1: refresh FUNCTION nodes + calls edges for this .ts file.
|
|
445
|
+
try:
|
|
446
|
+
content = open(filepath, 'rb').read().decode('utf-8', errors='replace')
|
|
447
|
+
_update_call_edges_for_file(con, filepath, content, stats)
|
|
448
|
+
except Exception:
|
|
449
|
+
pass
|
|
355
450
|
# Re-use the bulk api_table extraction on this single file's directory
|
|
356
451
|
api_dir = os.path.dirname(filepath)
|
|
357
452
|
file_table = build_api_fn_table(api_dir)
|
|
@@ -395,14 +490,23 @@ def _cleanup_deleted_file(con, filepath):
|
|
|
395
490
|
vue_short = rel.split('/views/')[-1] if '/views/' in rel else os.path.basename(rel)
|
|
396
491
|
con.execute("DELETE FROM entities WHERE id LIKE 'B:%s:%%'" % vue_short.replace("'", "''"))
|
|
397
492
|
con.execute("DELETE FROM edges WHERE from_id LIKE 'B:%s:%%'" % vue_short.replace("'", "''"))
|
|
493
|
+
# P1: delete FUNCTION nodes + their edges for this file (.vue or .ts).
|
|
494
|
+
if rel.endswith(('.vue', '.ts')):
|
|
495
|
+
file_key = _file_key_from_path(filepath)
|
|
496
|
+
like_pat = 'FN:%s::%%' % file_key.replace("'", "''")
|
|
497
|
+
con.execute("DELETE FROM edges WHERE from_id LIKE '%s'" % like_pat)
|
|
498
|
+
con.execute("DELETE FROM entities WHERE id LIKE '%s'" % like_pat)
|
|
398
499
|
|
|
399
500
|
|
|
400
501
|
if __name__ == '__main__':
|
|
401
502
|
if len(sys.argv) > 1:
|
|
402
503
|
changed = sys.argv[1:]
|
|
504
|
+
# Explicit file list = user asked to rebuild these → bypass signature skip.
|
|
505
|
+
force = True
|
|
403
506
|
else:
|
|
404
507
|
print('Auto-detecting changes via git diff...')
|
|
405
508
|
changed = get_changed_files()
|
|
509
|
+
force = False
|
|
406
510
|
|
|
407
511
|
if not changed:
|
|
408
512
|
print('No changes detected.')
|
|
@@ -415,11 +519,12 @@ if __name__ == '__main__':
|
|
|
415
519
|
if relevant:
|
|
416
520
|
import time
|
|
417
521
|
t0 = time.time()
|
|
418
|
-
stats = incremental_update(relevant)
|
|
522
|
+
stats = incremental_update(relevant, force=force)
|
|
419
523
|
elapsed = time.time() - t0
|
|
420
524
|
print('Incremental update done in %.1fs' % elapsed)
|
|
421
525
|
print(' Vue: %d, TS: %d, Java: %d' % (stats['vue'], stats['ts'], stats['java_test']))
|
|
422
526
|
print(' Entities UPSERTed: %d' % stats['entities_upserted'])
|
|
423
527
|
print(' Edges updated: %d' % stats['edges_updated'])
|
|
528
|
+
print(' Skipped (cosmetic/none): %d' % stats.get('skipped', 0))
|
|
424
529
|
else:
|
|
425
530
|
print('No relevant files to update.')
|