java-codebase-rag 0.6.4__py3-none-any.whl → 0.6.5__py3-none-any.whl
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.
- build_ast_graph.py +463 -243
- {java_codebase_rag-0.6.4.dist-info → java_codebase_rag-0.6.5.dist-info}/METADATA +1 -1
- {java_codebase_rag-0.6.4.dist-info → java_codebase_rag-0.6.5.dist-info}/RECORD +9 -9
- java_index_flow_lancedb.py +12 -3
- path_filtering.py +9 -1
- {java_codebase_rag-0.6.4.dist-info → java_codebase_rag-0.6.5.dist-info}/WHEEL +0 -0
- {java_codebase_rag-0.6.4.dist-info → java_codebase_rag-0.6.5.dist-info}/entry_points.txt +0 -0
- {java_codebase_rag-0.6.4.dist-info → java_codebase_rag-0.6.5.dist-info}/licenses/LICENSE +0 -0
- {java_codebase_rag-0.6.4.dist-info → java_codebase_rag-0.6.5.dist-info}/top_level.txt +0 -0
build_ast_graph.py
CHANGED
|
@@ -39,6 +39,7 @@ from dataclasses import asdict, dataclass, field, replace
|
|
|
39
39
|
from pathlib import Path
|
|
40
40
|
|
|
41
41
|
import ladybug
|
|
42
|
+
import pyarrow as pa
|
|
42
43
|
|
|
43
44
|
from ast_java import (
|
|
44
45
|
ONTOLOGY_VERSION,
|
|
@@ -187,6 +188,12 @@ class TypeIndexEntry:
|
|
|
187
188
|
package: str
|
|
188
189
|
outer_fqn: str | None
|
|
189
190
|
node_id: str
|
|
191
|
+
# True when this entry was loaded from the existing graph by
|
|
192
|
+
# `_load_existing_types` (an unchanged-file stub used only for cross-file
|
|
193
|
+
# resolution). Its `decl` is a placeholder (no annotations/methods), so its
|
|
194
|
+
# recomputed role/capabilities must never be written back over the real
|
|
195
|
+
# stored values. See `_write_nodes_impl`.
|
|
196
|
+
loaded_from_db: bool = False
|
|
190
197
|
|
|
191
198
|
|
|
192
199
|
@dataclass
|
|
@@ -199,6 +206,11 @@ class MemberEntry:
|
|
|
199
206
|
module: str
|
|
200
207
|
microservice: str
|
|
201
208
|
node_id: str
|
|
209
|
+
# True when loaded from the existing graph by `_load_existing_members`
|
|
210
|
+
# (an unchanged-file stub used only for cross-file call resolution). Its
|
|
211
|
+
# DECLARES edge already persists in the graph, so it must not be re-emitted
|
|
212
|
+
# by `_populate_declares_rows` (REL tables have no PK → would duplicate).
|
|
213
|
+
loaded_from_db: bool = False
|
|
202
214
|
|
|
203
215
|
|
|
204
216
|
@dataclass
|
|
@@ -555,7 +567,7 @@ def _load_existing_types(conn: ladybug.Connection, tables: GraphTables, exclude_
|
|
|
555
567
|
if exclude_files is not None and not exclude_files:
|
|
556
568
|
return
|
|
557
569
|
|
|
558
|
-
where = "WHERE s.kind IN
|
|
570
|
+
where = f"WHERE s.kind IN {list(_TYPE_KINDS)}"
|
|
559
571
|
params: dict = {}
|
|
560
572
|
if exclude_files:
|
|
561
573
|
where += "\n AND NOT (s.filename IN $exclude_files)"
|
|
@@ -585,6 +597,7 @@ def _load_existing_types(conn: ladybug.Connection, tables: GraphTables, exclude_
|
|
|
585
597
|
package=package,
|
|
586
598
|
outer_fqn=None,
|
|
587
599
|
node_id=node_id,
|
|
600
|
+
loaded_from_db=True,
|
|
588
601
|
)
|
|
589
602
|
tables.types[fqn] = entry
|
|
590
603
|
tables.by_simple_name.setdefault(name, []).append(entry)
|
|
@@ -633,6 +646,7 @@ def _load_existing_members(conn: ladybug.Connection, tables: GraphTables, exclud
|
|
|
633
646
|
module="",
|
|
634
647
|
microservice="",
|
|
635
648
|
node_id=node_id,
|
|
649
|
+
loaded_from_db=True,
|
|
636
650
|
))
|
|
637
651
|
|
|
638
652
|
|
|
@@ -670,6 +684,54 @@ def _find_dependents(conn: ladybug.Connection, changed_node_ids: set[str]) -> se
|
|
|
670
684
|
return dependent_files
|
|
671
685
|
|
|
672
686
|
|
|
687
|
+
def _find_annotation_dependents(conn: ladybug.Connection, changed_node_ids: set[str]) -> set[str]:
|
|
688
|
+
"""Find files that USE an annotation whose DEFINITION is among the changed nodes.
|
|
689
|
+
|
|
690
|
+
Annotation usage is a node property (``annotations`` STRING[]), not a
|
|
691
|
+
Symbol->Symbol edge, so `_find_dependents` — which walks edges — never pulls
|
|
692
|
+
annotation users into scope. When an annotation definition changes (e.g.
|
|
693
|
+
``@interface Foo`` gains a meta-annotation that shifts the Layer-A chain in
|
|
694
|
+
`resolve_role_and_capabilities`), every type carrying ``@Foo`` may need its
|
|
695
|
+
``role``/``capabilities`` recomputed or it goes stale until the next full
|
|
696
|
+
rebuild. Return those users' files so the orchestrator treats them as
|
|
697
|
+
dependents (re-parsed, role re-SET); the expansion cap bounds the scope.
|
|
698
|
+
|
|
699
|
+
Scope is direct usage only: a user of an annotation that transitively
|
|
700
|
+
composes the changed one (e.g. ``@A`` where ``@A`` is meta-annotated with the
|
|
701
|
+
changed ``@B``) is NOT pulled in — that reverse-chain walk is left to a
|
|
702
|
+
future hardening pass. The direct case covers the dominant real-world shape
|
|
703
|
+
(a stereotype annotation applied directly to many types).
|
|
704
|
+
"""
|
|
705
|
+
if not changed_node_ids:
|
|
706
|
+
return set()
|
|
707
|
+
# Changed annotation definitions → the simple names users reference them by.
|
|
708
|
+
# Runs before `_delete_file_scope`, so the def nodes still exist.
|
|
709
|
+
name_result = conn.execute(
|
|
710
|
+
"MATCH (s:Symbol) WHERE s.id IN $ids AND s.kind = 'annotation' RETURN s.name",
|
|
711
|
+
{"ids": list(changed_node_ids)},
|
|
712
|
+
)
|
|
713
|
+
names: list[str] = []
|
|
714
|
+
while name_result.has_next():
|
|
715
|
+
nm = name_result.get_next()[0]
|
|
716
|
+
if nm:
|
|
717
|
+
names.append(nm)
|
|
718
|
+
if not names:
|
|
719
|
+
return set()
|
|
720
|
+
dependent_files: set[str] = set()
|
|
721
|
+
for nm in names:
|
|
722
|
+
user_result = conn.execute(
|
|
723
|
+
"MATCH (s:Symbol) "
|
|
724
|
+
"WHERE list_contains(s.annotations, $nm) AND s.filename <> '' "
|
|
725
|
+
"RETURN DISTINCT s.filename",
|
|
726
|
+
{"nm": nm},
|
|
727
|
+
)
|
|
728
|
+
while user_result.has_next():
|
|
729
|
+
fn = user_result.get_next()[0]
|
|
730
|
+
if fn:
|
|
731
|
+
dependent_files.add(fn)
|
|
732
|
+
return dependent_files
|
|
733
|
+
|
|
734
|
+
|
|
673
735
|
def _delete_file_scope(
|
|
674
736
|
conn: ladybug.Connection,
|
|
675
737
|
changed_files: set[str],
|
|
@@ -821,8 +883,8 @@ def _write_nodes_merge(
|
|
|
821
883
|
project_root: Path,
|
|
822
884
|
meta_chain: dict[str, frozenset[str]] | None,
|
|
823
885
|
) -> None:
|
|
824
|
-
"""Write nodes to existing LadybugDB database using
|
|
825
|
-
_write_nodes_impl(conn, tables, project_root=project_root, meta_chain=meta_chain
|
|
886
|
+
"""Write nodes to existing LadybugDB database using bulk COPY FROM."""
|
|
887
|
+
_write_nodes_impl(conn, tables, project_root=project_root, meta_chain=meta_chain)
|
|
826
888
|
|
|
827
889
|
|
|
828
890
|
# ---------- file walk (see `path_filtering.iter_java_source_files`) ----------
|
|
@@ -3004,27 +3066,112 @@ def _node_row(**kwargs) -> dict:
|
|
|
3004
3066
|
return base
|
|
3005
3067
|
|
|
3006
3068
|
|
|
3007
|
-
|
|
3008
|
-
"
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
)
|
|
3069
|
+
def _bulk_copy(conn: ladybug.Connection, table_name: str, columns: list[str], rows: list[dict]) -> None:
|
|
3070
|
+
"""Bulk-load rows into a node/rel table via in-memory pyarrow COPY FROM.
|
|
3071
|
+
|
|
3072
|
+
`columns` fixes column order; for REL tables the first two MUST be the
|
|
3073
|
+
FROM/TO node primary keys (kuzu requirement). Empty `rows` is a no-op.
|
|
3074
|
+
|
|
3075
|
+
Spike result (PR-P1 step-1): REL `COPY FROM` expects columns named `FROM` and `TO`
|
|
3076
|
+
for the endpoint node IDs, followed by property columns in the declared order.
|
|
3077
|
+
`pa.Table.from_pylist(rows)` correctly infers types from the dict values, but
|
|
3078
|
+
we must select columns in the exact order expected by the table schema.
|
|
3079
|
+
"""
|
|
3080
|
+
if not rows:
|
|
3081
|
+
return
|
|
3082
|
+
tbl = pa.Table.from_pylist(rows)
|
|
3083
|
+
# Select columns in the exact order expected by the table schema
|
|
3084
|
+
tbl = tbl.select(columns)
|
|
3085
|
+
conn.execute(f"COPY {table_name} FROM $rows", {"rows": tbl})
|
|
3016
3086
|
|
|
3017
|
-
|
|
3018
|
-
|
|
3087
|
+
|
|
3088
|
+
def _existing_node_ids(conn: ladybug.Connection) -> set[str]:
|
|
3089
|
+
"""Return every node id (Symbol, Route, Client, Producer) currently in the graph.
|
|
3090
|
+
|
|
3091
|
+
Bulk ``COPY FROM`` enforces referential integrity: a REL row whose FROM/TO
|
|
3092
|
+
endpoint isn't a loaded node raises ``Unable to find primary key value``. The
|
|
3093
|
+
legacy per-row ``MERGE (a:Symbol {id:$src}),(b:Symbol {id:$dst})`` silently
|
|
3094
|
+
dropped such edges (a ``MATCH`` against a missing endpoint creates nothing).
|
|
3095
|
+
Edge writers filter edge rows against this set to reproduce that exactly.
|
|
3096
|
+
|
|
3097
|
+
This queries the live DB rather than just ``tables`` because it is shared
|
|
3098
|
+
with the incremental path, whose edges legitimately reference nodes written
|
|
3099
|
+
in prior runs.
|
|
3100
|
+
"""
|
|
3101
|
+
result = conn.execute("MATCH (n) RETURN n.id")
|
|
3102
|
+
ids: set[str] = set()
|
|
3103
|
+
while result.has_next():
|
|
3104
|
+
ids.add(result.get_next()[0])
|
|
3105
|
+
return ids
|
|
3106
|
+
|
|
3107
|
+
|
|
3108
|
+
# Column-order constants for bulk COPY FROM.
|
|
3109
|
+
# For REL tables, the first two entries are FROM/TO node primary keys (kuzu requirement).
|
|
3110
|
+
# Order matches the corresponding _SCHEMA_* declarations above.
|
|
3111
|
+
_NODE_COLUMNS = [
|
|
3112
|
+
"id", "kind", "name", "fqn", "package", "module", "microservice",
|
|
3113
|
+
"filename", "start_line", "end_line", "start_byte", "end_byte",
|
|
3114
|
+
"modifiers", "annotations", "capabilities", "role", "signature", "parent_id", "resolved"
|
|
3115
|
+
]
|
|
3116
|
+
|
|
3117
|
+
# Type declaration kinds. Tuple (not set) so the rendered SQL `IN` clause is
|
|
3118
|
+
# deterministic. Used to (a) load type stubs for cross-file resolution and
|
|
3119
|
+
# (b) scope the incremental property-refresh SET to type nodes.
|
|
3120
|
+
_TYPE_KINDS: tuple[str, ...] = ("class", "interface", "enum", "annotation", "record")
|
|
3121
|
+
|
|
3122
|
+
# Update every mutable Symbol field on an existing node by primary key. Used on
|
|
3123
|
+
# the incremental path to refresh preserved dependent type nodes whose
|
|
3124
|
+
# `role`/`capabilities` (and other project-wide-derived fields) can shift
|
|
3125
|
+
# without their own source changing — restoring the upsert the legacy per-row
|
|
3126
|
+
# `MERGE (n:Symbol {id:$id}) SET …` provided. Field list mirrors `_NODE_COLUMNS`
|
|
3127
|
+
# minus `id`.
|
|
3128
|
+
_SET_SYMBOL_BY_ID = (
|
|
3129
|
+
"MATCH (n:Symbol {id: $id}) "
|
|
3019
3130
|
"SET n.kind = $kind, n.name = $name, n.fqn = $fqn, "
|
|
3020
3131
|
"n.package = $package, n.module = $module, n.microservice = $microservice, "
|
|
3021
3132
|
"n.filename = $filename, "
|
|
3022
3133
|
"n.start_line = $start_line, n.end_line = $end_line, "
|
|
3023
3134
|
"n.start_byte = $start_byte, n.end_byte = $end_byte, "
|
|
3024
|
-
"n.modifiers = $modifiers, n.annotations = $annotations,
|
|
3025
|
-
"n.
|
|
3135
|
+
"n.modifiers = $modifiers, n.annotations = $annotations, "
|
|
3136
|
+
"n.capabilities = $capabilities, n.role = $role, "
|
|
3137
|
+
"n.signature = $signature, n.parent_id = $parent_id, n.resolved = $resolved"
|
|
3138
|
+
)
|
|
3139
|
+
|
|
3140
|
+
# Refresh every mutable Route field on an existing Route node by id. Mirrors the
|
|
3141
|
+
# `_write_nodes_impl` Symbol pattern (bulk COPY new rows + per-row SET existing
|
|
3142
|
+
# ones) so the global pass 5-6 step no longer needs a per-row MERGE upsert.
|
|
3143
|
+
# Field list mirrors `_ROUTE_COLUMNS` minus `id`.
|
|
3144
|
+
_SET_ROUTE_BY_ID = (
|
|
3145
|
+
"MATCH (r:Route {id: $id}) "
|
|
3146
|
+
"SET r.kind = $kind, r.framework = $framework, r.method = $method, "
|
|
3147
|
+
"r.path = $path, r.path_template = $path_template, r.path_regex = $path_regex, "
|
|
3148
|
+
"r.topic = $topic, r.broker = $broker, r.feign_name = $feign_name, r.feign_url = $feign_url, "
|
|
3149
|
+
"r.microservice = $microservice, r.module = $module, r.filename = $filename, "
|
|
3150
|
+
"r.start_line = $start_line, r.end_line = $end_line, r.resolved = $resolved"
|
|
3026
3151
|
)
|
|
3027
3152
|
|
|
3153
|
+
_REL_EXTENDS_COLUMNS = ["FROM", "TO", "source_file", "dst_name", "dst_fqn", "resolved"]
|
|
3154
|
+
_REL_IMPLEMENTS_COLUMNS = ["FROM", "TO", "source_file", "dst_name", "dst_fqn", "resolved"]
|
|
3155
|
+
_REL_INJECTS_COLUMNS = ["FROM", "TO", "source_file", "dst_name", "dst_fqn", "resolved", "mechanism", "annotation", "field_or_param"]
|
|
3156
|
+
_REL_DECLARES_COLUMNS = ["FROM", "TO", "source_file"]
|
|
3157
|
+
_REL_OVERRIDES_COLUMNS = ["FROM", "TO", "source_file"]
|
|
3158
|
+
_REL_CALLS_COLUMNS = ["FROM", "TO", "source_file", "call_site_line", "call_site_byte", "arg_count", "confidence", "strategy", "source", "resolved", "callee_declaring_role"]
|
|
3159
|
+
|
|
3160
|
+
_UNRESOLVED_CALL_SITE_COLUMNS = ["id", "caller_id", "call_site_line", "call_site_byte", "arg_count", "callee_simple", "receiver_expr", "reason"]
|
|
3161
|
+
_REL_UNRESOLVED_AT_COLUMNS = ["FROM", "TO", "source_file"]
|
|
3162
|
+
|
|
3163
|
+
# Node table column constants (for bulk COPY FROM)
|
|
3164
|
+
_ROUTE_COLUMNS = ["id", "kind", "framework", "method", "path", "path_template", "path_regex", "topic", "broker", "feign_name", "feign_url", "microservice", "module", "filename", "start_line", "end_line", "resolved"]
|
|
3165
|
+
_CLIENT_COLUMNS = ["id", "client_kind", "target_service", "path", "path_template", "path_regex", "method", "member_fqn", "member_id", "microservice", "module", "filename", "start_line", "end_line", "resolved", "source_layer"]
|
|
3166
|
+
_PRODUCER_COLUMNS = ["id", "producer_kind", "topic", "broker", "direction", "member_fqn", "member_id", "microservice", "module", "filename", "start_line", "end_line", "resolved", "source_layer"]
|
|
3167
|
+
|
|
3168
|
+
# REL table column constants for routes/clients/producers
|
|
3169
|
+
_REL_EXPOSES_COLUMNS = ["FROM", "TO", "source_file", "confidence", "strategy"]
|
|
3170
|
+
_REL_DECLARES_CLIENT_COLUMNS = ["FROM", "TO", "source_file", "confidence", "strategy"]
|
|
3171
|
+
_REL_DECLARES_PRODUCER_COLUMNS = ["FROM", "TO", "source_file", "confidence", "strategy"]
|
|
3172
|
+
_REL_HTTP_CALLS_COLUMNS = ["FROM", "TO", "source_file", "confidence", "strategy", "method_call", "raw_uri", "match"]
|
|
3173
|
+
_REL_ASYNC_CALLS_COLUMNS = ["FROM", "TO", "source_file", "confidence", "strategy", "direction", "raw_topic", "match"]
|
|
3174
|
+
|
|
3028
3175
|
|
|
3029
3176
|
def _write_nodes_impl(
|
|
3030
3177
|
conn: ladybug.Connection,
|
|
@@ -3032,7 +3179,6 @@ def _write_nodes_impl(
|
|
|
3032
3179
|
*,
|
|
3033
3180
|
project_root: Path,
|
|
3034
3181
|
meta_chain: dict[str, frozenset[str]] | None,
|
|
3035
|
-
symbol_query: str,
|
|
3036
3182
|
) -> None:
|
|
3037
3183
|
overrides = load_brownfield_overrides(project_root)
|
|
3038
3184
|
try:
|
|
@@ -3041,18 +3187,28 @@ def _write_nodes_impl(
|
|
|
3041
3187
|
prs = str(project_root)
|
|
3042
3188
|
tables.cross_service_resolution = _load_config_cross_service_resolution(prs)
|
|
3043
3189
|
mch = meta_chain
|
|
3190
|
+
|
|
3191
|
+
# Stage all Symbol rows
|
|
3192
|
+
rows: list[dict] = []
|
|
3193
|
+
# Node ids loaded from the existing graph as resolution-only stubs
|
|
3194
|
+
# (`_load_existing_types`); their staged rows carry placeholder values and
|
|
3195
|
+
# must never be written back over the real nodes.
|
|
3196
|
+
stub_ids: set[str] = set()
|
|
3197
|
+
|
|
3044
3198
|
# packages
|
|
3045
3199
|
for pkg, pid in tables.packages.items():
|
|
3046
|
-
|
|
3200
|
+
rows.append(_node_row(
|
|
3047
3201
|
id=pid, kind="package", name=pkg.rsplit(".", 1)[-1], fqn=pkg, package=pkg,
|
|
3048
3202
|
))
|
|
3049
3203
|
# files
|
|
3050
3204
|
for path, fid in tables.files.items():
|
|
3051
|
-
|
|
3205
|
+
rows.append(_node_row(
|
|
3052
3206
|
id=fid, kind="file", name=Path(path).name, fqn=path, filename=path,
|
|
3053
3207
|
))
|
|
3054
3208
|
# types
|
|
3055
3209
|
for entry in tables.types.values():
|
|
3210
|
+
if entry.loaded_from_db:
|
|
3211
|
+
stub_ids.add(entry.node_id)
|
|
3056
3212
|
d = entry.decl
|
|
3057
3213
|
role, capabilities = resolve_role_and_capabilities(
|
|
3058
3214
|
d,
|
|
@@ -3060,7 +3216,7 @@ def _write_nodes_impl(
|
|
|
3060
3216
|
meta_chain=mch,
|
|
3061
3217
|
)
|
|
3062
3218
|
tables.type_role_by_node_id[entry.node_id] = role
|
|
3063
|
-
|
|
3219
|
+
rows.append(_node_row(
|
|
3064
3220
|
id=entry.node_id, kind=d.kind, name=d.name, fqn=d.fqn,
|
|
3065
3221
|
package=entry.package,
|
|
3066
3222
|
module=entry.module, microservice=entry.microservice,
|
|
@@ -3076,7 +3232,7 @@ def _write_nodes_impl(
|
|
|
3076
3232
|
))
|
|
3077
3233
|
# members (methods / constructors)
|
|
3078
3234
|
for m in tables.members:
|
|
3079
|
-
|
|
3235
|
+
rows.append(_node_row(
|
|
3080
3236
|
id=m.node_id, kind=m.kind, name=m.decl.name,
|
|
3081
3237
|
fqn=f"{m.parent_fqn}#{m.decl.signature}",
|
|
3082
3238
|
package=tables.types[m.parent_fqn].package if m.parent_fqn in tables.types else "",
|
|
@@ -3090,7 +3246,35 @@ def _write_nodes_impl(
|
|
|
3090
3246
|
))
|
|
3091
3247
|
# phantoms
|
|
3092
3248
|
for pid, row in tables.phantoms.items():
|
|
3093
|
-
|
|
3249
|
+
rows.append(row)
|
|
3250
|
+
|
|
3251
|
+
# Bulk-load new Symbol rows. The full-rebuild path starts from an empty
|
|
3252
|
+
# database (`_drop_all`), so every row is new. The incremental path reaches
|
|
3253
|
+
# here with a populated database: changed-file nodes were deleted by
|
|
3254
|
+
# `_delete_file_scope` (absent here → new), while dependent-file nodes are
|
|
3255
|
+
# deliberately preserved (see `_delete_file_scope` / issue #305).
|
|
3256
|
+
existing_ids = _existing_node_ids(conn)
|
|
3257
|
+
new_rows = [row for row in rows if row["id"] not in existing_ids]
|
|
3258
|
+
_bulk_copy(conn, "Symbol", _NODE_COLUMNS, new_rows)
|
|
3259
|
+
|
|
3260
|
+
# Refresh mutable properties on preserved dependent TYPE nodes (incremental
|
|
3261
|
+
# path only; `update_rows` is empty on the full path). `role`/`capabilities`
|
|
3262
|
+
# — and any other field derived from project-wide inputs (meta-annotation
|
|
3263
|
+
# chain, brownfield overrides) — can shift without the type's own source
|
|
3264
|
+
# changing, so a preserved dependent must be re-SET to stay byte-equivalent
|
|
3265
|
+
# with a full rebuild. The legacy per-row `_MERGE_SYMBOL` upserted every
|
|
3266
|
+
# staged node and did this implicitly; bulk `COPY FROM` only appends, so the
|
|
3267
|
+
# SET is explicit here. Stubs (`stub_ids`) are skipped: their decl is a
|
|
3268
|
+
# placeholder and their stored values are authoritative. Non-type kinds
|
|
3269
|
+
# carry no mutable role/capabilities, so they are skipped too.
|
|
3270
|
+
update_rows = [
|
|
3271
|
+
row for row in rows
|
|
3272
|
+
if row["id"] in existing_ids
|
|
3273
|
+
and row["id"] not in stub_ids
|
|
3274
|
+
and row["kind"] in _TYPE_KINDS
|
|
3275
|
+
]
|
|
3276
|
+
for row in update_rows:
|
|
3277
|
+
conn.execute(_SET_SYMBOL_BY_ID, row)
|
|
3094
3278
|
|
|
3095
3279
|
|
|
3096
3280
|
def _write_nodes(
|
|
@@ -3100,95 +3284,21 @@ def _write_nodes(
|
|
|
3100
3284
|
project_root: Path,
|
|
3101
3285
|
meta_chain: dict[str, frozenset[str]] | None,
|
|
3102
3286
|
) -> None:
|
|
3103
|
-
_write_nodes_impl(conn, tables, project_root=project_root, meta_chain=meta_chain
|
|
3287
|
+
_write_nodes_impl(conn, tables, project_root=project_root, meta_chain=meta_chain)
|
|
3104
3288
|
|
|
3105
3289
|
|
|
3106
|
-
_CREATE_EXT = (
|
|
3107
|
-
"MATCH (a:Symbol {id: $src}), (b:Symbol {id: $dst}) "
|
|
3108
|
-
"CREATE (a)-[:EXTENDS {source_file: $source_file, dst_name: $dst_name, dst_fqn: $dst_fqn, resolved: $resolved}]->(b)"
|
|
3109
|
-
)
|
|
3110
|
-
_CREATE_IMPL = (
|
|
3111
|
-
"MATCH (a:Symbol {id: $src}), (b:Symbol {id: $dst}) "
|
|
3112
|
-
"CREATE (a)-[:IMPLEMENTS {source_file: $source_file, dst_name: $dst_name, dst_fqn: $dst_fqn, resolved: $resolved}]->(b)"
|
|
3113
|
-
)
|
|
3114
|
-
_CREATE_INJ = (
|
|
3115
|
-
"MATCH (a:Symbol {id: $src}), (b:Symbol {id: $dst}) "
|
|
3116
|
-
"CREATE (a)-[:INJECTS {source_file: $source_file, dst_name: $dst_name, dst_fqn: $dst_fqn, resolved: $resolved, "
|
|
3117
|
-
"mechanism: $mechanism, annotation: $annotation, field_or_param: $field_or_param}]->(b)"
|
|
3118
|
-
)
|
|
3119
|
-
_CREATE_DECL = (
|
|
3120
|
-
"MATCH (a:Symbol {id: $src}), (b:Symbol {id: $dst}) "
|
|
3121
|
-
"CREATE (a)-[:DECLARES {source_file: $source_file}]->(b)"
|
|
3122
|
-
)
|
|
3123
|
-
_CREATE_OVERRIDES = (
|
|
3124
|
-
"MATCH (a:Symbol {id: $src}), (b:Symbol {id: $dst}) "
|
|
3125
|
-
"CREATE (a)-[:OVERRIDES {source_file: $source_file}]->(b)"
|
|
3126
|
-
)
|
|
3127
|
-
_CREATE_CALL = (
|
|
3128
|
-
"MATCH (a:Symbol {id: $src}), (b:Symbol {id: $dst}) "
|
|
3129
|
-
"CREATE (a)-[:CALLS {"
|
|
3130
|
-
"source_file: $source_file, "
|
|
3131
|
-
"call_site_line: $line, call_site_byte: $byte, arg_count: $argc, "
|
|
3132
|
-
"confidence: $conf, strategy: $strat, source: $src_kind, resolved: $resolved, "
|
|
3133
|
-
"callee_declaring_role: $callee_declaring_role"
|
|
3134
|
-
"}]->(b)"
|
|
3135
|
-
)
|
|
3136
|
-
|
|
3137
|
-
_CREATE_ROUTE = (
|
|
3138
|
-
"CREATE (:Route {"
|
|
3139
|
-
"id: $id, kind: $kind, framework: $framework, method: $method, "
|
|
3140
|
-
"path: $path, path_template: $path_template, path_regex: $path_regex, "
|
|
3141
|
-
"topic: $topic, broker: $broker, feign_name: $feign_name, feign_url: $feign_url, "
|
|
3142
|
-
"microservice: $microservice, module: $module, filename: $filename, "
|
|
3143
|
-
"start_line: $start_line, end_line: $end_line, resolved: $resolved"
|
|
3144
|
-
"})"
|
|
3145
|
-
)
|
|
3146
|
-
_CREATE_CLIENT = (
|
|
3147
|
-
"CREATE (:Client {"
|
|
3148
|
-
"id: $id, client_kind: $client_kind, target_service: $target_service, "
|
|
3149
|
-
"path: $path, path_template: $path_template, path_regex: $path_regex, method: $method, "
|
|
3150
|
-
"member_fqn: $member_fqn, member_id: $member_id, "
|
|
3151
|
-
"microservice: $microservice, module: $module, filename: $filename, "
|
|
3152
|
-
"start_line: $start_line, end_line: $end_line, resolved: $resolved, source_layer: $source_layer"
|
|
3153
|
-
"})"
|
|
3154
|
-
)
|
|
3155
|
-
|
|
3156
|
-
_CREATE_EXPOSES = (
|
|
3157
|
-
"MATCH (s:Symbol {id: $sid}), (r:Route {id: $rid}) "
|
|
3158
|
-
"CREATE (s)-[:EXPOSES {source_file: $source_file, confidence: $confidence, strategy: $strategy}]->(r)"
|
|
3159
|
-
)
|
|
3160
|
-
_CREATE_DECLARES_CLIENT = (
|
|
3161
|
-
"MATCH (s:Symbol {id: $sid}), (c:Client {id: $cid}) "
|
|
3162
|
-
"CREATE (s)-[:DECLARES_CLIENT {source_file: $source_file, confidence: $confidence, strategy: $strategy}]->(c)"
|
|
3163
|
-
)
|
|
3164
|
-
_CREATE_PRODUCER = (
|
|
3165
|
-
"CREATE (:Producer {"
|
|
3166
|
-
"id: $id, producer_kind: $producer_kind, topic: $topic, broker: $broker, "
|
|
3167
|
-
"direction: $direction, member_fqn: $member_fqn, member_id: $member_id, "
|
|
3168
|
-
"microservice: $microservice, module: $module, filename: $filename, "
|
|
3169
|
-
"start_line: $start_line, end_line: $end_line, resolved: $resolved, "
|
|
3170
|
-
"source_layer: $source_layer"
|
|
3171
|
-
"})"
|
|
3172
|
-
)
|
|
3173
|
-
_CREATE_DECLARES_PRODUCER = (
|
|
3174
|
-
"MATCH (s:Symbol {id: $sid}), (p:Producer {id: $pid}) "
|
|
3175
|
-
"CREATE (s)-[:DECLARES_PRODUCER {source_file: $source_file, confidence: $confidence, strategy: $strategy}]->(p)"
|
|
3176
|
-
)
|
|
3177
|
-
_CREATE_HTTP_CALL = (
|
|
3178
|
-
"MATCH (c:Client {id: $cid}), (r:Route {id: $rid}) "
|
|
3179
|
-
"CREATE (c)-[:HTTP_CALLS {source_file: $source_file, confidence: $confidence, strategy: $strategy, "
|
|
3180
|
-
"method_call: $method_call, raw_uri: $raw_uri, match: $match}]->(r)"
|
|
3181
|
-
)
|
|
3182
|
-
_CREATE_ASYNC_CALL = (
|
|
3183
|
-
"MATCH (p:Producer {id: $pid}), (r:Route {id: $rid}) "
|
|
3184
|
-
"CREATE (p)-[:ASYNC_CALLS {source_file: $source_file, confidence: $confidence, strategy: $strategy, "
|
|
3185
|
-
"direction: $direction, raw_topic: $raw_topic, match: $match}]->(r)"
|
|
3186
|
-
)
|
|
3187
3290
|
|
|
3188
3291
|
|
|
3189
3292
|
def _populate_declares_rows(tables: GraphTables) -> None:
|
|
3293
|
+
# Skip members loaded from the existing graph for cross-file resolution: a
|
|
3294
|
+
# DECLARES edge for an unchanged-file member already persists (its
|
|
3295
|
+
# source_file is out of scope, so `_delete_file_scope` left it), and
|
|
3296
|
+
# re-emitting it would append a duplicate (REL tables carry no primary key).
|
|
3297
|
+
# Full-rebuild never loads members, so this is a no-op there.
|
|
3190
3298
|
tables.declares_rows = [
|
|
3191
|
-
DeclaresRow(src_id=m.parent_id, dst_id=m.node_id)
|
|
3299
|
+
DeclaresRow(src_id=m.parent_id, dst_id=m.node_id)
|
|
3300
|
+
for m in tables.members
|
|
3301
|
+
if not m.loaded_from_db
|
|
3192
3302
|
]
|
|
3193
3303
|
|
|
3194
3304
|
|
|
@@ -3246,93 +3356,126 @@ def _write_edges(conn: ladybug.Connection, tables: GraphTables, _file_by_node_id
|
|
|
3246
3356
|
if _file_by_node_id is None:
|
|
3247
3357
|
_file_by_node_id = _build_file_by_node_id(tables)
|
|
3248
3358
|
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3359
|
+
# Bulk COPY FROM enforces referential integrity — a REL row whose endpoint
|
|
3360
|
+
# node isn't loaded raises "Unable to find primary key value". The legacy
|
|
3361
|
+
# per-row MERGE silently skipped such edges; drop them here to preserve the
|
|
3362
|
+
# per-row graph exactly. _existing_node_ids reads the live DB (not just
|
|
3363
|
+
# `tables`) so the incremental path's references to prior-run nodes still hold.
|
|
3364
|
+
valid_ids = _existing_node_ids(conn)
|
|
3365
|
+
|
|
3366
|
+
# Stage EXTENDS rows
|
|
3367
|
+
extends_rows = [
|
|
3368
|
+
{
|
|
3369
|
+
"FROM": r.src_id, "TO": r.dst_id,
|
|
3252
3370
|
"source_file": _file_by_node_id.get(r.src_id, ""),
|
|
3253
3371
|
"dst_name": r.dst_name, "dst_fqn": r.dst_fqn, "resolved": r.resolved,
|
|
3254
|
-
}
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3372
|
+
}
|
|
3373
|
+
for r in tables.extends_rows
|
|
3374
|
+
if r.src_id in valid_ids and r.dst_id in valid_ids
|
|
3375
|
+
]
|
|
3376
|
+
_bulk_copy(conn, "EXTENDS", _REL_EXTENDS_COLUMNS, extends_rows)
|
|
3377
|
+
|
|
3378
|
+
# Stage IMPLEMENTS rows
|
|
3379
|
+
implements_rows = [
|
|
3380
|
+
{
|
|
3381
|
+
"FROM": r.src_id, "TO": r.dst_id,
|
|
3258
3382
|
"source_file": _file_by_node_id.get(r.src_id, ""),
|
|
3259
3383
|
"dst_name": r.dst_name, "dst_fqn": r.dst_fqn, "resolved": r.resolved,
|
|
3260
|
-
}
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3384
|
+
}
|
|
3385
|
+
for r in tables.implements_rows
|
|
3386
|
+
if r.src_id in valid_ids and r.dst_id in valid_ids
|
|
3387
|
+
]
|
|
3388
|
+
_bulk_copy(conn, "IMPLEMENTS", _REL_IMPLEMENTS_COLUMNS, implements_rows)
|
|
3389
|
+
|
|
3390
|
+
# Stage INJECTS rows
|
|
3391
|
+
injects_rows = [
|
|
3392
|
+
{
|
|
3393
|
+
"FROM": r.src_id, "TO": r.dst_id,
|
|
3264
3394
|
"source_file": _file_by_node_id.get(r.src_id, ""),
|
|
3265
3395
|
"dst_name": r.dst_name, "dst_fqn": r.dst_fqn, "resolved": r.resolved,
|
|
3266
3396
|
"mechanism": r.mechanism, "annotation": r.annotation,
|
|
3267
3397
|
"field_or_param": r.field_or_param,
|
|
3268
|
-
}
|
|
3398
|
+
}
|
|
3399
|
+
for r in tables.injects_rows
|
|
3400
|
+
if r.src_id in valid_ids and r.dst_id in valid_ids
|
|
3401
|
+
]
|
|
3402
|
+
_bulk_copy(conn, "INJECTS", _REL_INJECTS_COLUMNS, injects_rows)
|
|
3269
3403
|
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3404
|
+
# Stage DECLARES rows
|
|
3405
|
+
declares_rows = [
|
|
3406
|
+
{
|
|
3407
|
+
"FROM": row.src_id, "TO": row.dst_id,
|
|
3273
3408
|
"source_file": _file_by_node_id.get(row.src_id, ""),
|
|
3274
|
-
}
|
|
3409
|
+
}
|
|
3410
|
+
for row in tables.declares_rows
|
|
3411
|
+
if row.src_id in valid_ids and row.dst_id in valid_ids
|
|
3412
|
+
]
|
|
3413
|
+
_bulk_copy(conn, "DECLARES", _REL_DECLARES_COLUMNS, declares_rows)
|
|
3275
3414
|
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3415
|
+
# Stage OVERRIDES rows
|
|
3416
|
+
overrides_rows = [
|
|
3417
|
+
{
|
|
3418
|
+
"FROM": row.src_id, "TO": row.dst_id,
|
|
3279
3419
|
"source_file": _file_by_node_id.get(row.src_id, ""),
|
|
3280
|
-
}
|
|
3420
|
+
}
|
|
3421
|
+
for row in tables.overrides_rows
|
|
3422
|
+
if row.src_id in valid_ids and row.dst_id in valid_ids
|
|
3423
|
+
]
|
|
3424
|
+
_bulk_copy(conn, "OVERRIDES", _REL_OVERRIDES_COLUMNS, overrides_rows)
|
|
3281
3425
|
|
|
3426
|
+
# Stage CALLS rows with dedup and callee_declaring_role materialization
|
|
3282
3427
|
seen_calls: set[tuple[str, str, int, int]] = set()
|
|
3283
|
-
|
|
3428
|
+
calls_rows: list[dict] = []
|
|
3429
|
+
member_by_id = {m.node_id: m for m in tables.members}
|
|
3284
3430
|
for row in tables.calls_rows:
|
|
3431
|
+
if row.src_id not in valid_ids or row.dst_id not in valid_ids:
|
|
3432
|
+
continue
|
|
3285
3433
|
key = (row.src_id, row.dst_id, row.arg_count, row.call_site_line)
|
|
3286
|
-
if key
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
for row in unique_calls:
|
|
3292
|
-
conn.execute(_CREATE_CALL, {
|
|
3293
|
-
"src": row.src_id, "dst": row.dst_id,
|
|
3434
|
+
if key in seen_calls:
|
|
3435
|
+
continue
|
|
3436
|
+
seen_calls.add(key)
|
|
3437
|
+
calls_rows.append({
|
|
3438
|
+
"FROM": row.src_id, "TO": row.dst_id,
|
|
3294
3439
|
"source_file": _file_by_node_id.get(row.src_id, ""),
|
|
3295
|
-
"
|
|
3296
|
-
"
|
|
3297
|
-
"
|
|
3298
|
-
"conf": row.confidence,
|
|
3299
|
-
"strat": row.strategy,
|
|
3300
|
-
"src_kind": row.source,
|
|
3301
|
-
"resolved": row.resolved,
|
|
3440
|
+
"call_site_line": row.call_site_line, "call_site_byte": row.call_site_byte,
|
|
3441
|
+
"arg_count": row.arg_count, "confidence": row.confidence, "strategy": row.strategy,
|
|
3442
|
+
"source": row.source, "resolved": row.resolved,
|
|
3302
3443
|
"callee_declaring_role": _callee_declaring_role_at_write(
|
|
3303
3444
|
tables, row.dst_id, member_by_id=member_by_id,
|
|
3304
3445
|
),
|
|
3305
3446
|
})
|
|
3447
|
+
_bulk_copy(conn, "CALLS", _REL_CALLS_COLUMNS, calls_rows)
|
|
3306
3448
|
|
|
3307
|
-
|
|
3308
|
-
"CREATE (:UnresolvedCallSite {"
|
|
3309
|
-
"id: $id, caller_id: $caller_id, call_site_line: $line, call_site_byte: $byte, "
|
|
3310
|
-
"arg_count: $argc, callee_simple: $callee, receiver_expr: $recv, reason: $reason"
|
|
3311
|
-
"})"
|
|
3312
|
-
)
|
|
3313
|
-
_CREATE_UNRESOLVED_AT = (
|
|
3314
|
-
"MATCH (a:Symbol {id: $caller}), (u:UnresolvedCallSite {id: $ucs}) "
|
|
3315
|
-
"CREATE (a)-[:UNRESOLVED_AT {source_file: $source_file}]->(u)"
|
|
3316
|
-
)
|
|
3449
|
+
# Stage UnresolvedCallSite node rows (must load before UNRESOLVED_AT edges)
|
|
3317
3450
|
seen_ucs: set[str] = set()
|
|
3451
|
+
ucs_rows: list[dict] = []
|
|
3318
3452
|
for row in tables.unresolved_call_site_rows:
|
|
3319
3453
|
if row.id in seen_ucs:
|
|
3320
3454
|
continue
|
|
3321
3455
|
seen_ucs.add(row.id)
|
|
3322
|
-
|
|
3456
|
+
ucs_rows.append({
|
|
3323
3457
|
"id": row.id,
|
|
3324
3458
|
"caller_id": row.caller_id,
|
|
3325
|
-
"
|
|
3326
|
-
"
|
|
3327
|
-
"
|
|
3328
|
-
"
|
|
3329
|
-
"
|
|
3459
|
+
"call_site_line": row.call_site_line,
|
|
3460
|
+
"call_site_byte": row.call_site_byte,
|
|
3461
|
+
"arg_count": row.arg_count,
|
|
3462
|
+
"callee_simple": row.callee_simple,
|
|
3463
|
+
"receiver_expr": row.receiver_expr,
|
|
3330
3464
|
"reason": row.reason,
|
|
3331
3465
|
})
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3466
|
+
_bulk_copy(conn, "UnresolvedCallSite", _UNRESOLVED_CALL_SITE_COLUMNS, ucs_rows)
|
|
3467
|
+
|
|
3468
|
+
# Stage UNRESOLVED_AT edge rows (one per unique UnresolvedCallSite node)
|
|
3469
|
+
# Use the same ucs_rows list to ensure 1:1 correspondence
|
|
3470
|
+
unresolved_at_rows = [
|
|
3471
|
+
{
|
|
3472
|
+
"FROM": ucs_row["caller_id"], "TO": ucs_row["id"],
|
|
3473
|
+
"source_file": _file_by_node_id.get(ucs_row["caller_id"], ""),
|
|
3474
|
+
}
|
|
3475
|
+
for ucs_row in ucs_rows
|
|
3476
|
+
if ucs_row["caller_id"] in valid_ids
|
|
3477
|
+
]
|
|
3478
|
+
_bulk_copy(conn, "UNRESOLVED_AT", _REL_UNRESOLVED_AT_COLUMNS, unresolved_at_rows)
|
|
3336
3479
|
|
|
3337
3480
|
|
|
3338
3481
|
def _write_routes_and_exposes(conn: ladybug.Connection, tables: GraphTables, _file_by_node_id: dict[str, str] | None = None) -> None:
|
|
@@ -3346,8 +3489,12 @@ def _write_routes_and_exposes(conn: ladybug.Connection, tables: GraphTables, _fi
|
|
|
3346
3489
|
# Build producer_id -> filename lookup for ASYNC_CALLS source_file.
|
|
3347
3490
|
_file_by_producer_id: dict[str, str] = {row.id: row.filename for row in tables.producer_rows}
|
|
3348
3491
|
|
|
3349
|
-
|
|
3350
|
-
|
|
3492
|
+
# Bulk COPY FROM enforces referential integrity — get all valid node IDs
|
|
3493
|
+
valid_ids = _existing_node_ids(conn)
|
|
3494
|
+
|
|
3495
|
+
# Stage Route node rows (bulk-load before edges that reference them)
|
|
3496
|
+
route_rows = [
|
|
3497
|
+
{
|
|
3351
3498
|
"id": row.id,
|
|
3352
3499
|
"kind": row.kind,
|
|
3353
3500
|
"framework": row.framework,
|
|
@@ -3365,57 +3512,97 @@ def _write_routes_and_exposes(conn: ladybug.Connection, tables: GraphTables, _fi
|
|
|
3365
3512
|
"start_line": row.start_line,
|
|
3366
3513
|
"end_line": row.end_line,
|
|
3367
3514
|
"resolved": row.resolved,
|
|
3368
|
-
}
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3515
|
+
}
|
|
3516
|
+
for row in tables.routes_rows
|
|
3517
|
+
]
|
|
3518
|
+
_bulk_copy(conn, "Route", _ROUTE_COLUMNS, route_rows)
|
|
3519
|
+
|
|
3520
|
+
# Stage Client node rows (bulk-load before edges that reference them)
|
|
3521
|
+
client_rows = [asdict(row) for row in tables.client_rows]
|
|
3522
|
+
_bulk_copy(conn, "Client", _CLIENT_COLUMNS, client_rows)
|
|
3523
|
+
|
|
3524
|
+
# Stage Producer node rows (bulk-load before edges that reference them)
|
|
3525
|
+
producer_rows = [asdict(row) for row in tables.producer_rows]
|
|
3526
|
+
_bulk_copy(conn, "Producer", _PRODUCER_COLUMNS, producer_rows)
|
|
3527
|
+
|
|
3528
|
+
# Re-fetch valid IDs after loading Route/Client/Producer nodes (now includes them)
|
|
3529
|
+
valid_ids = _existing_node_ids(conn)
|
|
3530
|
+
|
|
3531
|
+
# Stage EXPOSES edge rows (Symbol -> Route)
|
|
3532
|
+
exposes_rows = [
|
|
3533
|
+
{
|
|
3534
|
+
"FROM": row.symbol_id,
|
|
3535
|
+
"TO": row.route_id,
|
|
3373
3536
|
"source_file": _file_by_node_id.get(row.symbol_id, ""),
|
|
3374
3537
|
"confidence": row.confidence,
|
|
3375
3538
|
"strategy": row.strategy,
|
|
3376
|
-
}
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3539
|
+
}
|
|
3540
|
+
for row in tables.exposes_rows
|
|
3541
|
+
if row.symbol_id in valid_ids and row.route_id in valid_ids
|
|
3542
|
+
]
|
|
3543
|
+
_bulk_copy(conn, "EXPOSES", _REL_EXPOSES_COLUMNS, exposes_rows)
|
|
3544
|
+
|
|
3545
|
+
# Stage DECLARES_CLIENT edge rows (Symbol -> Client)
|
|
3546
|
+
declares_client_rows = [
|
|
3547
|
+
{
|
|
3548
|
+
"FROM": row.symbol_id,
|
|
3549
|
+
"TO": row.client_id,
|
|
3383
3550
|
"source_file": _file_by_node_id.get(row.symbol_id, ""),
|
|
3384
3551
|
"confidence": row.confidence,
|
|
3385
3552
|
"strategy": row.strategy,
|
|
3386
|
-
}
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3553
|
+
}
|
|
3554
|
+
for row in tables.declares_client_rows
|
|
3555
|
+
if row.symbol_id in valid_ids and row.client_id in valid_ids
|
|
3556
|
+
]
|
|
3557
|
+
_bulk_copy(conn, "DECLARES_CLIENT", _REL_DECLARES_CLIENT_COLUMNS, declares_client_rows)
|
|
3558
|
+
|
|
3559
|
+
# Stage DECLARES_PRODUCER edge rows (Symbol -> Producer)
|
|
3560
|
+
declares_producer_rows = [
|
|
3561
|
+
{
|
|
3562
|
+
"FROM": row.symbol_id,
|
|
3563
|
+
"TO": row.producer_id,
|
|
3393
3564
|
"source_file": _file_by_node_id.get(row.symbol_id, ""),
|
|
3394
3565
|
"confidence": row.confidence,
|
|
3395
3566
|
"strategy": row.strategy,
|
|
3396
|
-
}
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3567
|
+
}
|
|
3568
|
+
for row in tables.declares_producer_rows
|
|
3569
|
+
if row.symbol_id in valid_ids and row.producer_id in valid_ids
|
|
3570
|
+
]
|
|
3571
|
+
_bulk_copy(conn, "DECLARES_PRODUCER", _REL_DECLARES_PRODUCER_COLUMNS, declares_producer_rows)
|
|
3572
|
+
|
|
3573
|
+
# Stage HTTP_CALLS edge rows (Client -> Route)
|
|
3574
|
+
http_call_rows = [
|
|
3575
|
+
{
|
|
3576
|
+
"FROM": row.client_id,
|
|
3577
|
+
"TO": row.route_id,
|
|
3401
3578
|
"source_file": _file_by_client_id.get(row.client_id, ""),
|
|
3402
3579
|
"confidence": row.confidence,
|
|
3403
3580
|
"strategy": row.strategy,
|
|
3404
3581
|
"method_call": row.method_call,
|
|
3405
3582
|
"raw_uri": row.raw_uri,
|
|
3406
3583
|
"match": row.match,
|
|
3407
|
-
}
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3584
|
+
}
|
|
3585
|
+
for row in tables.http_call_rows
|
|
3586
|
+
if row.client_id in valid_ids and row.route_id in valid_ids
|
|
3587
|
+
]
|
|
3588
|
+
_bulk_copy(conn, "HTTP_CALLS", _REL_HTTP_CALLS_COLUMNS, http_call_rows)
|
|
3589
|
+
|
|
3590
|
+
# Stage ASYNC_CALLS edge rows (Producer -> Route)
|
|
3591
|
+
async_call_rows = [
|
|
3592
|
+
{
|
|
3593
|
+
"FROM": row.producer_id,
|
|
3594
|
+
"TO": row.route_id,
|
|
3412
3595
|
"source_file": _file_by_producer_id.get(row.producer_id, ""),
|
|
3413
3596
|
"confidence": row.confidence,
|
|
3414
3597
|
"strategy": row.strategy,
|
|
3415
3598
|
"direction": row.direction,
|
|
3416
3599
|
"raw_topic": row.raw_topic,
|
|
3417
3600
|
"match": row.match,
|
|
3418
|
-
}
|
|
3601
|
+
}
|
|
3602
|
+
for row in tables.async_call_rows
|
|
3603
|
+
if row.producer_id in valid_ids and row.route_id in valid_ids
|
|
3604
|
+
]
|
|
3605
|
+
_bulk_copy(conn, "ASYNC_CALLS", _REL_ASYNC_CALLS_COLUMNS, async_call_rows)
|
|
3419
3606
|
|
|
3420
3607
|
|
|
3421
3608
|
def _write_meta(conn: ladybug.Connection, tables: GraphTables, source_root: Path) -> None:
|
|
@@ -3650,6 +3837,12 @@ def incremental_rebuild(
|
|
|
3650
3837
|
# Find dependents
|
|
3651
3838
|
dependent_files = _find_dependents(conn, changed_node_ids)
|
|
3652
3839
|
|
|
3840
|
+
# Annotation-definition change: also pull in files that USE the changed
|
|
3841
|
+
# annotation. Annotation usage is a node property, not a Symbol->Symbol
|
|
3842
|
+
# edge, so `_find_dependents` misses them and their role (derived from
|
|
3843
|
+
# the project-wide meta-chain) would go stale. See PR-P5b.
|
|
3844
|
+
dependent_files |= _find_annotation_dependents(conn, changed_node_ids)
|
|
3845
|
+
|
|
3653
3846
|
# Union changed files with dependents
|
|
3654
3847
|
scope_files = changed_files | dependent_files
|
|
3655
3848
|
|
|
@@ -3814,80 +4007,107 @@ def _write_clients_producers_and_calls(conn: ladybug.Connection, tables: GraphTa
|
|
|
3814
4007
|
Route nodes (created by pass5 for cross-service calls) that wouldn't
|
|
3815
4008
|
otherwise exist in LadybugDB.
|
|
3816
4009
|
"""
|
|
3817
|
-
#
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
4010
|
+
# Bulk-write routes, mirroring `_write_nodes_impl`: COPY the rows that are new
|
|
4011
|
+
# to the DB, and SET every mutable field on the routes already present (the
|
|
4012
|
+
# caller does NOT delete routes, only Clients/Producers — see the global
|
|
4013
|
+
# pass 5-6 block). `tables.routes_rows` is the full route set (pass4 routes +
|
|
4014
|
+
# pass5 phantom routes), not just phantoms, so the SET keeps existing routes'
|
|
4015
|
+
# properties in sync with pass5's cross-service enrichment while the COPY
|
|
4016
|
+
# materializes phantoms that have no node yet. Replaces the last per-row
|
|
4017
|
+
# graph write (MERGE upsert).
|
|
4018
|
+
route_rows = [asdict(row) for row in tables.routes_rows]
|
|
4019
|
+
existing_route_ids = _existing_node_ids(conn)
|
|
4020
|
+
new_route_rows = [r for r in route_rows if r["id"] not in existing_route_ids]
|
|
4021
|
+
_bulk_copy(conn, "Route", _ROUTE_COLUMNS, new_route_rows)
|
|
4022
|
+
for r in route_rows:
|
|
4023
|
+
if r["id"] in existing_route_ids:
|
|
4024
|
+
conn.execute(_SET_ROUTE_BY_ID, r)
|
|
3829
4025
|
|
|
3830
4026
|
# Build node_id lookup for members and types
|
|
3831
4027
|
member_by_id = {m.node_id: m for m in tables.members}
|
|
3832
4028
|
|
|
3833
|
-
#
|
|
3834
|
-
for row in tables.client_rows:
|
|
3835
|
-
conn.execute(_CREATE_CLIENT, asdict(row))
|
|
3836
|
-
for row in tables.producer_rows:
|
|
3837
|
-
conn.execute(_CREATE_PRODUCER, asdict(row))
|
|
3838
|
-
|
|
4029
|
+
# Build client_id and producer_id lookups for source_file resolution
|
|
3839
4030
|
client_by_id = {c.id: c for c in tables.client_rows}
|
|
3840
4031
|
producer_by_id = {p.id: p for p in tables.producer_rows}
|
|
3841
4032
|
|
|
3842
|
-
#
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
4033
|
+
# Stage Client + Producer NODE rows unconditionally. The caller DETACH-DELETEs
|
|
4034
|
+
# every Client/Producer node immediately before calling this (see the global
|
|
4035
|
+
# pass 5-6 block), so none are in the DB yet and COPY-ing them fresh is safe.
|
|
4036
|
+
# Do NOT filter node rows against the existing-id set — that is the EDGE filter
|
|
4037
|
+
# pattern mis-applied to nodes, and would drop every node being created (the
|
|
4038
|
+
# caller's delete makes the pre-load set empty by construction).
|
|
4039
|
+
client_rows = [asdict(row) for row in tables.client_rows]
|
|
4040
|
+
_bulk_copy(conn, "Client", _CLIENT_COLUMNS, client_rows)
|
|
4041
|
+
producer_rows = [asdict(row) for row in tables.producer_rows]
|
|
4042
|
+
_bulk_copy(conn, "Producer", _PRODUCER_COLUMNS, producer_rows)
|
|
4043
|
+
|
|
4044
|
+
# Endpoint filtering applies to EDGES only: re-fetch the live node set (now
|
|
4045
|
+
# includes the Clients/Producers just loaded) and drop edge rows whose
|
|
4046
|
+
# endpoints still aren't materialized — reproduces per-row MERGE silent-drop.
|
|
4047
|
+
valid_ids = _existing_node_ids(conn)
|
|
4048
|
+
|
|
4049
|
+
# Stage DECLARES_CLIENT edge rows (Symbol -> Client)
|
|
4050
|
+
# Build source_file lookup from member_by_id
|
|
4051
|
+
declares_client_rows = [
|
|
4052
|
+
{
|
|
4053
|
+
"FROM": row.symbol_id,
|
|
4054
|
+
"TO": row.client_id,
|
|
4055
|
+
"source_file": member_by_id.get(row.symbol_id, MemberEntry(kind="", decl=None, parent_id="", parent_fqn="", file_path="", module="", microservice="", node_id="")).file_path,
|
|
3849
4056
|
"confidence": row.confidence,
|
|
3850
4057
|
"strategy": row.strategy,
|
|
3851
|
-
}
|
|
4058
|
+
}
|
|
4059
|
+
for row in tables.declares_client_rows
|
|
4060
|
+
if row.symbol_id in valid_ids and row.client_id in valid_ids
|
|
4061
|
+
]
|
|
4062
|
+
_bulk_copy(conn, "DECLARES_CLIENT", _REL_DECLARES_CLIENT_COLUMNS, declares_client_rows)
|
|
3852
4063
|
|
|
3853
|
-
#
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
"
|
|
3858
|
-
"
|
|
3859
|
-
"source_file": source_file,
|
|
4064
|
+
# Stage DECLARES_PRODUCER edge rows (Symbol -> Producer)
|
|
4065
|
+
declares_producer_rows = [
|
|
4066
|
+
{
|
|
4067
|
+
"FROM": row.symbol_id,
|
|
4068
|
+
"TO": row.producer_id,
|
|
4069
|
+
"source_file": member_by_id.get(row.symbol_id, MemberEntry(kind="", decl=None, parent_id="", parent_fqn="", file_path="", module="", microservice="", node_id="")).file_path,
|
|
3860
4070
|
"confidence": row.confidence,
|
|
3861
4071
|
"strategy": row.strategy,
|
|
3862
|
-
}
|
|
4072
|
+
}
|
|
4073
|
+
for row in tables.declares_producer_rows
|
|
4074
|
+
if row.symbol_id in valid_ids and row.producer_id in valid_ids
|
|
4075
|
+
]
|
|
4076
|
+
_bulk_copy(conn, "DECLARES_PRODUCER", _REL_DECLARES_PRODUCER_COLUMNS, declares_producer_rows)
|
|
3863
4077
|
|
|
3864
|
-
#
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
"
|
|
3869
|
-
"
|
|
3870
|
-
"source_file": client.filename if client else "",
|
|
4078
|
+
# Stage HTTP_CALLS edge rows (Client -> Route)
|
|
4079
|
+
http_call_rows = [
|
|
4080
|
+
{
|
|
4081
|
+
"FROM": row.client_id,
|
|
4082
|
+
"TO": row.route_id,
|
|
4083
|
+
"source_file": client_by_id.get(row.client_id, ClientRow(id="", client_kind="", target_service="", path="", path_template="", path_regex="", method="", member_fqn="", member_id="", microservice="", module="", filename="", start_line=0, end_line=0, resolved=False, source_layer="")).filename,
|
|
3871
4084
|
"confidence": row.confidence,
|
|
3872
4085
|
"strategy": row.strategy,
|
|
3873
4086
|
"method_call": row.method_call,
|
|
3874
4087
|
"raw_uri": row.raw_uri,
|
|
3875
4088
|
"match": row.match,
|
|
3876
|
-
}
|
|
4089
|
+
}
|
|
4090
|
+
for row in tables.http_call_rows
|
|
4091
|
+
if row.client_id in valid_ids and row.route_id in valid_ids
|
|
4092
|
+
]
|
|
4093
|
+
_bulk_copy(conn, "HTTP_CALLS", _REL_HTTP_CALLS_COLUMNS, http_call_rows)
|
|
3877
4094
|
|
|
3878
|
-
#
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
"
|
|
3883
|
-
"
|
|
3884
|
-
"source_file": producer.filename if producer else "",
|
|
4095
|
+
# Stage ASYNC_CALLS edge rows (Producer -> Route)
|
|
4096
|
+
async_call_rows = [
|
|
4097
|
+
{
|
|
4098
|
+
"FROM": row.producer_id,
|
|
4099
|
+
"TO": row.route_id,
|
|
4100
|
+
"source_file": producer_by_id.get(row.producer_id, ProducerRow(id="", producer_kind="", topic="", broker="", direction="", member_fqn="", member_id="", microservice="", module="", filename="", start_line=0, end_line=0, resolved=False, source_layer="")).filename,
|
|
3885
4101
|
"confidence": row.confidence,
|
|
3886
4102
|
"strategy": row.strategy,
|
|
3887
4103
|
"direction": row.direction,
|
|
3888
4104
|
"raw_topic": row.raw_topic,
|
|
3889
4105
|
"match": row.match,
|
|
3890
|
-
}
|
|
4106
|
+
}
|
|
4107
|
+
for row in tables.async_call_rows
|
|
4108
|
+
if row.producer_id in valid_ids and row.route_id in valid_ids
|
|
4109
|
+
]
|
|
4110
|
+
_bulk_copy(conn, "ASYNC_CALLS", _REL_ASYNC_CALLS_COLUMNS, async_call_rows)
|
|
3891
4111
|
|
|
3892
4112
|
|
|
3893
4113
|
def write_ladybug(
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
ast_java.py,sha256=Paee3ZV9G5iy8LqfkVq0Ah_1fV0k632oS5JPkiWzwB8,99206
|
|
2
2
|
brownfield_events.py,sha256=yxXkKDgMb3VPtaiakGzncHM_EGnda8xIue6w90yYp8s,2055
|
|
3
|
-
build_ast_graph.py,sha256=
|
|
3
|
+
build_ast_graph.py,sha256=jubb9Ex_6B3ktsInqNkk-EksBtQjofoFhE92lmHr308,169659
|
|
4
4
|
chunk_heuristics.py,sha256=aQk2NOKxzUdqoUAJUO3G3LE0MN_bYZWNLQ0tkmj5uts,1813
|
|
5
5
|
graph_enrich.py,sha256=W_OQK7YRU1K8wi-vfrxZWUd-EErTqWx840UBrhVpNsM,62788
|
|
6
6
|
index_common.py,sha256=HT6FKHFJ084eFvd3fR1j8z8gf4eWoPHVW8GXLpw464I,285
|
|
7
|
-
java_index_flow_lancedb.py,sha256=
|
|
7
|
+
java_index_flow_lancedb.py,sha256=JHdRWDZoZ3wnxi8NNG2ck8zEmSALHB2UVB1N8TSvlmg,24109
|
|
8
8
|
java_index_v1_common.py,sha256=nF1KrSqboF_RRvWerG9knRRFmWwsrG_CvhgnsoZ8KqA,1154
|
|
9
9
|
java_ontology.py,sha256=71bCLDNvMy0SpZPzSR5apJ0qJXNd6y5ggkLdBEw_PFo,16682
|
|
10
10
|
ladybug_queries.py,sha256=7vSP7WsUKQYXHFenBMlCdpUFed39h3oul-WRkf55YVc,90330
|
|
11
11
|
mcp_hints.py,sha256=3swh05LSiWur3tm3-yssndBsLxIxFhy501kBtJI8jJ0,42509
|
|
12
12
|
mcp_v2.py,sha256=S8PiVGDlyI7cvTmeMdQobhKUxWqKoY2YxXXr4pu9lQI,80348
|
|
13
|
-
path_filtering.py,sha256=
|
|
13
|
+
path_filtering.py,sha256=R--XzI51LXBu5IBKMCnJWbkNr6I5d-SDmltyQQnWco0,17674
|
|
14
14
|
pr_analysis.py,sha256=zrmZZD5yotJtM02Kif6_jgI_oeformOao793akp0N6Y,18394
|
|
15
15
|
search_lancedb.py,sha256=ga_qySeCzA6hCibxUPXpCbTxW9mCjTdwirMhMhfNCz4,36801
|
|
16
16
|
server.py,sha256=DcJwocSknBy0vwsUvYiC5hr-hrD_rRT-u9_DmuRTC9k,35035
|
|
@@ -26,9 +26,9 @@ java_codebase_rag/pipeline.py,sha256=ydNktEGL1YniAjJsr37yKBo_bGV4cN_LTGVTmmrsrZw
|
|
|
26
26
|
java_codebase_rag/progress.py,sha256=2IxdMALDM0wAQCyJrrfZ975zM_85C-4BfHxf4AtYifE,23212
|
|
27
27
|
java_codebase_rag/install_data/agents/explorer-rag-enhanced.md,sha256=BkdQpBEWqSdvGHgbqMdRb5CWfEiFRJK4Dgqbyal3l6s,14551
|
|
28
28
|
java_codebase_rag/install_data/skills/explore-codebase/SKILL.md,sha256=YkRnrM7Wh5E8raFjAW3RrN2V9-ov8upaGC3UdpSx6U8,12346
|
|
29
|
-
java_codebase_rag-0.6.
|
|
30
|
-
java_codebase_rag-0.6.
|
|
31
|
-
java_codebase_rag-0.6.
|
|
32
|
-
java_codebase_rag-0.6.
|
|
33
|
-
java_codebase_rag-0.6.
|
|
34
|
-
java_codebase_rag-0.6.
|
|
29
|
+
java_codebase_rag-0.6.5.dist-info/licenses/LICENSE,sha256=gxvtiHtuviR_q8ZAjWw-QTcF3DyPzg6ZY-lQrr8OPpw,1068
|
|
30
|
+
java_codebase_rag-0.6.5.dist-info/METADATA,sha256=jbKFqnBab9XoMAtIhfX-A9n0ztxhOEPrNcuH5G9qrEM,17242
|
|
31
|
+
java_codebase_rag-0.6.5.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
32
|
+
java_codebase_rag-0.6.5.dist-info/entry_points.txt,sha256=wsPZwot0Ui4JI3TIgW8LcbN8bNtKFbwQAlHAAJXfYgQ,117
|
|
33
|
+
java_codebase_rag-0.6.5.dist-info/top_level.txt,sha256=syQgi8XPBwY2ws_NZ1uRCxTf_s41NpshwEHNdcdnk3A,245
|
|
34
|
+
java_codebase_rag-0.6.5.dist-info/RECORD,,
|
java_index_flow_lancedb.py
CHANGED
|
@@ -60,16 +60,21 @@ if "detect_change" in _ck_params:
|
|
|
60
60
|
PROJECT_ROOT = coco.ContextKey[Path]("java_lance_project_root")
|
|
61
61
|
LANCE_DB = coco.ContextKey("java_lance_async_conn")
|
|
62
62
|
EMBEDDER = coco.ContextKey[SentenceTransformerEmbedder]("java_lance_embedder")
|
|
63
|
+
IGNORE = coco.ContextKey[LayeredIgnore]("java_lance_layered_ignore")
|
|
63
64
|
elif "tracked" in _ck_params:
|
|
64
65
|
PROJECT_ROOT = coco.ContextKey[Path]("java_lance_project_root", tracked=False)
|
|
65
66
|
LANCE_DB = coco.ContextKey("java_lance_async_conn", tracked=False)
|
|
66
67
|
EMBEDDER = coco.ContextKey[SentenceTransformerEmbedder](
|
|
67
68
|
"java_lance_embedder", tracked=False
|
|
68
69
|
)
|
|
70
|
+
IGNORE = coco.ContextKey[LayeredIgnore](
|
|
71
|
+
"java_lance_layered_ignore", tracked=False
|
|
72
|
+
)
|
|
69
73
|
else:
|
|
70
74
|
PROJECT_ROOT = coco.ContextKey[Path]("java_lance_project_root")
|
|
71
75
|
LANCE_DB = coco.ContextKey("java_lance_async_conn")
|
|
72
76
|
EMBEDDER = coco.ContextKey[SentenceTransformerEmbedder]("java_lance_embedder")
|
|
77
|
+
IGNORE = coco.ContextKey[LayeredIgnore]("java_lance_layered_ignore")
|
|
73
78
|
|
|
74
79
|
splitter = RecursiveSplitter()
|
|
75
80
|
|
|
@@ -292,6 +297,7 @@ async def coco_lifespan(builder: coco.EnvironmentBuilder) -> AsyncIterator[None]
|
|
|
292
297
|
trust_remote_code=True,
|
|
293
298
|
)
|
|
294
299
|
builder.provide(EMBEDDER, embedder)
|
|
300
|
+
builder.provide(IGNORE, LayeredIgnore(root))
|
|
295
301
|
|
|
296
302
|
uri = str(index_dir)
|
|
297
303
|
|
|
@@ -348,7 +354,8 @@ async def process_java_file(
|
|
|
348
354
|
) -> None:
|
|
349
355
|
embedder = coco.use_context(EMBEDDER)
|
|
350
356
|
project_root = coco.use_context(PROJECT_ROOT)
|
|
351
|
-
|
|
357
|
+
ignore = coco.use_context(IGNORE)
|
|
358
|
+
if ignore.is_ignored((project_root / file.file_path.path).resolve()):
|
|
352
359
|
return
|
|
353
360
|
try:
|
|
354
361
|
content = await file.read_text()
|
|
@@ -420,7 +427,8 @@ async def process_sql_file(
|
|
|
420
427
|
) -> None:
|
|
421
428
|
embedder = coco.use_context(EMBEDDER)
|
|
422
429
|
project_root = coco.use_context(PROJECT_ROOT)
|
|
423
|
-
|
|
430
|
+
ignore = coco.use_context(IGNORE)
|
|
431
|
+
if ignore.is_ignored((project_root / file.file_path.path).resolve()):
|
|
424
432
|
return
|
|
425
433
|
try:
|
|
426
434
|
content = await file.read_text()
|
|
@@ -468,7 +476,8 @@ async def process_yaml_file(
|
|
|
468
476
|
) -> None:
|
|
469
477
|
embedder = coco.use_context(EMBEDDER)
|
|
470
478
|
project_root = coco.use_context(PROJECT_ROOT)
|
|
471
|
-
|
|
479
|
+
ignore = coco.use_context(IGNORE)
|
|
480
|
+
if ignore.is_ignored((project_root / file.file_path.path).resolve()):
|
|
472
481
|
return
|
|
473
482
|
try:
|
|
474
483
|
content = await file.read_text()
|
path_filtering.py
CHANGED
|
@@ -300,6 +300,7 @@ class LayeredIgnore:
|
|
|
300
300
|
_scan_negation_any_bundle_ignore(self.project_root)
|
|
301
301
|
or (use_gitignore and _scan_negation_any_gitignore(self.project_root))
|
|
302
302
|
)
|
|
303
|
+
self._mega_cache: dict[str, tuple[list[str], GitIgnoreSpec, list[tuple[str, Path | None, int, str]]]] = {}
|
|
303
304
|
|
|
304
305
|
def cocoindex_excluded_patterns(self) -> list[str]:
|
|
305
306
|
"""Patterns for CocoIndex ``PatternFilePathMatcher.excluded_patterns``.
|
|
@@ -332,6 +333,11 @@ class LayeredIgnore:
|
|
|
332
333
|
return path.as_posix()
|
|
333
334
|
|
|
334
335
|
def _mega(self, rel_project: str) -> tuple[list[str], GitIgnoreSpec, list[tuple[str, Path | None, int, str]]]:
|
|
336
|
+
# Cache by directory (parent of rel_project). _mega_build_for_rel reads only dir_parts,
|
|
337
|
+
# so files in the same directory share the same mega/spec/meta tuple.
|
|
338
|
+
cache_key = Path(rel_project).parent.as_posix()
|
|
339
|
+
if cache_key in self._mega_cache:
|
|
340
|
+
return self._mega_cache[cache_key]
|
|
335
341
|
mega, meta = _mega_build_for_rel(
|
|
336
342
|
self.project_root,
|
|
337
343
|
rel_project,
|
|
@@ -340,7 +346,9 @@ class LayeredIgnore:
|
|
|
340
346
|
project_ignore_path=self._project_ignore_path,
|
|
341
347
|
project_lines=self._project_lines,
|
|
342
348
|
)
|
|
343
|
-
|
|
349
|
+
result = (mega, GitIgnoreSpec.from_lines(mega), meta)
|
|
350
|
+
self._mega_cache[cache_key] = result
|
|
351
|
+
return result
|
|
344
352
|
|
|
345
353
|
def is_ignored(self, path: Path) -> bool:
|
|
346
354
|
"""Return whether ``path`` is ignored by any configured layer.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|