{"$schema":"https://json.schemastore.org/sarif-2.1.0.json","version":"2.1.0","runs":[{"tool":{"driver":{"name":"codehealth","informationUri":"https://codehealth.canine.dev","rules":[{"id":"D1","name":"Cyclomatic Complexity","shortDescription":{"text":"Cyclomatic Complexity"},"helpUri":"https://codehealth.canine.dev/dimensions/D1"},{"id":"D2","name":"Cognitive Complexity","shortDescription":{"text":"Cognitive Complexity"},"helpUri":"https://codehealth.canine.dev/dimensions/D2"},{"id":"D3","name":"God Classes","shortDescription":{"text":"God Classes"},"helpUri":"https://codehealth.canine.dev/dimensions/D3"},{"id":"D4","name":"Code Duplication","shortDescription":{"text":"Code Duplication"},"helpUri":"https://codehealth.canine.dev/dimensions/D4"},{"id":"D5","name":"Coupling","shortDescription":{"text":"Coupling"},"helpUri":"https://codehealth.canine.dev/dimensions/D5"},{"id":"D6","name":"Cohesion (LCOM4)","shortDescription":{"text":"Cohesion (LCOM4)"},"helpUri":"https://codehealth.canine.dev/dimensions/D6"},{"id":"D9","name":"Test Distribution","shortDescription":{"text":"Test Distribution"},"helpUri":"https://codehealth.canine.dev/dimensions/D9"},{"id":"D11","name":"Test Reliability","shortDescription":{"text":"Test Reliability"},"helpUri":"https://codehealth.canine.dev/dimensions/D11"},{"id":"D12","name":"Dependency Hygiene","shortDescription":{"text":"Dependency Hygiene"},"helpUri":"https://codehealth.canine.dev/dimensions/D12"},{"id":"D13","name":"Secret Scanning","shortDescription":{"text":"Secret Scanning"},"helpUri":"https://codehealth.canine.dev/dimensions/D13","relationships":[{"target":{"id":"CWE-798","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},"kinds":["relevant"]},{"target":{"id":"CWE-259","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},"kinds":["relevant"]},{"target":{"id":"A07:2025","toolComponent":{"name":"OWASP Top 10","guid":"7d1f0c2e-4b6a-4e39-9a51-0b2e8c3d5f61"}},"kinds":["relevant"]}],"properties":{"cwe":["CWE-798","CWE-259"],"owasp":"A07:2025"}},{"id":"D14","name":"License Compliance","shortDescription":{"text":"License Compliance"},"helpUri":"https://codehealth.canine.dev/dimensions/D14"},{"id":"D15","name":"Churn \u00D7 Complexity Hotspots","shortDescription":{"text":"Churn \u00D7 Complexity Hotspots"},"helpUri":"https://codehealth.canine.dev/dimensions/D15"},{"id":"D16","name":"Bus Factor","shortDescription":{"text":"Bus Factor"},"helpUri":"https://codehealth.canine.dev/dimensions/D16"},{"id":"D17","name":"Explicit Debt","shortDescription":{"text":"Explicit Debt"},"helpUri":"https://codehealth.canine.dev/dimensions/D17"},{"id":"D19","name":"Documentation Quality","shortDescription":{"text":"Documentation Quality"},"helpUri":"https://codehealth.canine.dev/dimensions/D19"},{"id":"D21","name":"Naming Consistency","shortDescription":{"text":"Naming Consistency"},"helpUri":"https://codehealth.canine.dev/dimensions/D21"},{"id":"D26","name":"Project Cohesion","shortDescription":{"text":"Project Cohesion"},"helpUri":"https://codehealth.canine.dev/dimensions/D26"},{"id":"D28","name":"Secrets (history)","shortDescription":{"text":"Secrets (history)"},"helpUri":"https://codehealth.canine.dev/dimensions/D28","relationships":[{"target":{"id":"CWE-798","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},"kinds":["relevant"]},{"target":{"id":"CWE-259","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},"kinds":["relevant"]},{"target":{"id":"A07:2025","toolComponent":{"name":"OWASP Top 10","guid":"7d1f0c2e-4b6a-4e39-9a51-0b2e8c3d5f61"}},"kinds":["relevant"]}],"properties":{"cwe":["CWE-798","CWE-259"],"owasp":"A07:2025"}},{"id":"D29","name":"Static Analysis (SAST)","shortDescription":{"text":"Static Analysis (SAST)"},"helpUri":"https://codehealth.canine.dev/dimensions/D29","relationships":[{"target":{"id":"CWE-79","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},"kinds":["relevant"]},{"target":{"id":"CWE-89","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},"kinds":["relevant"]},{"target":{"id":"CWE-78","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},"kinds":["relevant"]},{"target":{"id":"CWE-94","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},"kinds":["relevant"]},{"target":{"id":"CWE-77","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},"kinds":["relevant"]},{"target":{"id":"A05:2025","toolComponent":{"name":"OWASP Top 10","guid":"7d1f0c2e-4b6a-4e39-9a51-0b2e8c3d5f61"}},"kinds":["relevant"]}],"properties":{"cwe":["CWE-79","CWE-89","CWE-78","CWE-94","CWE-77"],"owasp":"A05:2025"}},{"id":"D30","name":"Dependency Vulnerabilities","shortDescription":{"text":"Dependency Vulnerabilities"},"helpUri":"https://codehealth.canine.dev/dimensions/D30","relationships":[{"target":{"id":"CWE-1395","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},"kinds":["relevant"]},{"target":{"id":"CWE-937","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},"kinds":["relevant"]},{"target":{"id":"A03:2025","toolComponent":{"name":"OWASP Top 10","guid":"7d1f0c2e-4b6a-4e39-9a51-0b2e8c3d5f61"}},"kinds":["relevant"]}],"properties":{"cwe":["CWE-1395","CWE-937"],"owasp":"A03:2025"}},{"id":"D31","name":"IaC \u0026 Container Security","shortDescription":{"text":"IaC \u0026 Container Security"},"helpUri":"https://codehealth.canine.dev/dimensions/D31","relationships":[{"target":{"id":"CWE-1032","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},"kinds":["relevant"]},{"target":{"id":"CWE-732","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},"kinds":["relevant"]},{"target":{"id":"CWE-16","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},"kinds":["relevant"]},{"target":{"id":"A02:2025","toolComponent":{"name":"OWASP Top 10","guid":"7d1f0c2e-4b6a-4e39-9a51-0b2e8c3d5f61"}},"kinds":["relevant"]}],"properties":{"cwe":["CWE-1032","CWE-732","CWE-16"],"owasp":"A02:2025"}},{"id":"D34","name":"Knowledge Freshness","shortDescription":{"text":"Knowledge Freshness"},"helpUri":"https://codehealth.canine.dev/dimensions/D34"},{"id":"D35","name":"Change Coupling","shortDescription":{"text":"Change Coupling"},"helpUri":"https://codehealth.canine.dev/dimensions/D35"},{"id":"D36","name":"Supply-chain Provenance \u0026 Signing","shortDescription":{"text":"Supply-chain Provenance \u0026 Signing"},"helpUri":"https://codehealth.canine.dev/dimensions/D36","relationships":[{"target":{"id":"CWE-1357","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},"kinds":["relevant"]},{"target":{"id":"CWE-494","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},"kinds":["relevant"]},{"target":{"id":"A03:2025","toolComponent":{"name":"OWASP Top 10","guid":"7d1f0c2e-4b6a-4e39-9a51-0b2e8c3d5f61"}},"kinds":["relevant"]}],"properties":{"cwe":["CWE-1357","CWE-494"],"owasp":"A03:2025"}},{"id":"D37","name":"Vulnerability-disclosure Policy","shortDescription":{"text":"Vulnerability-disclosure Policy"},"helpUri":"https://codehealth.canine.dev/dimensions/D37","relationships":[{"target":{"id":"CWE-1059","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},"kinds":["relevant"]},{"target":{"id":"A02:2025","toolComponent":{"name":"OWASP Top 10","guid":"7d1f0c2e-4b6a-4e39-9a51-0b2e8c3d5f61"}},"kinds":["relevant"]}],"properties":{"cwe":["CWE-1059"],"owasp":"A02:2025"}},{"id":"D43","name":"Malicious Dependencies","shortDescription":{"text":"Malicious Dependencies"},"helpUri":"https://codehealth.canine.dev/dimensions/D43","relationships":[{"target":{"id":"CWE-506","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},"kinds":["relevant"]},{"target":{"id":"A08:2025","toolComponent":{"name":"OWASP Top 10","guid":"7d1f0c2e-4b6a-4e39-9a51-0b2e8c3d5f61"}},"kinds":["relevant"]}],"properties":{"cwe":["CWE-506"],"owasp":"A08:2025"}},{"id":"D44","name":"Platform End-of-Life","shortDescription":{"text":"Platform End-of-Life"},"helpUri":"https://codehealth.canine.dev/dimensions/D44"},{"id":"AX10","name":"Code composition","shortDescription":{"text":"Code composition"},"helpUri":"https://codehealth.canine.dev/dimensions/AX10"},{"id":"AX3","name":"Project dependency cycles","shortDescription":{"text":"Project dependency cycles"},"helpUri":"https://codehealth.canine.dev/dimensions/AX3"},{"id":"AX4","name":"Dependency direction","shortDescription":{"text":"Dependency direction"},"helpUri":"https://codehealth.canine.dev/dimensions/AX4"},{"id":"AXB2","name":"Runtime readiness","shortDescription":{"text":"Runtime readiness"},"helpUri":"https://codehealth.canine.dev/dimensions/AXB2"},{"id":"C2","name":"Access Controls","shortDescription":{"text":"Access Controls"},"helpUri":"https://codehealth.canine.dev/dimensions/C2"},{"id":"ED5","name":"Idempotency","shortDescription":{"text":"Idempotency"},"helpUri":"https://codehealth.canine.dev/dimensions/ED5"},{"id":"M1","name":"Documentation (README)","shortDescription":{"text":"Documentation (README)"},"helpUri":"https://codehealth.canine.dev/dimensions/M1"},{"id":"M2","name":"Architecture documentation","shortDescription":{"text":"Architecture documentation"},"helpUri":"https://codehealth.canine.dev/dimensions/M2"},{"id":"M3","name":"Folder \u0026 project structure","shortDescription":{"text":"Folder \u0026 project structure"},"helpUri":"https://codehealth.canine.dev/dimensions/M3"},{"id":"M4","name":"Documentation accuracy","shortDescription":{"text":"Documentation accuracy"},"helpUri":"https://codehealth.canine.dev/dimensions/M4"},{"id":"P1","name":"CI/CD gates","shortDescription":{"text":"CI/CD gates"},"helpUri":"https://codehealth.canine.dev/dimensions/P1"},{"id":"P10","name":"Library API \u0026 versioning","shortDescription":{"text":"Library API \u0026 versioning"},"helpUri":"https://codehealth.canine.dev/dimensions/P10"},{"id":"P12","name":"CI test-gate honesty","shortDescription":{"text":"CI test-gate honesty"},"helpUri":"https://codehealth.canine.dev/dimensions/P12"},{"id":"P2","name":"Observability","shortDescription":{"text":"Observability"},"helpUri":"https://codehealth.canine.dev/dimensions/P2"},{"id":"P3","name":"Security \u0026 performance tooling","shortDescription":{"text":"Security \u0026 performance tooling"},"helpUri":"https://codehealth.canine.dev/dimensions/P3"},{"id":"P4","name":"Deployment \u0026 Rollback","shortDescription":{"text":"Deployment \u0026 Rollback"},"helpUri":"https://codehealth.canine.dev/dimensions/P4"},{"id":"P5","name":"DR \u0026 Backup","shortDescription":{"text":"DR \u0026 Backup"},"helpUri":"https://codehealth.canine.dev/dimensions/P5"},{"id":"P6","name":"Release Hygiene","shortDescription":{"text":"Release Hygiene"},"helpUri":"https://codehealth.canine.dev/dimensions/P6"},{"id":"P7","name":"Outbound HTTP resilience","shortDescription":{"text":"Outbound HTTP resilience"},"helpUri":"https://codehealth.canine.dev/dimensions/P7"},{"id":"PF3","name":"Async \u0026 latency hygiene","shortDescription":{"text":"Async \u0026 latency hygiene"},"helpUri":"https://codehealth.canine.dev/dimensions/PF3"},{"id":"X1","name":"Async correctness","shortDescription":{"text":"Async correctness"},"helpUri":"https://codehealth.canine.dev/dimensions/X1"},{"id":"X10","name":"Duplicated predicate","shortDescription":{"text":"Duplicated predicate"},"helpUri":"https://codehealth.canine.dev/dimensions/X10"},{"id":"X12","name":"Unreachable branch","shortDescription":{"text":"Unreachable branch"},"helpUri":"https://codehealth.canine.dev/dimensions/X12"},{"id":"X13","name":"Undrained process stream","shortDescription":{"text":"Undrained process stream"},"helpUri":"https://codehealth.canine.dev/dimensions/X13"},{"id":"X14","name":"Bypassable address classification","shortDescription":{"text":"Bypassable address classification"},"helpUri":"https://codehealth.canine.dev/dimensions/X14"},{"id":"X15","name":"Unvalidated length from an untrusted reader","shortDescription":{"text":"Unvalidated length from an untrusted reader"},"helpUri":"https://codehealth.canine.dev/dimensions/X15"},{"id":"X17","name":"Uncapped recursion over a caller-supplied document","shortDescription":{"text":"Uncapped recursion over a caller-supplied document"},"helpUri":"https://codehealth.canine.dev/dimensions/X17"},{"id":"X19","name":"Unrestored process-global state","shortDescription":{"text":"Unrestored process-global state"},"helpUri":"https://codehealth.canine.dev/dimensions/X19"},{"id":"X20","name":"Mistyped argument guard","shortDescription":{"text":"Mistyped argument guard"},"helpUri":"https://codehealth.canine.dev/dimensions/X20"},{"id":"X21","name":"Side-effecting pattern guard","shortDescription":{"text":"Side-effecting pattern guard"},"helpUri":"https://codehealth.canine.dev/dimensions/X21"},{"id":"X22","name":"Contradicted release guard","shortDescription":{"text":"Contradicted release guard"},"helpUri":"https://codehealth.canine.dev/dimensions/X22"},{"id":"X23","name":"Unguarded diagnostic materialisation","shortDescription":{"text":"Unguarded diagnostic materialisation"},"helpUri":"https://codehealth.canine.dev/dimensions/X23"},{"id":"X24","name":"Document value interpolated into markup unescaped","shortDescription":{"text":"Document value interpolated into markup unescaped"},"helpUri":"https://codehealth.canine.dev/dimensions/X24"},{"id":"X25","name":"Inert configuration knob","shortDescription":{"text":"Inert configuration knob"},"helpUri":"https://codehealth.canine.dev/dimensions/X25"},{"id":"X26","name":"Unsynchronised callback handoff","shortDescription":{"text":"Unsynchronised callback handoff"},"helpUri":"https://codehealth.canine.dev/dimensions/X26"},{"id":"X27","name":"Collection changed while being enumerated","shortDescription":{"text":"Collection changed while being enumerated"},"helpUri":"https://codehealth.canine.dev/dimensions/X27"},{"id":"X28","name":"Index access outside its own emptiness guard","shortDescription":{"text":"Index access outside its own emptiness guard"},"helpUri":"https://codehealth.canine.dev/dimensions/X28"},{"id":"X3","name":"Exception handling","shortDescription":{"text":"Exception handling"},"helpUri":"https://codehealth.canine.dev/dimensions/X3"},{"id":"X30","name":"Support guard that admits what it rejects","shortDescription":{"text":"Support guard that admits what it rejects"},"helpUri":"https://codehealth.canine.dev/dimensions/X30"},{"id":"X32","name":"Type resolved by simple name across every loaded assembly","shortDescription":{"text":"Type resolved by simple name across every loaded assembly"},"helpUri":"https://codehealth.canine.dev/dimensions/X32"},{"id":"X4","name":"Structured logging","shortDescription":{"text":"Structured logging"},"helpUri":"https://codehealth.canine.dev/dimensions/X4"},{"id":"X6","name":"Hand-rolled structured-format parsing","shortDescription":{"text":"Hand-rolled structured-format parsing"},"helpUri":"https://codehealth.canine.dev/dimensions/X6"},{"id":"X7","name":"Silent fallback defaults","shortDescription":{"text":"Silent fallback defaults"},"helpUri":"https://codehealth.canine.dev/dimensions/X7"},{"id":"X9","name":"Subsumed condition operand","shortDescription":{"text":"Subsumed condition operand"},"helpUri":"https://codehealth.canine.dev/dimensions/X9"}]}},"results":[{"ruleId":"D1","level":"warning","message":{"text":"combined_extraction.extract_nodes_and_edges (cyclomatic 48): combined_extraction.extract_nodes_and_edges has cyclomatic complexity 48 (threshold 15). To reduce it, separate the cases: extract each independent branch into its own named function, and where the body has guards that only reject input, fold those into early returns at the top."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/combined_extraction.py"},"region":{"startLine":58}}}],"partialFingerprints":{"codehealthFindingId/v1":"b1728dc5042d33b5252dcfa4915f951b71e71ea4c01c1bedac1a623e2bc9e532"}},{"ruleId":"D1","level":"warning","message":{"text":"edge_operations.extract_edges (cyclomatic 38): edge_operations.extract_edges has cyclomatic complexity 38 (threshold 15). To reduce it, separate the cases: extract each independent branch into its own named function, and where the body has guards that only reject input, fold those into early returns at the top."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/edge_operations.py"},"region":{"startLine":134}}}],"partialFingerprints":{"codehealthFindingId/v1":"7456f26fbd3e3d22fa321c13eb8289c010f7973caab317575a9220d1670bcf24"}},{"ruleId":"D1","level":"warning","message":{"text":"search_filters.edge_search_filter_query_constructor (cyclomatic 35): search_filters.edge_search_filter_query_constructor has cyclomatic complexity 35 (threshold 15). To reduce it, separate the cases: extract each independent branch into its own named function, and where the body has guards that only reject input, fold those into early returns at the top."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/search/search_filters.py"},"region":{"startLine":126}}}],"partialFingerprints":{"codehealthFindingId/v1":"ae84af67d32bad17d1755ce755ea32946068a3393a35b7f2dec482a431600f1d"}},{"ruleId":"D1","level":"warning","message":{"text":"edge_operations._dedupe_extracted_edge (cyclomatic 35): edge_operations._dedupe_extracted_edge has cyclomatic complexity 35 (threshold 15). To reduce it, separate the cases: extract each independent branch into its own named function, and where the body has guards that only reject input, fold those into early returns at the top."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/edge_operations.py"},"region":{"startLine":864}}}],"partialFingerprints":{"codehealthFindingId/v1":"482dae18f9378169e4b5952e3796adfbf70eb054dbbd7f1762df9251a282d203"}},{"ruleId":"D1","level":"warning","message":{"text":"apply._validate_decision (cyclomatic 26): apply._validate_decision has cyclomatic complexity 26 (threshold 15). To reduce it, split the body: these branches sit side by side rather than nested inside one another, so extracting each one on its own would leave a function per branch. Group the statements between the checks into named steps and move each step into its own function, so the body reads as a short sequence of named stages."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":".github/intake/apply.py"},"region":{"startLine":156}}}],"partialFingerprints":{"codehealthFindingId/v1":"5ef3e9bba67f6286b39d8585aff33bb15208b230ee375cb210b7c3235937c005"}},{"ruleId":"D1","level":"warning","message":{"text":"OpenAIDecisionsClient._map_answers (cyclomatic 26): OpenAIDecisionsClient._map_answers has cyclomatic complexity 26 (threshold 15). To reduce it, separate the cases: extract each independent branch into its own named function, and where the body has guards that only reject input, fold those into early returns at the top."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/openai_decisions_client.py"},"region":{"startLine":301}}}],"partialFingerprints":{"codehealthFindingId/v1":"58b9940a2cc9d704cf63930981263c5dcfa7f40822f3f94e21342cdeb31be756"}},{"ruleId":"D1","level":"warning","message":{"text":"edge_operations.resolve_extracted_edges (cyclomatic 26): edge_operations.resolve_extracted_edges has cyclomatic complexity 26 (threshold 15). To reduce it, separate the cases: extract each independent branch into its own named function, and where the body has guards that only reject input, fold those into early returns at the top."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/edge_operations.py"},"region":{"startLine":424}}}],"partialFingerprints":{"codehealthFindingId/v1":"e9a32cb3b5dbd652ea687c0c72aaea49dc432cce13eefe7a469b0441e8f6305b"}},{"ruleId":"D1","level":"warning","message":{"text":"GraphitiConfig.apply_cli_overrides (cyclomatic 23): GraphitiConfig.apply_cli_overrides has cyclomatic complexity 23 (threshold 15). To reduce it, name the conditions: bind each compound test to a well-named local or a small predicate function, so the body reads as a sequence of named decisions rather than a chain of operators."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/config/schema.py"},"region":{"startLine":308}}}],"partialFingerprints":{"codehealthFindingId/v1":"a08dbc48301dc0a1e817db107e9991bf6aa40185a7a2f70fb5a448956d895f32"}},{"ruleId":"D1","level":"warning","message":{"text":"bulk_utils.add_nodes_and_edges_bulk_tx (cyclomatic 22): bulk_utils.add_nodes_and_edges_bulk_tx has cyclomatic complexity 22 (threshold 15). To reduce it, separate the cases: extract each independent branch into its own named function, and where the body has guards that only reject input, fold those into early returns at the top."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/bulk_utils.py"},"region":{"startLine":151}}}],"partialFingerprints":{"codehealthFindingId/v1":"3cb498de9ec19e838785db7ed45d029055c94c98bdd0e54f5d7c858cea486b7c"}},{"ruleId":"D1","level":"warning","message":{"text":"Graphiti.summarize_saga (cyclomatic 21): Graphiti.summarize_saga has cyclomatic complexity 21 (threshold 15). To reduce it, separate the cases: extract each independent branch into its own named function, and where the body has guards that only reject input, fold those into early returns at the top."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/graphiti.py"},"region":{"startLine":590}}}],"partialFingerprints":{"codehealthFindingId/v1":"069479aa48c00f807cc7338b95183bf9a51df8ce6cb4c2d121b9c8c5623a878e"}},{"ruleId":"D1","level":"warning","message":{"text":"OpenAIDecisionsClient._post (cyclomatic 21): OpenAIDecisionsClient._post has cyclomatic complexity 21 (threshold 15). To reduce it, separate the cases: extract each independent branch into its own named function, and where the body has guards that only reject input, fold those into early returns at the top."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/openai_decisions_client.py"},"region":{"startLine":191}}}],"partialFingerprints":{"codehealthFindingId/v1":"d04a6c32f84e50413f02676fad015ad875861e944930a055f6703e56e25a1adb"}},{"ruleId":"D1","level":"warning","message":{"text":"search.edge_search (cyclomatic 21): search.edge_search has cyclomatic complexity 21 (threshold 15). To reduce it, split the body: these branches sit side by side rather than nested inside one another, so extracting each one on its own would leave a function per branch. Group the statements between the checks into named steps and move each step into its own function, so the body reads as a short sequence of named stages."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/search/search.py"},"region":{"startLine":259}}}],"partialFingerprints":{"codehealthFindingId/v1":"11532b9004240e6bf381a48ae548c0a10f058fdaf7e4494a8bde678a51276c47"}},{"ruleId":"D1","level":"warning","message":{"text":"bulk_utils.dedupe_edges_bulk (cyclomatic 21): bulk_utils.dedupe_edges_bulk has cyclomatic complexity 21 (threshold 15). To reduce it, separate the cases: extract each independent branch into its own named function, and where the body has guards that only reject input, fold those into early returns at the top."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/bulk_utils.py"},"region":{"startLine":504}}}],"partialFingerprints":{"codehealthFindingId/v1":"57c87e337e10f4a35c4d0a40069ab046d046ceea05b4946b9b4bcf99a5efad54"}},{"ruleId":"D1","level":"warning","message":{"text":"LLMClientFactory.create (cyclomatic 21): LLMClientFactory.create has cyclomatic complexity 21 (threshold 15). To reduce it, separate the cases: extract each independent branch into its own named function, and where the body has guards that only reject input, fold those into early returns at the top."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/services/factories.py"},"region":{"startLine":135}}}],"partialFingerprints":{"codehealthFindingId/v1":"2016f1c403d4c62e604233bbffffc5baca9413ef0de640b88b8f5012d50e91c4"}},{"ruleId":"D1","level":"warning","message":{"text":"EmbedderFactory.create (cyclomatic 20): EmbedderFactory.create has cyclomatic complexity 20 (threshold 15). To reduce it, separate the cases: extract each independent branch into its own named function, and where the body has guards that only reject input, fold those into early returns at the top."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/services/factories.py"},"region":{"startLine":311}}}],"partialFingerprints":{"codehealthFindingId/v1":"7ce335e7ca2274d7234ce43e12033132620d0b6541d00deecc07979207487a1b"}},{"ruleId":"D1","level":"warning","message":{"text":"decide.fetch_intake_item (cyclomatic 19): decide.fetch_intake_item has cyclomatic complexity 19 (threshold 15). To reduce it, separate the cases: extract each independent branch into its own named function, and where the body has guards that only reject input, fold those into early returns at the top."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":".github/intake/decide.py"},"region":{"startLine":161}}}],"partialFingerprints":{"codehealthFindingId/v1":"830a81cd63c29b583eb215e2039b71875052f6408ba1b10bc4a656216b6c7761"}},{"ruleId":"D1","level":"warning","message":{"text":"Graphiti.add_episode_bulk (cyclomatic 19): Graphiti.add_episode_bulk has cyclomatic complexity 19 (threshold 15). To reduce it, separate the cases: extract each independent branch into its own named function, and where the body has guards that only reject input, fold those into early returns at the top."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/graphiti.py"},"region":{"startLine":1588}}}],"partialFingerprints":{"codehealthFindingId/v1":"471298920ff299d85dd3a0a3d0d8473e31a837feaaaaaf8e7318ca2e73aa5a79"}},{"ruleId":"D1","level":"warning","message":{"text":"GeminiClient._generate_response (cyclomatic 19): GeminiClient._generate_response has cyclomatic complexity 19 (threshold 15). To reduce it, separate the cases: extract each independent branch into its own named function, and where the body has guards that only reject input, fold those into early returns at the top."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/gemini_client.py"},"region":{"startLine":243}}}],"partialFingerprints":{"codehealthFindingId/v1":"1bd7a7ef5a7bda618674f6924240c91114faabb6fd2ad6c2fad49aee5bfec8ba"}},{"ruleId":"D1","level":"warning","message":{"text":"node_operations._resolve_with_llm (cyclomatic 19): node_operations._resolve_with_llm has cyclomatic complexity 19 (threshold 15). To reduce it, split the body: these branches sit side by side rather than nested inside one another, so extracting each one on its own would leave a function per branch. Group the statements between the checks into named steps and move each step into its own function, so the body reads as a short sequence of named stages."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/node_operations.py"},"region":{"startLine":649}}}],"partialFingerprints":{"codehealthFindingId/v1":"9ddd84ea9227deff4eedab1c1fd1d11dbaf3b6a279051c1874a79b814a80cb5f"}},{"ruleId":"D1","level":"warning","message":{"text":"Graphiti.add_triplet (cyclomatic 18): Graphiti.add_triplet has cyclomatic complexity 18 (threshold 15). To reduce it, split the body: these branches sit side by side rather than nested inside one another, so extracting each one on its own would leave a function per branch. Group the statements between the checks into named steps and move each step into its own function, so the body reads as a short sequence of named stages."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/graphiti.py"},"region":{"startLine":2033}}}],"partialFingerprints":{"codehealthFindingId/v1":"77c15f81384115d42d2be91c8975fc8a0c5cb2f95537c07c3115ff1ff07e105e"}},{"ruleId":"D1","level":"warning","message":{"text":"search.node_search (cyclomatic 18): search.node_search has cyclomatic complexity 18 (threshold 15). To reduce it, split the body: these branches sit side by side rather than nested inside one another, so extracting each one on its own would leave a function per branch. Group the statements between the checks into named steps and move each step into its own function, so the body reads as a short sequence of named stages."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/search/search.py"},"region":{"startLine":473}}}],"partialFingerprints":{"codehealthFindingId/v1":"6a9c2d089961639bf9b051eef73f9e1afccf5725f74780336b900f497c21cbfe"}},{"ruleId":"D1","level":"warning","message":{"text":"content_chunking.generate_covering_chunks (cyclomatic 17): content_chunking.generate_covering_chunks has cyclomatic complexity 17 (threshold 15). To reduce it, separate the cases: extract each independent branch into its own named function, and where the body has guards that only reject input, fold those into early returns at the top."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/content_chunking.py"},"region":{"startLine":719}}}],"partialFingerprints":{"codehealthFindingId/v1":"57b41917a19b2cd82885f26918b7e2b1adcd13c462bbf8095ecb6fc1894516a1"}},{"ruleId":"D1","level":"warning","message":{"text":"decide.decide_pull_request (cyclomatic 16): decide.decide_pull_request has cyclomatic complexity 16 (threshold 15). To reduce it, split the body: these branches sit side by side rather than nested inside one another, so extracting each one on its own would leave a function per branch. Group the statements between the checks into named steps and move each step into its own function, so the body reads as a short sequence of named stages."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":".github/intake/decide.py"},"region":{"startLine":383}}}],"partialFingerprints":{"codehealthFindingId/v1":"ae835bf0b24eea6c067b6590d8b3817573e7aea66f7cf409444a4771ce4051d8"}},{"ruleId":"D1","level":"warning","message":{"text":"OpenAIDecisionsClient.generate_response (cyclomatic 16): OpenAIDecisionsClient.generate_response has cyclomatic complexity 16 (threshold 15). To reduce it, split the body: these branches sit side by side rather than nested inside one another, so extracting each one on its own would leave a function per branch. Group the statements between the checks into named steps and move each step into its own function, so the body reads as a short sequence of named stages."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/openai_decisions_client.py"},"region":{"startLine":108}}}],"partialFingerprints":{"codehealthFindingId/v1":"ce54cfbb2d984e3890ae3cde2a6cd23396a714c40005bb5e6ae62951e8c52645"}},{"ruleId":"D2","level":"warning","message":{"text":"search_filters.edge_search_filter_query_constructor (cognitive 92): search_filters.edge_search_filter_query_constructor has cognitive complexity 92 (threshold 15). Drivers by points: if/else 27 (60 pts), loops 12 (32 pts) (nesting depth added 53). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/search/search_filters.py"},"region":{"startLine":126}}}],"partialFingerprints":{"codehealthFindingId/v1":"e39034fb61123be87ec2534c6c21cd292c18183785ac811b52108557c513d5fc"}},{"ruleId":"D2","level":"warning","message":{"text":"combined_extraction.extract_nodes_and_edges (cognitive 80): combined_extraction.extract_nodes_and_edges has cognitive complexity 80 (threshold 15). Drivers by points: if/else 25 (49 pts), loops 9 (16 pts), boolean chains 9, ternaries 5 (6 pts) (nesting depth added 32). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/combined_extraction.py"},"region":{"startLine":58}}}],"partialFingerprints":{"codehealthFindingId/v1":"6727b54a84ba2141933b4e65e8c5b7e5ce108e033c1f8d4d6817207bb78118bb"}},{"ruleId":"D2","level":"warning","message":{"text":"edge_operations.extract_edges (cognitive 57): edge_operations.extract_edges has cognitive complexity 57 (threshold 15). Drivers by points: if/else 15 (28 pts), boolean chains 10, loops 5 (7 pts), error handling 2 (6 pts), ternaries 5 (6 pts) (nesting depth added 20). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/edge_operations.py"},"region":{"startLine":134}}}],"partialFingerprints":{"codehealthFindingId/v1":"0fa5097889624a37c219aa0be7ed10f0034b76673d6e66a743a6f9ddfd6d1572"}},{"ruleId":"D2","level":"warning","message":{"text":"OpenAIDecisionsClient._map_answers (cognitive 50): OpenAIDecisionsClient._map_answers has cognitive complexity 50 (threshold 15). Drivers by points: if/else 16 (37 pts), boolean chains 7, loops 2 (4 pts), ternaries 1 (2 pts) (nesting depth added 24). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/openai_decisions_client.py"},"region":{"startLine":301}}}],"partialFingerprints":{"codehealthFindingId/v1":"34bdb90618e90536010163617da769d24f93aec8f9e81f39f463ace5d154a594"}},{"ruleId":"D2","level":"warning","message":{"text":"bulk_utils.dedupe_edges_bulk (cognitive 47): bulk_utils.dedupe_edges_bulk has cognitive complexity 47 (threshold 15). Drivers by points: if/else 6 (24 pts), loops 10 (19 pts), boolean chains 3, ternaries 1 (nesting depth added 27). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/bulk_utils.py"},"region":{"startLine":504}}}],"partialFingerprints":{"codehealthFindingId/v1":"e3be739b12adc5281b0e5f4e7147ee023876f8fdf99b18d53a60658a3a3e7e59"}},{"ruleId":"D2","level":"warning","message":{"text":"bulk_utils.add_nodes_and_edges_bulk_tx (cognitive 45): bulk_utils.add_nodes_and_edges_bulk_tx has cognitive complexity 45 (threshold 15). Drivers by points: if/else 10 (20 pts), loops 9 (17 pts), ternaries 2 (6 pts), boolean chains 2 (nesting depth added 22). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/bulk_utils.py"},"region":{"startLine":151}}}],"partialFingerprints":{"codehealthFindingId/v1":"896cc18336d712ce03e26793e92913eb5f750c58e75fcc2ce6873084b8beaccc"}},{"ruleId":"D2","level":"warning","message":{"text":"edge_operations._dedupe_extracted_edge (cognitive 45): edge_operations._dedupe_extracted_edge has cognitive complexity 45 (threshold 15). Drivers by points: if/else 14 (20 pts), ternaries 7 (13 pts), boolean chains 8, loops 3 (4 pts) (nesting depth added 13). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/edge_operations.py"},"region":{"startLine":864}}}],"partialFingerprints":{"codehealthFindingId/v1":"af8b3efc2bf822f8b13e36b40c5e1e8fe437b2b0824f0f792382d071dfb77675"}},{"ruleId":"D2","level":"warning","message":{"text":"content_chunking.generate_covering_chunks (cognitive 44): content_chunking.generate_covering_chunks has cognitive complexity 44 (threshold 15). Drivers by points: if/else 10 (30 pts), loops 6 (13 pts), boolean chains 1 (nesting depth added 27). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/content_chunking.py"},"region":{"startLine":719}}}],"partialFingerprints":{"codehealthFindingId/v1":"e25bdbafb0003576ff41b5d8423fb25da7f99a267113e66fc59b9a650996d8f7"}},{"ruleId":"D2","level":"warning","message":{"text":"edge_operations.resolve_extracted_edges (cognitive 43): edge_operations.resolve_extracted_edges has cognitive complexity 43 (threshold 15). Drivers by points: if/else 11 (22 pts), loops 11 (18 pts), boolean chains 2, ternaries 1 (nesting depth added 18). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/edge_operations.py"},"region":{"startLine":424}}}],"partialFingerprints":{"codehealthFindingId/v1":"c7bb5ebc2b7b930d24d6ec7787ed4ee1a3bb9ae56786972c1bcd4d5bc93d4946"}},{"ruleId":"D2","level":"warning","message":{"text":"bulk_utils.dedupe_nodes_bulk (cognitive 33): bulk_utils.dedupe_nodes_bulk has cognitive complexity 33 (threshold 15). Drivers by points: if/else 8 (25 pts), loops 6 (8 pts) (nesting depth added 19). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/bulk_utils.py"},"region":{"startLine":388}}}],"partialFingerprints":{"codehealthFindingId/v1":"799a002c0c6a015a62f229ec0ba8363c6053ac4e58c198d3f841fdb508e62a4d"}},{"ruleId":"D2","level":"warning","message":{"text":"LLMClientFactory.create (cognitive 33): LLMClientFactory.create has cognitive complexity 33 (threshold 15). Drivers by points: if/else 15 (30 pts), ternaries 1 (2 pts), match/switch 1 (nesting depth added 16). The drivers above price the dispatch low by construction \u2014 a dispatch is charged once however many cases it lists, while each branch inside an arm is charged in full \u2014 so most of this count is what the case bodies hold, and the arms are where it can be reduced. To reduce it, keep the dispatch but shrink the arms: move each non-trivial case body into its own named function (or onto the value being matched) so the dispatch reads one line per case, and group related cases into a sub-dispatch. Keep every case explicit, and make the behaviour for cases you do not list a deliberate choice rather than an accident."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/services/factories.py"},"region":{"startLine":135}}}],"partialFingerprints":{"codehealthFindingId/v1":"a07ecc635d73d0f722593cd2f1c5d0ec98e1d891b2ca6b7dde743700b29811bc"}},{"ruleId":"D2","level":"warning","message":{"text":"Graphiti.add_episode_bulk (cognitive 31): Graphiti.add_episode_bulk has cognitive complexity 31 (threshold 15). Drivers by points: if/else 8 (17 pts), ternaries 3 (5 pts), boolean chains 4, loops 3 (4 pts), error handling 1 (nesting depth added 12). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/graphiti.py"},"region":{"startLine":1588}}}],"partialFingerprints":{"codehealthFindingId/v1":"a4dfba645e296602ebb1c089776daa6a9366f3c0f701dba5f3e2739eaaf9c69b"}},{"ruleId":"D2","level":"warning","message":{"text":"OpenAIDecisionsClient._post (cognitive 31): OpenAIDecisionsClient._post has cognitive complexity 31 (threshold 15). Drivers by points: if/else 10 (18 pts), error handling 3 (6 pts), boolean chains 5, loops 2 (nesting depth added 11). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/openai_decisions_client.py"},"region":{"startLine":191}}}],"partialFingerprints":{"codehealthFindingId/v1":"e000f2c3815a2c5f34607ebb49563e55d7be893af6088e5a5a44965eb0eb2164"}},{"ruleId":"D2","level":"warning","message":{"text":"GeminiEmbedder.create_batch (cognitive 30): GeminiEmbedder.create_batch has cognitive complexity 30 (threshold 15). Drivers by points: if/else 5 (14 pts), error handling 2 (6 pts), loops 3 (6 pts), boolean chains 4 (nesting depth added 16). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/embedder/gemini.py"},"region":{"startLine":113}}}],"partialFingerprints":{"codehealthFindingId/v1":"31ecdadba6e793244f23f4dd9d75190a5a335a7598b14c6049ade80692a8b9f4"}},{"ruleId":"D2","level":"warning","message":{"text":"Graphiti.summarize_saga (cognitive 30): Graphiti.summarize_saga has cognitive complexity 30 (threshold 15). Drivers by points: if/else 14 (20 pts), ternaries 2 (4 pts), boolean chains 3, loops 2 (3 pts) (nesting depth added 9). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/graphiti.py"},"region":{"startLine":590}}}],"partialFingerprints":{"codehealthFindingId/v1":"e240659fc9fc52812bd372165f76680a45134800ad8fccc2a2141f823eaff607"}},{"ruleId":"D2","level":"warning","message":{"text":"NeptuneDriver._sanitize_parameters (cognitive 28): NeptuneDriver._sanitize_parameters has cognitive complexity 28 (threshold 15). Drivers by points: if/else 6 (19 pts), loops 3 (8 pts), boolean chains 1 (nesting depth added 18). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neptune_driver.py"},"region":{"startLine":244}}}],"partialFingerprints":{"codehealthFindingId/v1":"fe7499699da1758b83da072e337fe1ed658aa006a47f19e2e2216f676500a8f7"}},{"ruleId":"D2","level":"warning","message":{"text":"lib.validate_prompt_library (cognitive 28): lib.validate_prompt_library has cognitive complexity 28 (threshold 15). Drivers by points: if/else 9 (23 pts), loops 3 (5 pts) (nesting depth added 16). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/prompts/lib.py"},"region":{"startLine":575}}}],"partialFingerprints":{"codehealthFindingId/v1":"d23926f3c611647c8d687e52a9c1ba0422710520e1440e08baed3987efbba682"}},{"ruleId":"D2","level":"warning","message":{"text":"decision_prompts.parse_list (cognitive 26): decision_prompts.parse_list has cognitive complexity 26 (threshold 15). Drivers by points: if/else 4 (11 pts), loops 4 (9 pts), error handling 3 (5 pts), boolean chains 1 (nesting depth added 14). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/decision_prompts.py"},"region":{"startLine":75}}}],"partialFingerprints":{"codehealthFindingId/v1":"a53c3f6abe043e5c3da2b24a5333618f0dd39577f93a06bf52fc143f6b63f728"}},{"ruleId":"D2","level":"warning","message":{"text":"GeminiClient._generate_response (cognitive 26): GeminiClient._generate_response has cognitive complexity 26 (threshold 15). Drivers by points: if/else 8 (15 pts), boolean chains 5, error handling 2 (3 pts), ternaries 2, loops 1 (nesting depth added 8). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/gemini_client.py"},"region":{"startLine":243}}}],"partialFingerprints":{"codehealthFindingId/v1":"53d513b676b8a6508cfdc5e6be5886d516dfff69a1b267196265345530dd40e0"}},{"ruleId":"D2","level":"warning","message":{"text":"search_utils.edge_similarity_search (cognitive 26): search_utils.edge_similarity_search has cognitive complexity 26 (threshold 15). Drivers by points: if/else 13 (23 pts), loops 1 (3 pts) (nesting depth added 12). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/search/search_utils.py"},"region":{"startLine":300}}}],"partialFingerprints":{"codehealthFindingId/v1":"db0f7f1eac73279481257983a44de5554eefad4a778cefb910f02d2a3d886b09"}},{"ruleId":"D2","level":"warning","message":{"text":"EmbedderFactory.create (cognitive 26): EmbedderFactory.create has cognitive complexity 26 (threshold 15). Drivers by points: if/else 10 (20 pts), boolean chains 5, match/switch 1 (nesting depth added 10). The drivers above price the dispatch low by construction \u2014 a dispatch is charged once however many cases it lists, while each branch inside an arm is charged in full \u2014 so most of this count is what the case bodies hold, and the arms are where it can be reduced. To reduce it, keep the dispatch but shrink the arms: move each non-trivial case body into its own named function (or onto the value being matched) so the dispatch reads one line per case, and group related cases into a sub-dispatch. Keep every case explicit, and make the behaviour for cases you do not list a deliberate choice rather than an accident."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/services/factories.py"},"region":{"startLine":311}}}],"partialFingerprints":{"codehealthFindingId/v1":"2627262116099b0bcdcc8e1b5a5dd837b7fad12a682599947e8857dca3e89741"}},{"ruleId":"D2","level":"warning","message":{"text":"apply._validate_decision (cognitive 25): apply._validate_decision has cognitive complexity 25 (threshold 15). Drivers by points: if/else 15, boolean chains 10. To reduce it, split the body: this score is breadth rather than depth \u2014 many checks laid out side by side rather than nested inside one another, so inverting conditions into early returns has nothing left to flatten. Group the statements between the checks into named steps and move each step into its own function, so the body reads as a short sequence of named stages."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":".github/intake/apply.py"},"region":{"startLine":156}}}],"partialFingerprints":{"codehealthFindingId/v1":"5d4605107258eedc0fed9ccb8bd6de27f731498a25cd0c26611590c31417a067"}},{"ruleId":"D2","level":"warning","message":{"text":"decide.fetch_intake_item (cognitive 24): decide.fetch_intake_item has cognitive complexity 24 (threshold 15). Drivers by points: if/else 7 (12 pts), boolean chains 8, loops 1 (2 pts), ternaries 2 (nesting depth added 6). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":".github/intake/decide.py"},"region":{"startLine":161}}}],"partialFingerprints":{"codehealthFindingId/v1":"f6cf98b73b69876332d1dac5984b7fbb1c7dd6449a05b000bf41cd66562dfb0f"}},{"ruleId":"D2","level":"warning","message":{"text":"Graphiti.__init__ (cognitive 23): Graphiti.__init__ has cognitive complexity 23 (threshold 15). Drivers by points: if/else 18 (23 pts) (nesting depth added 5). To reduce it, split the body: most of this score is breadth rather than depth \u2014 checks laid out side by side rather than stacked \u2014 so group the statements between the checks into named steps and move each step into its own function. Some of it IS depth: where a check sits inside another whose only job is to reach it, merge the two into one condition, and where an else follows a branch that already returns, drop the trailing else and let the rest of the body continue at one level."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/graphiti.py"},"region":{"startLine":210}}}],"partialFingerprints":{"codehealthFindingId/v1":"ed5a6185fcb21fc2b168187b84042736a46cc1326d844e83eddb186235d7265e"}},{"ruleId":"D2","level":"warning","message":{"text":"prompt_config.flatten_routes (cognitive 23): prompt_config.flatten_routes has cognitive complexity 23 (threshold 15). Drivers by points: if/else 9 (20 pts), loops 2 (3 pts) (nesting depth added 12). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/prompt_config.py"},"region":{"startLine":283}}}],"partialFingerprints":{"codehealthFindingId/v1":"acfa0b4c2583fff84569f30b335a39e0952a8670ef32aed204faf5766528fe96"}},{"ruleId":"D2","level":"warning","message":{"text":"search.edge_search (cognitive 23): search.edge_search has cognitive complexity 23 (threshold 15). Drivers by points: if/else 12 (16 pts), loops 2 (4 pts), boolean chains 3 (nesting depth added 6). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/search/search.py"},"region":{"startLine":259}}}],"partialFingerprints":{"codehealthFindingId/v1":"195d076a25e4c5e1e0231a02c717189f4ce8de84ee936f4add084b6dacb2f38b"}},{"ruleId":"D2","level":"warning","message":{"text":"search_utils.community_similarity_search (cognitive 23): search_utils.community_similarity_search has cognitive complexity 23 (threshold 15). Drivers by points: if/else 9 (18 pts), loops 1 (3 pts), error handling 1 (2 pts) (nesting depth added 12). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/search/search_utils.py"},"region":{"startLine":1067}}}],"partialFingerprints":{"codehealthFindingId/v1":"5757ecaeea17184025b77996cca6db2c7aa0383a604e25859b208556133321b8"}},{"ruleId":"D2","level":"warning","message":{"text":"dedup_helpers._resolve_with_similarity (cognitive 23): dedup_helpers._resolve_with_similarity has cognitive complexity 23 (threshold 15). Drivers by points: if/else 7 (17 pts), loops 3 (5 pts), boolean chains 1 (nesting depth added 12). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/dedup_helpers.py"},"region":{"startLine":321}}}],"partialFingerprints":{"codehealthFindingId/v1":"83248e415a85087d244ba81eee1fb3d41ca02aa89d1a620228b9f16feb3b566f"}},{"ruleId":"D2","level":"warning","message":{"text":"node_operations._resolve_with_llm (cognitive 23): node_operations._resolve_with_llm has cognitive complexity 23 (threshold 15). Drivers by points: if/else 10 (15 pts), ternaries 6, boolean chains 1, loops 1 (nesting depth added 5). To reduce it, split the body: most of this score is breadth rather than depth \u2014 checks laid out side by side rather than stacked \u2014 so group the statements between the checks into named steps and move each step into its own function. Some of it IS depth: where a check sits inside another whose only job is to reach it, merge the two into one condition, and where an else follows a branch that already returns, drop the trailing else and let the rest of the body continue at one level."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/node_operations.py"},"region":{"startLine":649}}}],"partialFingerprints":{"codehealthFindingId/v1":"70deb673bedfb5a6f86aefacd05ca5dd782a254151f0b18bc4bae0d77c571276"}},{"ruleId":"D2","level":"warning","message":{"text":"node_operations._process_summary_flight (cognitive 23): node_operations._process_summary_flight has cognitive complexity 23 (threshold 15). Drivers by points: if/else 9 (13 pts), loops 4 (7 pts), ternaries 3 (nesting depth added 7). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/node_operations.py"},"region":{"startLine":1172}}}],"partialFingerprints":{"codehealthFindingId/v1":"667f2368e815648ebefd62ce79d79e55fdbaf86c097ab1cfaf1929d4e320961a"}},{"ruleId":"D2","level":"warning","message":{"text":"AnthropicClient.generate_response (cognitive 22): AnthropicClient.generate_response has cognitive complexity 22 (threshold 15). Drivers by points: if/else 8 (16 pts), error handling 2 (4 pts), boolean chains 1, loops 1 (nesting depth added 10). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/anthropic_client.py"},"region":{"startLine":348}}}],"partialFingerprints":{"codehealthFindingId/v1":"fcab9e53544eae955a70a2bca645be7643e0210447024ffdb787f6c28bfa3f47"}},{"ruleId":"D2","level":"warning","message":{"text":"node_operations._extract_entity_summaries_batch (cognitive 22): node_operations._extract_entity_summaries_batch has cognitive complexity 22 (threshold 15). Drivers by points: if/else 8 (17 pts), boolean chains 4, loops 1 (nesting depth added 9). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/node_operations.py"},"region":{"startLine":1088}}}],"partialFingerprints":{"codehealthFindingId/v1":"707e5a00180bbbd5391c3fc4c1ad3bb0614b60a504a7a599cd41393bf5a8de27"}},{"ruleId":"D2","level":"warning","message":{"text":"GraphitiConfig.apply_cli_overrides (cognitive 22): GraphitiConfig.apply_cli_overrides has cognitive complexity 22 (threshold 15). Drivers by points: boolean chains 11, if/else 11. To reduce it, name the conditions: bind each compound test to a well-named local or a small predicate function, so the body reads as a sequence of named decisions rather than a chain of operators."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/config/schema.py"},"region":{"startLine":308}}}],"partialFingerprints":{"codehealthFindingId/v1":"fa0fa805b93c09e4a50b2bc04be7fd1c8ea0f431f5d7331310f28358caf2c385"}},{"ruleId":"D2","level":"warning","message":{"text":"AnthropicClient._generate_response (cognitive 21): AnthropicClient._generate_response has cognitive complexity 21 (threshold 15). Drivers by points: if/else 7 (12 pts), boolean chains 4, error handling 3, loops 2 (nesting depth added 5). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/anthropic_client.py"},"region":{"startLine":254}}}],"partialFingerprints":{"codehealthFindingId/v1":"1172b55d54b9ccc019a80fd8c35de751f08b3ae939bb272d5f1483aeb7d1cc10"}},{"ruleId":"D2","level":"warning","message":{"text":"JevClient._post (cognitive 21): JevClient._post has cognitive complexity 21 (threshold 15). Drivers by points: if/else 6 (11 pts), error handling 3 (6 pts), boolean chains 3, loops 1 (nesting depth added 8). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/jev_client.py"},"region":{"startLine":208}}}],"partialFingerprints":{"codehealthFindingId/v1":"6a3277591716f5d3335338aa14671235436c9aceed343625eee7dba4430bd587"}},{"ruleId":"D2","level":"warning","message":{"text":"search_utils.node_similarity_search (cognitive 21): search_utils.node_similarity_search has cognitive complexity 21 (threshold 15). Drivers by points: if/else 10 (18 pts), loops 1 (3 pts) (nesting depth added 10). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/search/search_utils.py"},"region":{"startLine":668}}}],"partialFingerprints":{"codehealthFindingId/v1":"1fcdf75193393a3a8ab8d29d16b8aa3d9f18296c0adfc0899ac971bb135ed804"}},{"ruleId":"D2","level":"warning","message":{"text":"GraphitiService.initialize (cognitive 21): GraphitiService.initialize has cognitive complexity 21 (threshold 15). Drivers by points: if/else 13 (16 pts), error handling 4, boolean chains 1 (nesting depth added 3). To reduce it, split the body: most of this score is breadth rather than depth \u2014 checks laid out side by side rather than stacked \u2014 so group the statements between the checks into named steps and move each step into its own function. Some of it IS depth: where a check sits inside another whose only job is to reach it, merge the two into one condition, and where an else follows a branch that already returns, drop the trailing else and let the rest of the body continue at one level."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":239}}}],"partialFingerprints":{"codehealthFindingId/v1":"62e68a1b4239f3b6a5812a745707d86ee57c24ed866710dcf2a12878188c25ac"}},{"ruleId":"D2","level":"warning","message":{"text":"graph_utils.label_propagation (cognitive 20): graph_utils.label_propagation has cognitive complexity 20 (threshold 15). Drivers by points: if/else 4 (9 pts), loops 4 (7 pts), ternaries 1 (3 pts), boolean chains 1 (nesting depth added 10). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/operations/graph_utils.py"},"region":{"startLine":27}}}],"partialFingerprints":{"codehealthFindingId/v1":"349892885dfb96c06acee34863d100314f9c1799c57b52ce565f12bf482aa5c2"}},{"ruleId":"D2","level":"warning","message":{"text":"Graphiti._resolve_nodes_and_edges_bulk (cognitive 20): Graphiti._resolve_nodes_and_edges_bulk has cognitive complexity 20 (threshold 15). Drivers by points: loops 9 (12 pts), if/else 2 (6 pts), boolean chains 2 (nesting depth added 7). To reduce it, break up the iteration: give each loop body a named function, and split a multi-phase loop into one function per phase so no single body carries the whole pipeline."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/graphiti.py"},"region":{"startLine":1134}}}],"partialFingerprints":{"codehealthFindingId/v1":"2e7d8a567229f2cca0b5a1176b5969a6a6df602a94106ca0ee20391d8b6533fa"}},{"ruleId":"D2","level":"warning","message":{"text":"GeminiClient.generate_response (cognitive 20): GeminiClient.generate_response has cognitive complexity 20 (threshold 15). Drivers by points: ternaries 3 (6 pts), boolean chains 5, error handling 2 (4 pts), if/else 2 (4 pts), loops 1 (nesting depth added 7). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/gemini_client.py"},"region":{"startLine":372}}}],"partialFingerprints":{"codehealthFindingId/v1":"f718a17974e3227fe7b84e20a0c0a4f1bd4eaf632ca367a89e993513e3653264"}},{"ruleId":"D2","level":"warning","message":{"text":"content_chunking.chunk_text_content (cognitive 20): content_chunking.chunk_text_content has cognitive complexity 20 (threshold 15). Drivers by points: if/else 8 (15 pts), boolean chains 3, loops 1, ternaries 1 (nesting depth added 7). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/content_chunking.py"},"region":{"startLine":376}}}],"partialFingerprints":{"codehealthFindingId/v1":"514e25ad017c55433e3cd2074e2f6bc95c703e1ca1e9e49584cdb408e4886215"}},{"ruleId":"D2","level":"warning","message":{"text":"community_operations.label_propagation (cognitive 20): community_operations.label_propagation has cognitive complexity 20 (threshold 15). Drivers by points: if/else 4 (9 pts), loops 4 (7 pts), ternaries 1 (3 pts), boolean chains 1 (nesting depth added 10). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/community_operations.py"},"region":{"startLine":96}}}],"partialFingerprints":{"codehealthFindingId/v1":"a0dc893adb8ab8e29f30ef962ef9f11dd2b063ffc5db531aca62bb36ef0df854"}},{"ruleId":"D2","level":"warning","message":{"text":"GeminiRerankerClient.rank (cognitive 18): GeminiRerankerClient.rank has cognitive complexity 18 (threshold 15). Drivers by points: if/else 6 (10 pts), boolean chains 3, error handling 2 (3 pts), loops 2 (nesting depth added 5). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/cross_encoder/gemini_reranker_client.py"},"region":{"startLine":73}}}],"partialFingerprints":{"codehealthFindingId/v1":"d7f4ca963fa8f78b80c6baadbe11d59dfab6f2254c951c04e56ebb23b5a56048"}},{"ruleId":"D2","level":"warning","message":{"text":"Graphiti.remove_episode (cognitive 18): Graphiti.remove_episode has cognitive complexity 18 (threshold 15). Drivers by points: if/else 6 (13 pts), loops 4 (5 pts) (nesting depth added 8). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/graphiti.py"},"region":{"startLine":2174}}}],"partialFingerprints":{"codehealthFindingId/v1":"9401cee85b4716a82087181315d500ec70074d360f816534d58db398e5da4b70"}},{"ruleId":"D2","level":"warning","message":{"text":"search_utils.get_relevant_edges (cognitive 18): search_utils.get_relevant_edges has cognitive complexity 18 (threshold 15). Drivers by points: if/else 8 (13 pts), ternaries 1 (3 pts), loops 1 (2 pts) (nesting depth added 8). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body. This shape REPEATS in the file: one other method here (search_utils.get_edge_invalidation_candidates) has the same decision points, in the same order, at the same nesting depths \u2014 so this is one pattern written twice rather than two separate problems. Splitting this body alone leaves the other exactly as it is. Where these are variations on one operation, the change that clears both is the shared one: lift the common shape into a single routine the variants call, parameterised by whatever genuinely differs between them, and keep in each method only the part that is not shared."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/search/search_utils.py"},"region":{"startLine":1413}}}],"partialFingerprints":{"codehealthFindingId/v1":"7ee1c667906d9331ad58a35625cf2480d8f0dd71e6e614ed0c0969863d05dd6a"}},{"ruleId":"D2","level":"warning","message":{"text":"search_utils.get_edge_invalidation_candidates (cognitive 18): search_utils.get_edge_invalidation_candidates has cognitive complexity 18 (threshold 15). Drivers by points: if/else 8 (13 pts), ternaries 1 (3 pts), loops 1 (2 pts) (nesting depth added 8). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body. This shape REPEATS in the file: one other method here (search_utils.get_relevant_edges) has the same decision points, in the same order, at the same nesting depths \u2014 so this is one pattern written twice rather than two separate problems. Splitting this body alone leaves the other exactly as it is. Where these are variations on one operation, the change that clears both is the shared one: lift the common shape into a single routine the variants call, parameterised by whatever genuinely differs between them, and keep in each method only the part that is not shared."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/search/search_utils.py"},"region":{"startLine":1598}}}],"partialFingerprints":{"codehealthFindingId/v1":"af84f76bc311ca8f3ce7bfb00c44c2a283cace4e15561eb28a5d04f56958abe5"}},{"ruleId":"D2","level":"warning","message":{"text":"Graphiti.add_triplet (cognitive 17): Graphiti.add_triplet has cognitive complexity 17 (threshold 15). Drivers by points: if/else 11, error handling 3, boolean chains 2, ternaries 1. To reduce it, split the body: this score is breadth rather than depth \u2014 many checks laid out side by side rather than nested inside one another, so inverting conditions into early returns has nothing left to flatten. Group the statements between the checks into named steps and move each step into its own function, so the body reads as a short sequence of named stages."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/graphiti.py"},"region":{"startLine":2033}}}],"partialFingerprints":{"codehealthFindingId/v1":"4f0c9747dd1c1b013cf4a7906e72db4b63c218373341bc338958492fac83b24a"}},{"ruleId":"D2","level":"warning","message":{"text":"node_db_queries.get_entity_node_save_bulk_query (cognitive 17): node_db_queries.get_entity_node_save_bulk_query has cognitive complexity 17 (threshold 15). Drivers by points: loops 4 (8 pts), ternaries 2 (5 pts), if/else 1 (3 pts), match/switch 1 (nesting depth added 9). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/models/nodes/node_db_queries.py"},"region":{"startLine":194}}}],"partialFingerprints":{"codehealthFindingId/v1":"00e57b429016db49b3874df1e73daa2e0ce1953966237c8a2570079b8ee3457d"}},{"ruleId":"D2","level":"warning","message":{"text":"search.node_search (cognitive 17): search.node_search has cognitive complexity 17 (threshold 15). Drivers by points: if/else 11 (15 pts), boolean chains 2 (nesting depth added 4). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/search/search.py"},"region":{"startLine":473}}}],"partialFingerprints":{"codehealthFindingId/v1":"99f258b542c5887dfcd4fba149610a25a4789ad8245cd976d8f4443c1c3d8061"}},{"ruleId":"D2","level":"warning","message":{"text":"search_utils.edge_fulltext_search (cognitive 17): search_utils.edge_fulltext_search has cognitive complexity 17 (threshold 15). Drivers by points: if/else 10 (11 pts), loops 1 (3 pts), ternaries 1 (3 pts) (nesting depth added 5). To reduce it, split the body: most of this score is breadth rather than depth \u2014 checks laid out side by side rather than stacked \u2014 so group the statements between the checks into named steps and move each step into its own function. Some of it IS depth: where a check sits inside another whose only job is to reach it, merge the two into one condition, and where an else follows a branch that already returns, drop the trailing else and let the rest of the body continue at one level."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/search/search_utils.py"},"region":{"startLine":185}}}],"partialFingerprints":{"codehealthFindingId/v1":"9226a64517b598e9a8c2d4ef42740914dd03d261a7c8758559d69f17795e8bb8"}},{"ruleId":"D2","level":"warning","message":{"text":"search_utils.community_fulltext_search (cognitive 17): search_utils.community_fulltext_search has cognitive complexity 17 (threshold 15). Drivers by points: if/else 8 (9 pts), loops 1 (3 pts), ternaries 1 (3 pts), error handling 1 (2 pts) (nesting depth added 6). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/search/search_utils.py"},"region":{"startLine":973}}}],"partialFingerprints":{"codehealthFindingId/v1":"2474a6e771d386c432fc8a6f1277e66888862604b39d9bc6e0561eea63132987"}},{"ruleId":"D2","level":"warning","message":{"text":"apply.apply_to_github (cognitive 16): apply.apply_to_github has cognitive complexity 16 (threshold 15). Drivers by points: if/else 10 (13 pts), loops 2, boolean chains 1 (nesting depth added 3). To reduce it, split the body: most of this score is breadth rather than depth \u2014 checks laid out side by side rather than stacked \u2014 so group the statements between the checks into named steps and move each step into its own function. Some of it IS depth: where a check sits inside another whose only job is to reach it, merge the two into one condition, and where an else follows a branch that already returns, drop the trailing else and let the rest of the body continue at one level."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":".github/intake/apply.py"},"region":{"startLine":429}}}],"partialFingerprints":{"codehealthFindingId/v1":"33111f568e244e01efa1f46f7b957051f03b294883969c24688483245b64195a"}},{"ruleId":"D2","level":"warning","message":{"text":"Graphiti._process_episode_data (cognitive 16): Graphiti._process_episode_data has cognitive complexity 16 (threshold 15). Drivers by points: if/else 7 (12 pts), boolean chains 2, loops 1, ternaries 1 (nesting depth added 5). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/graphiti.py"},"region":{"startLine":984}}}],"partialFingerprints":{"codehealthFindingId/v1":"b28f1f66c81c2b3bf1ea40de4aed8523ad50c9a09fd404741f9f4d4572cb41a4"}},{"ruleId":"D2","level":"warning","message":{"text":"prompt_config.flatten_overrides (cognitive 16): prompt_config.flatten_overrides has cognitive complexity 16 (threshold 15). Drivers by points: if/else 6 (13 pts), loops 2 (3 pts) (nesting depth added 8). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/prompt_config.py"},"region":{"startLine":99}}}],"partialFingerprints":{"codehealthFindingId/v1":"f7944a5a813ba24aae5d45b5a86df5c77ab34652ec9124fdd7fc9d0096925bd4"}},{"ruleId":"D2","level":"warning","message":{"text":"search.search (cognitive 16): search.search has cognitive complexity 16 (threshold 15). Drivers by points: boolean chains 10, if/else 3, ternaries 2 (3 pts) (nesting depth added 1). To reduce it, name the conditions: bind each compound test to a well-named local or a small predicate function, so the body reads as a sequence of named decisions rather than a chain of operators."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/search/search.py"},"region":{"startLine":98}}}],"partialFingerprints":{"codehealthFindingId/v1":"1cd60d338aa7d2d9a5671147bdd781ed48afa4a21ed2a7ff651a233a552c084c"}},{"ruleId":"D2","level":"warning","message":{"text":"search_utils.edge_bfs_search (cognitive 16): search_utils.edge_bfs_search has cognitive complexity 16 (threshold 15). Drivers by points: if/else 9 (11 pts), error handling 1 (2 pts), loops 1 (2 pts), boolean chains 1 (nesting depth added 4). To reduce it, split the body: most of this score is breadth rather than depth \u2014 checks laid out side by side rather than stacked \u2014 so group the statements between the checks into named steps and move each step into its own function. Some of it IS depth: where a check sits inside another whose only job is to reach it, merge the two into one condition, and where an else follows a branch that already returns, drop the trailing else and let the rest of the body continue at one level."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/search/search_utils.py"},"region":{"startLine":448}}}],"partialFingerprints":{"codehealthFindingId/v1":"0c9018d4cea071f46582c935855ea42160276bed4f956bef0b3a0d069b93d4a5"}},{"ruleId":"D2","level":"warning","message":{"text":"search_utils.node_fulltext_search (cognitive 16): search_utils.node_fulltext_search has cognitive complexity 16 (threshold 15). Drivers by points: if/else 9 (10 pts), loops 1 (3 pts), ternaries 1 (3 pts) (nesting depth added 5). To reduce it, split the body: most of this score is breadth rather than depth \u2014 checks laid out side by side rather than stacked \u2014 so group the statements between the checks into named steps and move each step into its own function. Some of it IS depth: where a check sits inside another whose only job is to reach it, merge the two into one condition, and where an else follows a branch that already returns, drop the trailing else and let the rest of the body continue at one level."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/search/search_utils.py"},"region":{"startLine":572}}}],"partialFingerprints":{"codehealthFindingId/v1":"72234cb95fa3cda3a09948903f291ede0d637a9d94490a24ea0a07acaf3e8846"}},{"ruleId":"D2","level":"warning","message":{"text":"content_chunking._chunk_by_sentences (cognitive 16): content_chunking._chunk_by_sentences has cognitive complexity 16 (threshold 15). Drivers by points: if/else 7 (14 pts), boolean chains 1, loops 1 (nesting depth added 7). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/content_chunking.py"},"region":{"startLine":453}}}],"partialFingerprints":{"codehealthFindingId/v1":"789444b640a866b9080965213c0b3771ef603e54edc82e32d4d5b32e0663ad41"}},{"ruleId":"D2","level":"warning","message":{"text":"graph_data_operations.retrieve_episodes (cognitive 16): graph_data_operations.retrieve_episodes has cognitive complexity 16 (threshold 15). Drivers by points: ternaries 5 (9 pts), if/else 4, error handling 1 (2 pts), boolean chains 1 (nesting depth added 5). To reduce it, split the body into named stages: move each independent step or branch into its own named function so the body reads as a short sequence of named calls rather than one long body."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/graph_data_operations.py"},"region":{"startLine":67}}}],"partialFingerprints":{"codehealthFindingId/v1":"0e046d321f525879e5b96709b9807ac64f34b1aa66371ffec36666b2e50837c3"}},{"ruleId":"D3","level":"warning","message":{"text":"FileTooLong: graphiti_core/graphiti.py: FileTooLong \u2014 1274 significant lines (blank, comment-only and punctuation-only lines excluded). The bar is 500 significant lines; this is 774 over it, 2.55\u00D7 the bar. To reduce it, split the file along the responsibilities already in it: move each cohesive group of declarations into its own sibling file in the same module or package, so no one file has to be read whole to change one of them."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/graphiti.py"},"region":{"startLine":1}}}],"partialFingerprints":{"codehealthFindingId/v1":"930379d941e418dfb42d60db12b9e8350215c6ff660a75d47f5ca30d11c66fe6"}},{"ruleId":"D3","level":"warning","message":{"text":"FileTooLong: maintenance/node_operations.py: FileTooLong \u2014 886 significant lines (blank, comment-only and punctuation-only lines excluded). The bar is 500 significant lines; this is 386 over it, 1.77\u00D7 the bar. To reduce it, split the file along the responsibilities already in it: move each cohesive group of declarations into its own sibling file in the same module or package, so no one file has to be read whole to change one of them."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/node_operations.py"},"region":{"startLine":1}}}],"partialFingerprints":{"codehealthFindingId/v1":"ce759f8219ba5ade539836a87a04304954c9d40b77ca73751c7b89f938452b79"}},{"ruleId":"D3","level":"warning","message":{"text":"FileTooLong: src/graphiti_mcp_server.py: FileTooLong \u2014 786 significant lines (blank, comment-only and punctuation-only lines excluded). The bar is 500 significant lines; this is 286 over it, 1.57\u00D7 the bar. To reduce it, split the file along the responsibilities already in it: move each cohesive group of declarations into its own sibling file in the same module or package, so no one file has to be read whole to change one of them."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":1}}}],"partialFingerprints":{"codehealthFindingId/v1":"84b49f0a17bba57b32d142efe2bcf90d8c440bc593e5a733d98d2535d9c4a150"}},{"ruleId":"D3","level":"warning","message":{"text":"FileTooLong: maintenance/edge_operations.py: FileTooLong \u2014 737 significant lines (blank, comment-only and punctuation-only lines excluded). The bar is 500 significant lines; this is 237 over it, 1.47\u00D7 the bar. To reduce it, split the file along the responsibilities already in it: move each cohesive group of declarations into its own sibling file in the same module or package, so no one file has to be read whole to change one of them."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/edge_operations.py"},"region":{"startLine":1}}}],"partialFingerprints":{"codehealthFindingId/v1":"1213c00143682c6f12c2a02893aa0de5984811fa094407241a97dc0ade31d7de"}},{"ruleId":"D3","level":"warning","message":{"text":"FileTooLong: search/search.py: FileTooLong \u2014 689 significant lines (blank, comment-only and punctuation-only lines excluded). The bar is 500 significant lines; this is 189 over it, 1.38\u00D7 the bar. To reduce it, split the file along the responsibilities already in it: move each cohesive group of declarations into its own sibling file in the same module or package, so no one file has to be read whole to change one of them."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/search/search.py"},"region":{"startLine":1}}}],"partialFingerprints":{"codehealthFindingId/v1":"19ae7e0ba560dfae96fd7f26f214249b918d4ff22c2e644b6b246c74bf102f1b"}},{"ruleId":"D3","level":"warning","message":{"text":"FileTooLong: search/search_utils.py: FileTooLong \u2014 590 significant lines (blank, comment-only and punctuation-only lines excluded). The bar is 500 significant lines; this is 90 over it, 1.18\u00D7 the bar. To reduce it, split the file along the responsibilities already in it: move each cohesive group of declarations into its own sibling file in the same module or package, so no one file has to be read whole to change one of them."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/search/search_utils.py"},"region":{"startLine":1}}}],"partialFingerprints":{"codehealthFindingId/v1":"f76c97110197ceb80dcf238477e775bc815e1577fe3ae6172d01dc94322bbb16"}},{"ruleId":"D4","level":"warning","message":{"text":"Near-duplicate member family (4 members, 52 shared lines): graphiti_core/driver/falkordb/operations/graph_ops.py:115-177 | graphiti_core/driver/kuzu/operations/graph_ops.py:86-151 | graphiti_core/driver/neo4j/operations/graph_ops.py:80-146 | graphiti_core/driver/neptune/operations/graph_ops.py:87-151 \u2014 These 4 members are variants of one another: a block of 52 lines reported below appears in every one of them, and the pairwise near-duplicate rows they would otherwise produce are collapsed into this row. Read them as one construct written 4 times. The repair is at the members\u0027 grain \u2014 factor the shared implementation out once and have all of them call it with their differences as parameters or as an injected step, or, where the difference is systematic, generate them from one template. Extracting the individual blocks below is not the same fix: it leaves every body in place and the next edit still has to be made 4 times."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/graph_ops.py"},"region":{"startLine":115}}}],"partialFingerprints":{"codehealthFindingId/v1":"2ed11bfc6a6eb80d72ddc6eba6bc11815d1d21b349cf9857c7e786402f946173"}},{"ruleId":"D4","level":"warning","message":{"text":"Members sharing a duplicated core (4 members, 50\u002B identical tokens): graphiti_core/driver/record_parsers.py:25-50 | graphiti_core/driver/record_parsers.py:54-85 | graphiti_core/edges.py:974-1010 | graphiti_core/nodes.py:1095-1123 \u2014 These 4 members share a duplicated core: a run of at least 50 identical tokens appears in every one of them. That run is NOT broken out as duplicated-block rows below \u2014 it is what admitted this row, and the blocks below cover only the part of it that clears the block floor, so they understate the correspondence. Read the members as one construct written 4 times. The repair is at the members\u0027 grain \u2014 factor the shared implementation out once and have all of them call it with their differences as parameters or as an injected step, or, where the difference is systematic, generate them from one template. Extracting the individual blocks below is not the same fix: it leaves every body in place and the next edit still has to be made 4 times."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/record_parsers.py"},"region":{"startLine":25}}}],"partialFingerprints":{"codehealthFindingId/v1":"829bb78a88f17ff58cc42f4c347fe7a2fa7754795f87e262de79f81eb6f86885"}},{"ruleId":"D4","level":"warning","message":{"text":"Members sharing a duplicated core (4 members, 50\u002B identical tokens): graphiti_core/search/search.py:274-470 | graphiti_core/search/search.py:488-673 | graphiti_core/search/search.py:689-775 | graphiti_core/search/search.py:790-891 \u2014 These 4 members share a duplicated core: a run of at least 50 identical tokens appears in every one of them. That run is NOT broken out as duplicated-block rows below \u2014 it is what admitted this row, and the blocks below cover only the part of it that clears the block floor, so they understate the correspondence. Read the members as one construct written 4 times. The repair is at the members\u0027 grain \u2014 factor the shared implementation out once and have all of them call it with their differences as parameters or as an injected step, or, where the difference is systematic, generate them from one template. Extracting the individual blocks below is not the same fix: it leaves every body in place and the next edit still has to be made 4 times."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/search/search.py"},"region":{"startLine":274}}}],"partialFingerprints":{"codehealthFindingId/v1":"f6fb0fa626360abb4199fdd69239c65d02638c0fbcb3e13cbd33bc6e585bccdc"}},{"ruleId":"D4","level":"warning","message":{"text":"Members sharing a duplicated core (4 members, 50\u002B identical tokens): graphiti_core/search/search_utils.py:192-297 | graphiti_core/search/search_utils.py:579-665 | graphiti_core/search/search_utils.py:889-970 | graphiti_core/search/search_utils.py:979-1064 \u2014 These 4 members share a duplicated core: a run of at least 50 identical tokens appears in every one of them. That run is NOT broken out as duplicated-block rows below \u2014 it is what admitted this row, and the blocks below cover only the part of it that clears the block floor, so they understate the correspondence. Read the members as one construct written 4 times. The repair is at the members\u0027 grain \u2014 factor the shared implementation out once and have all of them call it with their differences as parameters or as an injected step, or, where the difference is systematic, generate them from one template. Extracting the individual blocks below is not the same fix: it leaves every body in place and the next edit still has to be made 4 times."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/search/search_utils.py"},"region":{"startLine":192}}}],"partialFingerprints":{"codehealthFindingId/v1":"15798609da03453ad5191d764c9ae6ce94208606faa25a8c6059a31d248bb006"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (176\u2013178 lines \u00D7 2): graphiti_core/search/search_utils.py:1420-1595 | graphiti_core/search/search_utils.py:1605-1782 \u2014 both copies are in the same file, so extract the block into one function there and call it from each site \u2014 the copies drift apart the first time only one of them is edited. The matched lines also transfer control out of the body holding them, which cannot survive a move into a called unit unchanged: have the extracted unit return that decision and let each site act on it."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/search/search_utils.py"},"region":{"startLine":1420}}}],"partialFingerprints":{"codehealthFindingId/v1":"d5ab9aa7ae82f5ba7be2ce12639d867e0dd7f7691c5296a21defee9d57f00ce6"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (52 lines \u00D7 2): graphiti_core/driver/falkordb/operations/episode_node_ops.py:250-301 | graphiti_core/driver/kuzu/operations/episode_node_ops.py:215-266 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/episode_node_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/episode_node_ops.py\u0060 as WHOLE FILES: this scan already matched 9 separate duplicated blocks between them, totalling at least 197 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. The \u0060return\u0060 at the foot of the matched lines is the enclosing body\u0027s own terminal exit, not an early one: it moves with them unchanged, and each site calls the extracted unit from the position that \u0060return\u0060 occupied \u2014 no decision has to be handed back and re-acted on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/episode_node_ops.py"},"region":{"startLine":250}}}],"partialFingerprints":{"codehealthFindingId/v1":"0e5ba19139c29cec6700c66a6c3a75b56480b6e4909d38f5e89904241d0d8bac"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (45\u201351 lines \u00D7 2): graphiti_core/search/search_utils.py:354-404 | graphiti_core/search/search_utils.py:1090-1134 \u2014 both copies are in the same file, so extract the block into one function there and call it from each site \u2014 the copies drift apart the first time only one of them is edited."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/search/search_utils.py"},"region":{"startLine":354}}}],"partialFingerprints":{"codehealthFindingId/v1":"e50d993e7dacf1c00c09586e8a0af3752cf72f6fa5429ff0695fb5fb33373e38"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (30\u201332 lines \u00D7 2): graphiti_core/driver/falkordb/operations/graph_ops.py:77-108 | graphiti_core/driver/falkordb_driver.py:293-322 \u2014 the copies span different directories, so extracting a shared function means choosing where it lives: put it somewhere both call sites can already reach \u2014 a location they all depend on today, or a new shared one if there is none \u2014 and call it from each site; until then, every change has to be made twice. The matched lines also transfer control out of the body holding them, which cannot survive a move into a called unit unchanged: have the extracted unit return that decision and let each site act on it."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/graph_ops.py"},"region":{"startLine":77}}}],"partialFingerprints":{"codehealthFindingId/v1":"920755713880dbfd1150799a2e621d6ce1bcec1e1030460b3512360e6a91e056"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (32 lines \u00D7 2): graphiti_core/driver/operations/graph_utils.py:28-59 | graphiti_core/utils/maintenance/community_operations.py:103-134 \u2014 the copies span different directories, so extracting a shared function means choosing where it lives: put it somewhere both call sites can already reach \u2014 a location they all depend on today, or a new shared one if there is none \u2014 and call it from each site; until then, every change has to be made twice. The matched lines also transfer control out of the body holding them, which cannot survive a move into a called unit unchanged: have the extracted unit return that decision and let each site act on it."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/operations/graph_utils.py"},"region":{"startLine":28}}}],"partialFingerprints":{"codehealthFindingId/v1":"a86f443a3c4c27da0aa5452ea312c512d542e34da24c72242ed4d5b49d46d90e"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (32 lines \u00D7 2): graphiti_core/llm_client/jev_client.py:156-187 | graphiti_core/llm_client/openai_decisions_client.py:137-168 \u2014 before extracting anything, compare \u0060graphiti_core/llm_client/jev_client.py\u0060 and \u0060graphiti_core/llm_client/openai_decisions_client.py\u0060 as WHOLE FILES: this scan already matched 3 separate duplicated blocks between them, totalling at least 66 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. If that is what happened, the fix is to keep one copy and have the other call it (or delete it), which resolves this row and its siblings together \u2014 extracting one helper per block leaves the fork in place. Read the line range as the matched WINDOW rather than a finished unit: at \u0060graphiti_core/llm_client/jev_client.py:156\u0060 it does not close everything it opens, so those exact lines cannot be lifted as they stand \u2014 widen the region to the smallest complete statement or declaration that contains it, and extract that."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/jev_client.py"},"region":{"startLine":156}}}],"partialFingerprints":{"codehealthFindingId/v1":"1e5092aac7ce9331cc42abe20f709b4f238004baa42593277f9284db2143a7c5"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (29\u201331 lines \u00D7 4): graphiti_core/driver/falkordb/operations/graph_ops.py:143-171 | graphiti_core/driver/kuzu/operations/graph_ops.py:115-145 | graphiti_core/driver/neo4j/operations/graph_ops.py:110-139 | graphiti_core/driver/neptune/operations/graph_ops.py:116-145 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/graph_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/graph_ops.py\u0060 as WHOLE FILES: this scan already matched 4 separate duplicated blocks between them, totalling at least 90 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. Read the line range as the matched WINDOW rather than a finished unit: at \u0060graphiti_core/driver/falkordb/operations/graph_ops.py:143\u0060 it does not close everything it opens, so those exact lines cannot be lifted as they stand \u2014 widen the region to the smallest complete statement or declaration that contains it, and extract that. The matched lines also transfer control out of the body holding them, which cannot survive a move into a called unit unchanged: have the extracted unit return that decision and let each site act on it."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/graph_ops.py"},"region":{"startLine":143}}}],"partialFingerprints":{"codehealthFindingId/v1":"0b122cd1f47a09f8254574d162bda6e8b33e127ca3ad7929b4f1827575a4cc47"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (30 lines \u00D7 2): mcp_server/src/services/factories.py:188-217 | mcp_server/src/services/factories.py:339-368 \u2014 both copies are in the same file, so extract the block into one function there and call it from each site \u2014 the copies drift apart the first time only one of them is edited. Note first that the copies are not typed on the same thing: the declarations holding them bind \u0060config\u0060 to \u0060LLMConfig\u0060 in one and \u0060EmbedderConfig\u0060 in another, and the duplicated lines use it. The extracted unit therefore needs a parameter type that fits BOTH \u2014 their common supertype where they have one, or a new abstraction over them where they do not \u2014 and settling that is the step that comes BEFORE the extraction above. Where the two types are deliberately unrelated, the duplication is the price of that separation and the honest resolution is to record the decision rather than to extract."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/services/factories.py"},"region":{"startLine":188}}}],"partialFingerprints":{"codehealthFindingId/v1":"377eeb863aa15e1ebc0e766cae509157343414aa102f03174b50e4f175d9b38d"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (19\u201329 lines \u00D7 2): graphiti_core/utils/bulk_utils.py:171-189 | graphiti_core/utils/bulk_utils.py:195-223 \u2014 both copies are in the same file, so extract the block into one function there and call it from each site \u2014 the copies drift apart the first time only one of them is edited."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/bulk_utils.py"},"region":{"startLine":171}}}],"partialFingerprints":{"codehealthFindingId/v1":"b0505a38ed9a8100ced03571c74325bfe2737f435917948a859325c99828955c"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (20\u201328 lines \u00D7 3): graphiti_core/search/search_utils.py:624-643 | graphiti_core/search/search_utils.py:920-947 | graphiti_core/search/search_utils.py:1018-1042 \u2014 all 3 copies are in the same file, so extract the block into one function there and call it from every one of those sites \u2014 resolving only two of them leaves the rest to drift apart the first time one is edited. Read the line range as the matched WINDOW rather than a finished unit: at \u0060graphiti_core/search/search_utils.py:624\u0060 it begins part-way through the construct above it, so those exact lines cannot be lifted as they stand \u2014 widen the region to the smallest complete statement or declaration that contains it, and extract that. The matched lines also transfer control out of the body holding them, which cannot survive a move into a called unit unchanged: have the extracted unit return that decision and let each site act on it. Note that the copies do not run to the end of the range shown: their LAST lines are different code, not the same code under different names \u2014 the matched region ends inside that line. Extract the lines above it, and read the last line of each site separately."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/search/search_utils.py"},"region":{"startLine":624}}}],"partialFingerprints":{"codehealthFindingId/v1":"9fece872440c647503bc79ef0689ea6a8c46d2ad8372d19eb7b6231ea7e3f865"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (27\u201328 lines \u00D7 2): graphiti_core/utils/maintenance/edge_operations.py:913-939 | graphiti_core/utils/maintenance/edge_operations.py:1043-1070 \u2014 both copies are in the same file, so extract the block into one function there and call it from each site \u2014 the copies drift apart the first time only one of them is edited."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/edge_operations.py"},"region":{"startLine":913}}}],"partialFingerprints":{"codehealthFindingId/v1":"ae1021ab0ec1ee15cbec5c73a7081df0bdff30472d2cc5971132f832018efbf3"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (26 lines \u00D7 3): graphiti_core/driver/falkordb/operations/episode_node_ops.py:261-286 | graphiti_core/driver/kuzu/operations/episode_node_ops.py:226-251 | graphiti_core/driver/neptune/operations/episode_node_ops.py:235-260 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/episode_node_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/episode_node_ops.py\u0060 as WHOLE FILES: this scan already matched 9 separate duplicated blocks between them, totalling at least 197 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. Read the line range as the matched WINDOW rather than a finished unit: at \u0060graphiti_core/driver/falkordb/operations/episode_node_ops.py:261\u0060 it begins part-way through the construct above it, so those exact lines cannot be lifted as they stand \u2014 widen the region to the smallest complete statement or declaration that contains it, and extract that."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/episode_node_ops.py"},"region":{"startLine":261}}}],"partialFingerprints":{"codehealthFindingId/v1":"9b17b3042951f54885db47b25db83e73560c04263ca67f6e4b98e6543fbe64aa"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (26 lines \u00D7 3): graphiti_core/driver/falkordb/operations/episode_node_ops.py:273-298 | graphiti_core/driver/kuzu/operations/episode_node_ops.py:238-263 | graphiti_core/driver/neo4j/operations/episode_node_ops.py:258-283 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/episode_node_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/episode_node_ops.py\u0060 as WHOLE FILES: this scan already matched 9 separate duplicated blocks between them, totalling at least 197 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. Read the line range as the matched WINDOW rather than a finished unit: at \u0060graphiti_core/driver/falkordb/operations/episode_node_ops.py:273\u0060 it begins part-way through the construct above it, so those exact lines cannot be lifted as they stand \u2014 widen the region to the smallest complete statement or declaration that contains it, and extract that."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/episode_node_ops.py"},"region":{"startLine":273}}}],"partialFingerprints":{"codehealthFindingId/v1":"93095dc42aaca482350a0e5d4ab5214a16bb1b40161a4e5a1e2e3d670a1ca41e"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (26 lines \u00D7 3): graphiti_core/driver/neptune/operations/search_ops.py:136-161 | graphiti_core/driver/neptune/operations/search_ops.py:326-351 | graphiti_core/driver/neptune/operations/search_ops.py:552-577 \u2014 all 3 copies are in the same file, so extract the block into one function there and call it from every one of those sites \u2014 resolving only two of them leaves the rest to drift apart the first time one is edited. The matched lines also transfer control out of the body holding them, which cannot survive a move into a called unit unchanged: have the extracted unit return that decision and let each site act on it."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neptune/operations/search_ops.py"},"region":{"startLine":136}}}],"partialFingerprints":{"codehealthFindingId/v1":"59e6f6b455dd4ae5a43f7bf03e3245f1de9d1893d8d4e4a16c1d48ef664fff01"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (24\u201326 lines \u00D7 2): graphiti_core/nodes.py:521-544 | graphiti_core/nodes.py:772-797 \u2014 both copies are in the same file, so extract the block into one function there and call it from each site \u2014 the copies drift apart the first time only one of them is edited. Read the line range as the matched WINDOW rather than a finished unit: at \u0060graphiti_core/nodes.py:772\u0060 it begins part-way through the construct above it, so those exact lines cannot be lifted as they stand \u2014 widen the region to the smallest complete statement or declaration that contains it, and extract that."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/nodes.py"},"region":{"startLine":521}}}],"partialFingerprints":{"codehealthFindingId/v1":"042c8c2e13ffbf644280fdd560a9e7857d1d53fb66efb362faa7ebf9b07cdbb3"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (23\u201324 lines \u00D7 4): graphiti_core/driver/falkordb/operations/graph_ops.py:115-137 | graphiti_core/driver/kuzu/operations/graph_ops.py:86-109 | graphiti_core/driver/neo4j/operations/graph_ops.py:80-103 | graphiti_core/driver/neptune/operations/graph_ops.py:87-110 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/graph_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/graph_ops.py\u0060 as WHOLE FILES: this scan already matched 4 separate duplicated blocks between them, totalling at least 90 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. Read the line range as the matched WINDOW rather than a finished unit: at \u0060graphiti_core/driver/falkordb/operations/graph_ops.py:115\u0060 it does not close everything it opens, so those exact lines cannot be lifted as they stand \u2014 widen the region to the smallest complete statement or declaration that contains it, and extract that."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/graph_ops.py"},"region":{"startLine":115}}}],"partialFingerprints":{"codehealthFindingId/v1":"3bb91d8c3bad3add319ba8331721d0bcacff79a15ff00846eaf17c250712778e"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (24 lines \u00D7 3): graphiti_core/driver/falkordb/operations/community_edge_ops.py:163-186 | graphiti_core/driver/kuzu/operations/community_edge_ops.py:140-163 | graphiti_core/driver/neptune/operations/community_edge_ops.py:140-163 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/community_edge_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/community_edge_ops.py\u0060 as WHOLE FILES: this scan already matched 6 separate duplicated blocks between them, totalling at least 76 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. The \u0060return\u0060 at the foot of the matched lines is the enclosing body\u0027s own terminal exit, not an early one: it moves with them unchanged, and each site calls the extracted unit from the position that \u0060return\u0060 occupied \u2014 no decision has to be handed back and re-acted on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/community_edge_ops.py"},"region":{"startLine":163}}}],"partialFingerprints":{"codehealthFindingId/v1":"0d9261ee16b65ebca86dabe8dcea28358c71696002e548292f3463e4eecfce52"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (24 lines \u00D7 3): graphiti_core/driver/falkordb/operations/episode_node_ops.py:249-272 | graphiti_core/driver/kuzu/operations/episode_node_ops.py:214-237 | graphiti_core/driver/neo4j/operations/episode_node_ops.py:233-256 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/episode_node_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/episode_node_ops.py\u0060 as WHOLE FILES: this scan already matched 9 separate duplicated blocks between them, totalling at least 197 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. Read the line range as the matched WINDOW rather than a finished unit: at \u0060graphiti_core/driver/falkordb/operations/episode_node_ops.py:249\u0060 it does not close everything it opens, so those exact lines cannot be lifted as they stand \u2014 widen the region to the smallest complete statement or declaration that contains it, and extract that."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/episode_node_ops.py"},"region":{"startLine":249}}}],"partialFingerprints":{"codehealthFindingId/v1":"70c21b29b592ecd1f41dea3de05c8da2896a5ac925387921c61415727b9ef13f"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (24 lines \u00D7 3): graphiti_core/driver/falkordb/operations/episodic_edge_ops.py:177-200 | graphiti_core/driver/kuzu/operations/episodic_edge_ops.py:162-185 | graphiti_core/driver/neptune/operations/episodic_edge_ops.py:154-177 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/episodic_edge_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/episodic_edge_ops.py\u0060 as WHOLE FILES: this scan already matched 7 separate duplicated blocks between them, totalling at least 89 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. The \u0060return\u0060 at the foot of the matched lines is the enclosing body\u0027s own terminal exit, not an early one: it moves with them unchanged, and each site calls the extracted unit from the position that \u0060return\u0060 occupied \u2014 no decision has to be handed back and re-acted on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/episodic_edge_ops.py"},"region":{"startLine":177}}}],"partialFingerprints":{"codehealthFindingId/v1":"4e7bbf4311f9a4d654669d6af0172d0abf770576347322955d4b53d226b650d6"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (24 lines \u00D7 3): graphiti_core/driver/falkordb/operations/has_episode_edge_ops.py:172-195 | graphiti_core/driver/kuzu/operations/has_episode_edge_ops.py:148-171 | graphiti_core/driver/neptune/operations/has_episode_edge_ops.py:148-171 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/has_episode_edge_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/has_episode_edge_ops.py\u0060 as WHOLE FILES: this scan already matched 7 separate duplicated blocks between them, totalling at least 89 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. The \u0060return\u0060 at the foot of the matched lines is the enclosing body\u0027s own terminal exit, not an early one: it moves with them unchanged, and each site calls the extracted unit from the position that \u0060return\u0060 occupied \u2014 no decision has to be handed back and re-acted on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/has_episode_edge_ops.py"},"region":{"startLine":172}}}],"partialFingerprints":{"codehealthFindingId/v1":"8cc25066ae96b37031e8101ab416a63efbbc52da6b7be77a91b21759015c9ff3"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (24 lines \u00D7 3): graphiti_core/driver/falkordb/operations/next_episode_edge_ops.py:172-195 | graphiti_core/driver/kuzu/operations/next_episode_edge_ops.py:148-171 | graphiti_core/driver/neptune/operations/next_episode_edge_ops.py:148-171 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/next_episode_edge_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/next_episode_edge_ops.py\u0060 as WHOLE FILES: this scan already matched 7 separate duplicated blocks between them, totalling at least 89 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. The \u0060return\u0060 at the foot of the matched lines is the enclosing body\u0027s own terminal exit, not an early one: it moves with them unchanged, and each site calls the extracted unit from the position that \u0060return\u0060 occupied \u2014 no decision has to be handed back and re-acted on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/next_episode_edge_ops.py"},"region":{"startLine":172}}}],"partialFingerprints":{"codehealthFindingId/v1":"366ce30fa90485c988bd321396d8f335e8c564deeef8a164eaa532eee26dc036"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (24 lines \u00D7 2): graphiti_core/driver/falkordb/operations/community_node_ops.py:185-208 | graphiti_core/driver/kuzu/operations/community_node_ops.py:161-184 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/community_node_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/community_node_ops.py\u0060 as WHOLE FILES: this scan already matched 6 separate duplicated blocks between them, totalling at least 77 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. The \u0060return\u0060 at the foot of the matched lines is the enclosing body\u0027s own terminal exit, not an early one: it moves with them unchanged, and each site calls the extracted unit from the position that \u0060return\u0060 occupied \u2014 no decision has to be handed back and re-acted on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/community_node_ops.py"},"region":{"startLine":185}}}],"partialFingerprints":{"codehealthFindingId/v1":"03ac64f3944c6011c926c2d3c66024b0ffb91579437b4cf2d9496835b10bb83c"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (24 lines \u00D7 2): graphiti_core/driver/falkordb/operations/episode_node_ops.py:200-223 | graphiti_core/driver/kuzu/operations/episode_node_ops.py:165-188 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/episode_node_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/episode_node_ops.py\u0060 as WHOLE FILES: this scan already matched 9 separate duplicated blocks between them, totalling at least 197 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. The \u0060return\u0060 at the foot of the matched lines is the enclosing body\u0027s own terminal exit, not an early one: it moves with them unchanged, and each site calls the extracted unit from the position that \u0060return\u0060 occupied \u2014 no decision has to be handed back and re-acted on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/episode_node_ops.py"},"region":{"startLine":200}}}],"partialFingerprints":{"codehealthFindingId/v1":"776e4ef9b730a99455830ddc5bfb9e2f343587f72ce1ac3363c51c3fe4755192"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (24 lines \u00D7 2): graphiti_core/driver/falkordb/operations/saga_node_ops.py:185-208 | graphiti_core/driver/kuzu/operations/saga_node_ops.py:165-188 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/saga_node_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/saga_node_ops.py\u0060 as WHOLE FILES: this scan already matched 6 separate duplicated blocks between them, totalling at least 75 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. The \u0060return\u0060 at the foot of the matched lines is the enclosing body\u0027s own terminal exit, not an early one: it moves with them unchanged, and each site calls the extracted unit from the position that \u0060return\u0060 occupied \u2014 no decision has to be handed back and re-acted on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/saga_node_ops.py"},"region":{"startLine":185}}}],"partialFingerprints":{"codehealthFindingId/v1":"5e7fa103e3dc2a02a2db28a5b54f87335e06397ce26a3c0a13b5ff20d1609ed6"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (21\u201323 lines \u00D7 2): graphiti_core/search/search.py:753-775 | graphiti_core/search/search.py:871-891 \u2014 both copies are in the same file, so extract the block into one function there and call it from each site \u2014 the copies drift apart the first time only one of them is edited. Read the line range as the matched WINDOW rather than a finished unit: at \u0060graphiti_core/search/search.py:753\u0060 it begins part-way through the construct above it, so those exact lines cannot be lifted as they stand \u2014 widen the region to the smallest complete statement or declaration that contains it, and extract that. The \u0060return\u0060 at the foot of the matched lines is the enclosing body\u0027s own terminal exit, not an early one: it moves with them unchanged, and each site calls the extracted unit from the position that \u0060return\u0060 occupied \u2014 no decision has to be handed back and re-acted on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/search/search.py"},"region":{"startLine":753}}}],"partialFingerprints":{"codehealthFindingId/v1":"95b4189584bd84737ef08a7c93832dfa16dacc1634786fa64b079383c92d1b61"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (21\u201323 lines \u00D7 2): graphiti_core/search/search_utils.py:423-445 | graphiti_core/search/search_utils.py:549-569 \u2014 both copies are in the same file, so extract the block into one function there and call it from each site \u2014 the copies drift apart the first time only one of them is edited. Read the line range as the matched WINDOW rather than a finished unit: at \u0060graphiti_core/search/search_utils.py:423\u0060 it begins part-way through the construct above it, so those exact lines cannot be lifted as they stand \u2014 widen the region to the smallest complete statement or declaration that contains it, and extract that. The \u0060return\u0060 at the foot of the matched lines is the enclosing body\u0027s own terminal exit, not an early one: it moves with them unchanged, and each site calls the extracted unit from the position that \u0060return\u0060 occupied \u2014 no decision has to be handed back and re-acted on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/search/search_utils.py"},"region":{"startLine":423}}}],"partialFingerprints":{"codehealthFindingId/v1":"6941eca287a71e6453c302b58958ca913dc4a693aef8207011fae03cda2498ae"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (23 lines \u00D7 2): graphiti_core/utils/content_chunking.py:426-448 | graphiti_core/utils/content_chunking.py:483-505 \u2014 both copies are in the same file, so extract the block into one function there and call it from each site \u2014 the copies drift apart the first time only one of them is edited. The matched lines also transfer control out of the body holding them, which cannot survive a move into a called unit unchanged: have the extracted unit return that decision and let each site act on it. Note that the copies do not run to the end of the range shown: their LAST lines are different code, not the same code under different names \u2014 the matched region ends inside that line. Extract the lines above it, and read the last line of each site separately."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/content_chunking.py"},"region":{"startLine":426}}}],"partialFingerprints":{"codehealthFindingId/v1":"94ae1af6417e2671212a4648bd94255ade9e0aea94d58593696c11a297cacf9b"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (21\u201322 lines \u00D7 4): graphiti_core/driver/falkordb/operations/search_ops.py:538-558 | graphiti_core/driver/kuzu/operations/search_ops.py:661-681 | graphiti_core/driver/neo4j/operations/search_ops.py:552-573 | graphiti_core/driver/neptune/operations/search_ops.py:623-643 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/search_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/search_ops.py\u0060 as WHOLE FILES: this scan already matched 3 separate duplicated blocks between them, totalling at least 58 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. Read the line range as the matched WINDOW rather than a finished unit: at \u0060graphiti_core/driver/falkordb/operations/search_ops.py:538\u0060 it begins part-way through the construct above it, so those exact lines cannot be lifted as they stand \u2014 widen the region to the smallest complete statement or declaration that contains it, and extract that."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/search_ops.py"},"region":{"startLine":538}}}],"partialFingerprints":{"codehealthFindingId/v1":"23293da45ceb0d322fe0eea41754d82deabde3ab68776296957fa7479910efe4"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (22 lines \u00D7 2): graphiti_core/driver/falkordb/operations/search_ops.py:575-596 | graphiti_core/driver/neptune/operations/search_ops.py:660-681 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/search_ops.py\u0060 and \u0060graphiti_core/driver/neptune/operations/search_ops.py\u0060 as WHOLE FILES: this scan already matched 7 separate duplicated blocks between them, totalling at least 143 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. The matched lines also transfer control out of the body holding them, which cannot survive a move into a called unit unchanged: have the extracted unit return that decision and let each site act on it."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/search_ops.py"},"region":{"startLine":575}}}],"partialFingerprints":{"codehealthFindingId/v1":"98555c181748ceb714c9421a95463ec2da5be5d725634cb116fe187749424de4"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (22 lines \u00D7 2): graphiti_core/driver/neptune/operations/search_ops.py:447-468 | graphiti_core/driver/neptune/operations/search_ops.py:499-520 \u2014 both copies are in the same file, so extract the block into one function there and call it from each site \u2014 the copies drift apart the first time only one of them is edited. Read the line range as the matched WINDOW rather than a finished unit: at \u0060graphiti_core/driver/neptune/operations/search_ops.py:447\u0060 it does not close everything it opens, so those exact lines cannot be lifted as they stand \u2014 widen the region to the smallest complete statement or declaration that contains it, and extract that. The matched lines also transfer control out of the body holding them, which cannot survive a move into a called unit unchanged: have the extracted unit return that decision and let each site act on it."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neptune/operations/search_ops.py"},"region":{"startLine":447}}}],"partialFingerprints":{"codehealthFindingId/v1":"872556305bfe436250c01c5645ff1e200e76f9a7a13b94e13520b5f77376cbbf"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (21 lines \u00D7 6): graphiti_core/driver/falkordb/operations/community_edge_ops.py:153-173 | graphiti_core/driver/falkordb/operations/entity_edge_ops.py:181-201 | graphiti_core/driver/falkordb/operations/episode_node_ops.py:190-210 | graphiti_core/driver/falkordb/operations/episodic_edge_ops.py:167-187 | graphiti_core/driver/falkordb/operations/has_episode_edge_ops.py:162-182 | graphiti_core/driver/falkordb/operations/next_episode_edge_ops.py:162-182 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/community_edge_ops.py\u0060 and \u0060graphiti_core/driver/falkordb/operations/entity_edge_ops.py\u0060 as WHOLE FILES: this scan already matched 3 separate duplicated blocks between them, totalling at least 44 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. If that is what happened, the fix is to keep one copy and have the other call it (or delete it), which resolves this row and its siblings together \u2014 extracting one helper per block leaves the fork in place. Read the line range as the matched WINDOW rather than a finished unit: at \u0060graphiti_core/driver/falkordb/operations/community_edge_ops.py:153\u0060 it does not close everything it opens, so those exact lines cannot be lifted as they stand \u2014 widen the region to the smallest complete statement or declaration that contains it, and extract that. Note that the copies do not run to the end of the range shown: their LAST lines are different code, not the same code under different names \u2014 the matched region ends inside that line. Extract the lines above it, and read the last line of each site separately."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/community_edge_ops.py"},"region":{"startLine":153}}}],"partialFingerprints":{"codehealthFindingId/v1":"ccde9831ad6a4b32c99452df63cfd86b83b794c5e2318f2a98b2eda336aa8065"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (21 lines \u00D7 3): graphiti_core/driver/falkordb/operations/community_node_ops.py:175-195 | graphiti_core/driver/falkordb/operations/entity_node_ops.py:196-216 | graphiti_core/driver/falkordb/operations/saga_node_ops.py:175-195 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/community_node_ops.py\u0060 and \u0060graphiti_core/driver/falkordb/operations/entity_node_ops.py\u0060 as WHOLE FILES: this scan already matched 4 separate duplicated blocks between them, totalling at least 52 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. If that is what happened, the fix is to keep one copy and have the other call it (or delete it), which resolves this row and its siblings together \u2014 extracting one helper per block leaves the fork in place. Read the line range as the matched WINDOW rather than a finished unit: at \u0060graphiti_core/driver/falkordb/operations/community_node_ops.py:175\u0060 it does not close everything it opens, so those exact lines cannot be lifted as they stand \u2014 widen the region to the smallest complete statement or declaration that contains it, and extract that."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/community_node_ops.py"},"region":{"startLine":175}}}],"partialFingerprints":{"codehealthFindingId/v1":"0cfddf1f52a62879b805c61303ed2d528a1c219ec12799aeadeae9c414a79070"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (21 lines \u00D7 3): graphiti_core/driver/falkordb/operations/search_ops.py:179-199 | graphiti_core/driver/neo4j/operations/search_ops.py:193-213 | graphiti_core/driver/neptune/operations/search_ops.py:198-219 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/search_ops.py\u0060 and \u0060graphiti_core/driver/neo4j/operations/search_ops.py\u0060 as WHOLE FILES: this scan already matched 6 separate duplicated blocks between them, totalling at least 121 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. Read the line range as the matched WINDOW rather than a finished unit: at \u0060graphiti_core/driver/falkordb/operations/search_ops.py:179\u0060 it begins part-way through the construct above it, so those exact lines cannot be lifted as they stand \u2014 widen the region to the smallest complete statement or declaration that contains it, and extract that."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/search_ops.py"},"region":{"startLine":179}}}],"partialFingerprints":{"codehealthFindingId/v1":"bc9e800b739da2075383803395f1a8334ab9392b75e8fd8602c9fe2c3eb0e7ae"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (21 lines \u00D7 3): graphiti_core/driver/falkordb/operations/search_ops.py:344-364 | graphiti_core/driver/neo4j/operations/search_ops.py:359-379 | graphiti_core/driver/neptune/operations/search_ops.py:395-416 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/search_ops.py\u0060 and \u0060graphiti_core/driver/neo4j/operations/search_ops.py\u0060 as WHOLE FILES: this scan already matched 6 separate duplicated blocks between them, totalling at least 121 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. Read the line range as the matched WINDOW rather than a finished unit: at \u0060graphiti_core/driver/falkordb/operations/search_ops.py:344\u0060 it begins part-way through the construct above it, so those exact lines cannot be lifted as they stand \u2014 widen the region to the smallest complete statement or declaration that contains it, and extract that."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/search_ops.py"},"region":{"startLine":344}}}],"partialFingerprints":{"codehealthFindingId/v1":"e6202d4d8861ee38cf46f1f4fea2294f7c25d5c44a253bd2fd4d99b21d09a5a0"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (17\u201321 lines \u00D7 3): graphiti_core/search/search.py:571-587 | graphiti_core/search/search.py:713-731 | graphiti_core/search/search.py:817-837 \u2014 all 3 copies are in the same file, so extract the block into one function there and call it from every one of those sites \u2014 resolving only two of them leaves the rest to drift apart the first time one is edited. Read the line range as the matched WINDOW rather than a finished unit: at \u0060graphiti_core/search/search.py:571\u0060 it begins part-way through the construct above it, so those exact lines cannot be lifted as they stand \u2014 widen the region to the smallest complete statement or declaration that contains it, and extract that."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/search/search.py"},"region":{"startLine":571}}}],"partialFingerprints":{"codehealthFindingId/v1":"df79e9ae4ea81b947c64b7560ddbffbadf964d9aa3db1182a302834abc499b83"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (20 lines \u00D7 4): graphiti_core/search/search_filters.py:169-188 | graphiti_core/search/search_filters.py:200-219 | graphiti_core/search/search_filters.py:231-250 | graphiti_core/search/search_filters.py:262-281 \u2014 all 4 copies are in the same file, so extract the block into one function there and call it from every one of those sites \u2014 resolving only two of them leaves the rest to drift apart the first time one is edited."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/search/search_filters.py"},"region":{"startLine":169}}}],"partialFingerprints":{"codehealthFindingId/v1":"ec742349d16979949b1a359e8fc8249cd293f94940bab2e8aa41c33e1abbe0b6"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (19\u201320 lines \u00D7 3): graphiti_core/edges.py:390-408 | graphiti_core/edges.py:462-481 | graphiti_core/edges.py:557-575 \u2014 all 3 copies are in the same file, so extract the block into one function there and call it from every one of those sites \u2014 resolving only two of them leaves the rest to drift apart the first time one is edited."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/edges.py"},"region":{"startLine":390}}}],"partialFingerprints":{"codehealthFindingId/v1":"12ea98286385291ca91ceb193558a4d8753c57ca63b588396a380ee6706fbf7a"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (18\u201320 lines \u00D7 2): graphiti_core/llm_client/jev_client.py:189-206 | graphiti_core/llm_client/openai_decisions_client.py:170-189 \u2014 before extracting anything, compare \u0060graphiti_core/llm_client/jev_client.py\u0060 and \u0060graphiti_core/llm_client/openai_decisions_client.py\u0060 as WHOLE FILES: this scan already matched 3 separate duplicated blocks between them, totalling at least 66 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. If that is what happened, the fix is to keep one copy and have the other call it (or delete it), which resolves this row and its siblings together \u2014 extracting one helper per block leaves the fork in place. Read the line range as the matched WINDOW rather than a finished unit: at \u0060graphiti_core/llm_client/jev_client.py:189\u0060 it begins part-way through the construct above it, so those exact lines cannot be lifted as they stand \u2014 widen the region to the smallest complete statement or declaration that contains it, and extract that. The matched lines also transfer control out of the body holding them, which cannot survive a move into a called unit unchanged: have the extracted unit return that decision and let each site act on it."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/jev_client.py"},"region":{"startLine":189}}}],"partialFingerprints":{"codehealthFindingId/v1":"effe83126449d8991640718ecce588b2c136739cd5ab482d2998ec4cecb047a2"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (20 lines \u00D7 2): graphiti_core/search/search_utils.py:346-365 | graphiti_core/search/search_utils.py:689-708 \u2014 both copies are in the same file, so extract the block into one function there and call it from each site \u2014 the copies drift apart the first time only one of them is edited. Read the line range as the matched WINDOW rather than a finished unit: at \u0060graphiti_core/search/search_utils.py:346\u0060 it does not close everything it opens, so those exact lines cannot be lifted as they stand \u2014 widen the region to the smallest complete statement or declaration that contains it, and extract that."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/search/search_utils.py"},"region":{"startLine":346}}}],"partialFingerprints":{"codehealthFindingId/v1":"31073060b913cdab1d0bcd30af3a12f092256f6041704a5fa8d2f6dfa27b0657"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (20 lines \u00D7 2): mcp_server/src/graphiti_mcp_server.py:699-718 | mcp_server/src/graphiti_mcp_server.py:764-783 \u2014 both copies are in the same file, so extract the block into one function there and call it from each site \u2014 the copies drift apart the first time only one of them is edited. The matched lines also transfer control out of the body holding them, which cannot survive a move into a called unit unchanged: have the extracted unit return that decision and let each site act on it."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":699}}}],"partialFingerprints":{"codehealthFindingId/v1":"695abb5f757f731beb622027b70b88482a679f5a58699e19342172688ae3de82"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (18\u201319 lines \u00D7 4): graphiti_core/driver/falkordb/operations/search_ops.py:587-604 | graphiti_core/driver/kuzu/operations/search_ops.py:712-729 | graphiti_core/driver/neo4j/operations/search_ops.py:603-621 | graphiti_core/driver/neptune/operations/search_ops.py:672-689 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/search_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/search_ops.py\u0060 as WHOLE FILES: this scan already matched 3 separate duplicated blocks between them, totalling at least 58 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. Read the line range as the matched WINDOW rather than a finished unit: at \u0060graphiti_core/driver/falkordb/operations/search_ops.py:587\u0060 it begins part-way through the construct above it, so those exact lines cannot be lifted as they stand \u2014 widen the region to the smallest complete statement or declaration that contains it, and extract that."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/search_ops.py"},"region":{"startLine":587}}}],"partialFingerprints":{"codehealthFindingId/v1":"aecdc3f3fdc1bf92bec88f8f5da217ce7440721b12e236700f474a46ccbb2442"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (17\u201319 lines \u00D7 3): graphiti_core/driver/falkordb/operations/search_ops.py:289-305 | graphiti_core/driver/neo4j/operations/search_ops.py:303-319 | graphiti_core/driver/neptune/operations/search_ops.py:313-331 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/search_ops.py\u0060 and \u0060graphiti_core/driver/neo4j/operations/search_ops.py\u0060 as WHOLE FILES: this scan already matched 6 separate duplicated blocks between them, totalling at least 121 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. Read the line range as the matched WINDOW rather than a finished unit: at \u0060graphiti_core/driver/falkordb/operations/search_ops.py:289\u0060 it does not close everything it opens, so those exact lines cannot be lifted as they stand \u2014 widen the region to the smallest complete statement or declaration that contains it, and extract that. Note that the copies do not run to the end of the range shown: their LAST lines are different code, not the same code under different names \u2014 the matched region ends inside that line. Extract the lines above it, and read the last line of each site separately."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/search_ops.py"},"region":{"startLine":289}}}],"partialFingerprints":{"codehealthFindingId/v1":"a8477e4f8a50d273a9a1b6cd54aab69a428aa97a03e7550d18cc9eff3ed764ee"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (19 lines \u00D7 2): graphiti_core/driver/record_parsers.py:90-108 | graphiti_core/nodes.py:1073-1091 \u2014 before extracting anything, compare \u0060graphiti_core/driver/record_parsers.py\u0060 and \u0060graphiti_core/nodes.py\u0060 as WHOLE FILES: this scan already matched 3 separate duplicated blocks between them, totalling at least 35 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. The matched lines also transfer control out of the body holding them, which cannot survive a move into a called unit unchanged: have the extracted unit return that decision and let each site act on it."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/record_parsers.py"},"region":{"startLine":90}}}],"partialFingerprints":{"codehealthFindingId/v1":"f02fb261e32fa581435ff445c27919351f452b6c1adb957ac9ae62975ed161be"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (17\u201318 lines \u00D7 2): graphiti_core/driver/neo4j/operations/search_ops.py:593-609 | graphiti_core/search/search_utils.py:1879-1896 \u2014 the copies span different directories, so extracting a shared function means choosing where it lives: put it somewhere both call sites can already reach \u2014 a location they all depend on today, or a new shared one if there is none \u2014 and call it from each site; until then, every change has to be made twice."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neo4j/operations/search_ops.py"},"region":{"startLine":593}}}],"partialFingerprints":{"codehealthFindingId/v1":"ef0cb925b0165a6b679090a497bf1a6a02aa54d70f6a902f138669ffe7ad4b10"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (17\u201318 lines \u00D7 2): graphiti_core/utils/content_chunking.py:405-422 | graphiti_core/utils/content_chunking.py:463-479 \u2014 both copies are in the same file, so extract the block into one function there and call it from each site \u2014 the copies drift apart the first time only one of them is edited. The matched lines also transfer control out of the body holding them, which cannot survive a move into a called unit unchanged: have the extracted unit return that decision and let each site act on it."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/content_chunking.py"},"region":{"startLine":405}}}],"partialFingerprints":{"codehealthFindingId/v1":"edaf1f4a7536d00b2d87308416196bd9150d9d2cb60e65cebed6879a642ab834"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (17 lines \u00D7 5): graphiti_core/driver/falkordb/operations/search_ops.py:283-299 | graphiti_core/driver/kuzu/operations/search_ops.py:365-381 | graphiti_core/driver/neo4j/operations/search_ops.py:297-313 | graphiti_core/driver/neptune/operations/search_ops.py:307-323 | graphiti_core/search/search_utils.py:332-348 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/search_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/search_ops.py\u0060 as WHOLE FILES: this scan already matched 3 separate duplicated blocks between them, totalling at least 58 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. Read the line range as the matched WINDOW rather than a finished unit: at \u0060graphiti_core/driver/falkordb/operations/search_ops.py:283\u0060 it begins part-way through the construct above it, so those exact lines cannot be lifted as they stand \u2014 widen the region to the smallest complete statement or declaration that contains it, and extract that."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/search_ops.py"},"region":{"startLine":283}}}],"partialFingerprints":{"codehealthFindingId/v1":"3398b8f3947d387cb3f0c5d7c1766e3836eb43a67f9736f4a13086ad8b4f6a92"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (17 lines \u00D7 2): graphiti_core/graphiti.py:1070-1086 | graphiti_core/graphiti.py:1810-1826 \u2014 both copies are in the same file, so extract the block into one function there and call it from each site \u2014 the copies drift apart the first time only one of them is edited."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/graphiti.py"},"region":{"startLine":1070}}}],"partialFingerprints":{"codehealthFindingId/v1":"fd635f6cb6ac67c3f063b39ba52cf391c9c8ccff934b1f39e4c3a61b5573b01e"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (16\u201317 lines \u00D7 2): graphiti_core/search/search_utils.py:238-254 | graphiti_core/search/search_utils.py:607-622 \u2014 both copies are in the same file, so extract the block into one function there and call it from each site \u2014 the copies drift apart the first time only one of them is edited. Read the line range as the matched WINDOW rather than a finished unit: at \u0060graphiti_core/search/search_utils.py:238\u0060 it does not close everything it opens, so those exact lines cannot be lifted as they stand \u2014 widen the region to the smallest complete statement or declaration that contains it, and extract that."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/search/search_utils.py"},"region":{"startLine":238}}}],"partialFingerprints":{"codehealthFindingId/v1":"dc5d6876871255fd10219ec0d8fe1f5a3e074312dfa2035b70a49b0cad3a9e99"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (12\u201316 lines \u00D7 4): graphiti_core/search/search.py:409-420 | graphiti_core/search/search.py:614-629 | graphiti_core/search/search.py:749-764 | graphiti_core/search/search.py:867-880 \u2014 all 4 copies are in the same file, so extract the block into one function there and call it from every one of those sites \u2014 resolving only two of them leaves the rest to drift apart the first time one is edited. Note that the copies do not run to the end of the range shown: their LAST lines are different code, not the same code under different names \u2014 the matched region ends inside that line. Extract the lines above it, and read the last line of each site separately."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/search/search.py"},"region":{"startLine":409}}}],"partialFingerprints":{"codehealthFindingId/v1":"38a9828e41bf17d20d78f492a2fd3c195fcd6cc90084d515d8889878020de03c"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (16 lines \u00D7 2): graphiti_core/llm_client/gemini_client.py:458-473 | graphiti_core/llm_client/openai_base_client.py:337-352 \u2014 the copies sit in sibling files of one directory, so a shared home is within easy reach: extract the block into a single shared function the call sites can all reach \u2014 a file they already depend on, or a new one alongside them \u2014 and call it from both call sites, so a change lands once."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/gemini_client.py"},"region":{"startLine":458}}}],"partialFingerprints":{"codehealthFindingId/v1":"8541882c587ca96dec85fd1c847f1dd283c65a454bb572de50db1ccc6d6401c8"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (14\u201315 lines \u00D7 3): graphiti_core/search/search_utils.py:605-618 | graphiti_core/search/search_utils.py:905-918 | graphiti_core/search/search_utils.py:1002-1016 \u2014 all 3 copies are in the same file, so extract the block into one function there and call it from every one of those sites \u2014 resolving only two of them leaves the rest to drift apart the first time one is edited. Read the line range as the matched WINDOW rather than a finished unit: at \u0060graphiti_core/search/search_utils.py:605\u0060 it does not close everything it opens, so those exact lines cannot be lifted as they stand \u2014 widen the region to the smallest complete statement or declaration that contains it, and extract that."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/search/search_utils.py"},"region":{"startLine":605}}}],"partialFingerprints":{"codehealthFindingId/v1":"3fe73d0d4309c9be6aa0e672b3328ed18ed27f899a386c30d9d2e72612427f33"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (14\u201315 lines \u00D7 2): graphiti_core/search/search_utils.py:589-603 | graphiti_core/search/search_utils.py:681-694 \u2014 both copies are in the same file, so extract the block into one function there and call it from each site \u2014 the copies drift apart the first time only one of them is edited. Note that the copies do not run to the end of the range shown: their LAST lines are different code, not the same code under different names \u2014 the matched region ends inside that line. Extract the lines above it, and read the last line of each site separately."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/search/search_utils.py"},"region":{"startLine":589}}}],"partialFingerprints":{"codehealthFindingId/v1":"79702654e4eb8ed124b7c707db8a0965a4063444e2ffccf5f1c9199b39d955ab"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (14 lines \u00D7 2): graphiti_core/driver/kuzu/operations/search_ops.py:677-690 | graphiti_core/driver/kuzu/operations/search_ops.py:725-738 \u2014 both copies are in the same file, so extract the block into one function there and call it from each site \u2014 the copies drift apart the first time only one of them is edited. The matched lines also transfer control out of the body holding them, which cannot survive a move into a called unit unchanged: have the extracted unit return that decision and let each site act on it."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/kuzu/operations/search_ops.py"},"region":{"startLine":677}}}],"partialFingerprints":{"codehealthFindingId/v1":"41477e32a967636846713d88d3cac19efb9aa491a5ac38e88e0e6b8907b5d572"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (14 lines \u00D7 2): graphiti_core/llm_client/jev_client.py:99-112 | graphiti_core/llm_client/openai_decisions_client.py:67-80 \u2014 before extracting anything, compare \u0060graphiti_core/llm_client/jev_client.py\u0060 and \u0060graphiti_core/llm_client/openai_decisions_client.py\u0060 as WHOLE FILES: this scan already matched 3 separate duplicated blocks between them, totalling at least 66 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. If that is what happened, the fix is to keep one copy and have the other call it (or delete it), which resolves this row and its siblings together \u2014 extracting one helper per block leaves the fork in place."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/jev_client.py"},"region":{"startLine":99}}}],"partialFingerprints":{"codehealthFindingId/v1":"9044be0bb0832bee27c2d67d87e5dcc3c8dd80ab685a51e8839794a4358649cb"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (13 lines \u00D7 2): graphiti_core/driver/falkordb/operations/search_ops.py:555-567 | graphiti_core/driver/falkordb/operations/search_ops.py:601-613 \u2014 both copies are in the same file, so extract the block into one function there and call it from each site \u2014 the copies drift apart the first time only one of them is edited. The matched lines also transfer control out of the body holding them, which cannot survive a move into a called unit unchanged: have the extracted unit return that decision and let each site act on it."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/search_ops.py"},"region":{"startLine":555}}}],"partialFingerprints":{"codehealthFindingId/v1":"ddc7ce96b5d0d0e40e18d6aa6603ead1ea84e7aeef8349fc309ff0369d0f134f"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (13 lines \u00D7 2): graphiti_core/driver/neo4j/operations/search_ops.py:569-581 | graphiti_core/driver/neo4j/operations/search_ops.py:617-629 \u2014 both copies are in the same file, so extract the block into one function there and call it from each site \u2014 the copies drift apart the first time only one of them is edited. The matched lines also transfer control out of the body holding them, which cannot survive a move into a called unit unchanged: have the extracted unit return that decision and let each site act on it."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neo4j/operations/search_ops.py"},"region":{"startLine":569}}}],"partialFingerprints":{"codehealthFindingId/v1":"9a19207c9bd34c84495cb22a736d1faf8547ac23bdf33de09e2478527e9a157e"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (13 lines \u00D7 2): graphiti_core/driver/neptune/operations/search_ops.py:640-652 | graphiti_core/driver/neptune/operations/search_ops.py:686-698 \u2014 both copies are in the same file, so extract the block into one function there and call it from each site \u2014 the copies drift apart the first time only one of them is edited. The matched lines also transfer control out of the body holding them, which cannot survive a move into a called unit unchanged: have the extracted unit return that decision and let each site act on it."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neptune/operations/search_ops.py"},"region":{"startLine":640}}}],"partialFingerprints":{"codehealthFindingId/v1":"29da1a6774690f6b8e77701bfd736b4c72b86cf87bdf42e8636bd6ccca389f26"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (12\u201313 lines \u00D7 2): graphiti_core/utils/maintenance/combined_extraction.py:201-212 | graphiti_core/utils/maintenance/node_operations.py:359-371 \u2014 the copies sit in sibling files of one directory, so a shared home is within easy reach: extract the block into a single shared function the call sites can all reach \u2014 a file they already depend on, or a new one alongside them \u2014 and call it from both call sites, so a change lands once. The matched lines also transfer control out of the body holding them, which cannot survive a move into a called unit unchanged: have the extracted unit return that decision and let each site act on it. \u2605 These copies have DRIFTED, and that is worth reading before extracting anything: just before the matched lines, \u0060graphiti_core/utils/maintenance/combined_extraction.py:200\u0060 calls \u0060add\u0060, \u0060_normalize_string_exact\u0060 and \u0060graphiti_core/utils/maintenance/node_operations.py:358\u0060 does not \u2014 after which the two agree again for 4 more lines. One of those two behaviours is the intended one and the other is what a copy-paste left behind, so decide which BEFORE unifying them: extracting the shared part will silently settle it, and if the copy that skips the call is the wrong one, that bug is already live."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/combined_extraction.py"},"region":{"startLine":201}}}],"partialFingerprints":{"codehealthFindingId/v1":"15ffc2bf559f63f0675d3179396a0773932b1a866b4a6c92be54fdc52265979d"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (12 lines \u00D7 3): graphiti_core/search/search_utils.py:238-249 | graphiti_core/search/search_utils.py:607-618 | graphiti_core/search/search_utils.py:1004-1016 \u2014 all 3 copies are in the same file, so extract the block into one function there and call it from every one of those sites \u2014 resolving only two of them leaves the rest to drift apart the first time one is edited. Read the line range as the matched WINDOW rather than a finished unit: at \u0060graphiti_core/search/search_utils.py:238\u0060 it does not close everything it opens, so those exact lines cannot be lifted as they stand \u2014 widen the region to the smallest complete statement or declaration that contains it, and extract that."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/search/search_utils.py"},"region":{"startLine":238}}}],"partialFingerprints":{"codehealthFindingId/v1":"ebeafca30f14bffcfe0500ad0673c88f8d235278ce6b717dbd7f7a72571ae28e"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (12 lines \u00D7 3): graphiti_core/search/search_utils.py:371-382 | graphiti_core/search/search_utils.py:714-725 | graphiti_core/search/search_utils.py:1107-1118 \u2014 all 3 copies are in the same file, so extract the block into one function there and call it from every one of those sites \u2014 resolving only two of them leaves the rest to drift apart the first time one is edited. Read the line range as the matched WINDOW rather than a finished unit: at \u0060graphiti_core/search/search_utils.py:371\u0060 it begins part-way through the construct above it, so those exact lines cannot be lifted as they stand \u2014 widen the region to the smallest complete statement or declaration that contains it, and extract that."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/search/search_utils.py"},"region":{"startLine":371}}}],"partialFingerprints":{"codehealthFindingId/v1":"57779a6e0a9ef1274db485b0a4cef150020465a1492ec9c3f690d46705b7134c"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (11 lines \u00D7 3): graphiti_core/driver/falkordb/operations/entity_edge_ops.py:265-275 | graphiti_core/driver/kuzu/operations/entity_edge_ops.py:221-231 | graphiti_core/driver/neptune/operations/entity_edge_ops.py:242-252 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/entity_edge_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/entity_edge_ops.py\u0060 as WHOLE FILES: this scan already matched 8 separate duplicated blocks between them, totalling at least 77 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/entity_edge_ops.py"},"region":{"startLine":265}}}],"partialFingerprints":{"codehealthFindingId/v1":"ef45f897c6c81cfe75913fe105a5668c2b309b15679924e9fc06334169cacdaf"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (11 lines \u00D7 3): graphiti_core/driver/falkordb/operations/entity_node_ops.py:251-261 | graphiti_core/driver/kuzu/operations/entity_node_ops.py:226-236 | graphiti_core/driver/neptune/operations/entity_node_ops.py:222-232 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/entity_node_ops.py\u0060 and \u0060graphiti_core/driver/neptune/operations/entity_node_ops.py\u0060 as WHOLE FILES: this scan already matched 4 separate duplicated blocks between them, totalling at least 42 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/entity_node_ops.py"},"region":{"startLine":251}}}],"partialFingerprints":{"codehealthFindingId/v1":"b28fa7c550f8acf97bfa42ae84e89762249753a5687dd3d0d3e438188160c7e9"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (11 lines \u00D7 3): graphiti_core/search/search_utils.py:1977-1987 | graphiti_core/search/search_utils.py:2018-2028 | graphiti_core/search/search_utils.py:2065-2075 \u2014 all 3 copies are in the same file, so extract the block into one function there and call it from every one of those sites \u2014 resolving only two of them leaves the rest to drift apart the first time one is edited. Read the line range as the matched WINDOW rather than a finished unit: at \u0060graphiti_core/search/search_utils.py:1977\u0060 it begins part-way through the construct above it, so those exact lines cannot be lifted as they stand \u2014 widen the region to the smallest complete statement or declaration that contains it, and extract that. The \u0060return\u0060 at the foot of the matched lines is the enclosing body\u0027s own terminal exit, not an early one: it moves with them unchanged, and each site calls the extracted unit from the position that \u0060return\u0060 occupied \u2014 no decision has to be handed back and re-acted on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/search/search_utils.py"},"region":{"startLine":1977}}}],"partialFingerprints":{"codehealthFindingId/v1":"8c53129cafa7bacb6b706afa211093ae74ceb317de5bb6fd9a2ce65c057c6f80"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (10 lines \u00D7 3): graphiti_core/driver/neptune/operations/search_ops.py:66-75 | graphiti_core/driver/neptune/operations/search_ops.py:442-451 | graphiti_core/driver/neptune/operations/search_ops.py:494-503 \u2014 all 3 copies are in the same file, so extract the block into one function there and call it from every one of those sites \u2014 resolving only two of them leaves the rest to drift apart the first time one is edited. Read the line range as the matched WINDOW rather than a finished unit: at \u0060graphiti_core/driver/neptune/operations/search_ops.py:66\u0060 it does not close everything it opens, so those exact lines cannot be lifted as they stand \u2014 widen the region to the smallest complete statement or declaration that contains it, and extract that. The matched lines also transfer control out of the body holding them, which cannot survive a move into a called unit unchanged: have the extracted unit return that decision and let each site act on it."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neptune/operations/search_ops.py"},"region":{"startLine":66}}}],"partialFingerprints":{"codehealthFindingId/v1":"ad46c8d53cc793829701c2baefc75191fef57ae0f67ed987578b20332f2e4b32"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (10 lines \u00D7 2): graphiti_core/edges.py:975-984 | graphiti_core/nodes.py:1095-1104 \u2014 before extracting anything, compare \u0060graphiti_core/edges.py\u0060 and \u0060graphiti_core/nodes.py\u0060 as WHOLE FILES: this scan already matched 3 separate duplicated blocks between them, totalling at least 34 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. If that is what happened, the fix is to keep one copy and have the other call it (or delete it), which resolves this row and its siblings together \u2014 extracting one helper per block leaves the fork in place."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/edges.py"},"region":{"startLine":975}}}],"partialFingerprints":{"codehealthFindingId/v1":"bcd2c62a555b9ea9a29f6e77335fd3fa3fc1c12f8c1438d85a7339654d8f4969"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (9 lines \u00D7 2): graphiti_core/driver/record_parsers.py:28-36 | graphiti_core/nodes.py:1100-1108 \u2014 before extracting anything, compare \u0060graphiti_core/driver/record_parsers.py\u0060 and \u0060graphiti_core/nodes.py\u0060 as WHOLE FILES: this scan already matched 3 separate duplicated blocks between them, totalling at least 35 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/record_parsers.py"},"region":{"startLine":28}}}],"partialFingerprints":{"codehealthFindingId/v1":"fcc122c3f803f9002fadb374b7c3d50f3bfbffcd0d596e3293aa1115d6162d94"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (9 lines \u00D7 2): graphiti_core/llm_client/groq_client.py:69-77 | graphiti_core/llm_client/openai_generic_client.py:152-160 \u2014 the copies sit in sibling files of one directory, so a shared home is within easy reach: extract the block into a single shared function the call sites can all reach \u2014 a file they already depend on, or a new one alongside them \u2014 and call it from both call sites, so a change lands once. Read the line range as the matched WINDOW rather than a finished unit: at \u0060graphiti_core/llm_client/groq_client.py:69\u0060 it does not close everything it opens, so those exact lines cannot be lifted as they stand \u2014 widen the region to the smallest complete statement or declaration that contains it, and extract that. \u2605 These copies have DRIFTED, and that is worth reading before extracting anything: just before the matched lines, \u0060graphiti_core/llm_client/openai_generic_client.py:151\u0060 calls \u0060_clean_input\u0060 and \u0060graphiti_core/llm_client/groq_client.py:68\u0060 does not \u2014 after which the two agree again for 4 more lines. One of those two behaviours is the intended one and the other is what a copy-paste left behind, so decide which BEFORE unifying them: extracting the shared part will silently settle it, and if the copy that skips the call is the wrong one, that bug is already live."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/groq_client.py"},"region":{"startLine":69}}}],"partialFingerprints":{"codehealthFindingId/v1":"7c071c441742de1cd9228ff6148d9de5743b83a1b14fbd6139fd85561851be36"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (9 lines \u00D7 2): graphiti_core/nodes.py:507-515 | graphiti_core/nodes.py:758-766 \u2014 both copies are in the same file, so extract the block into one function there and call it from each site \u2014 the copies drift apart the first time only one of them is edited. The \u0060return\u0060 at the foot of the matched lines is the enclosing body\u0027s own terminal exit, not an early one: it moves with them unchanged, and each site calls the extracted unit from the position that \u0060return\u0060 occupied \u2014 no decision has to be handed back and re-acted on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/nodes.py"},"region":{"startLine":507}}}],"partialFingerprints":{"codehealthFindingId/v1":"ccb8caa1b76d3175054a533ca273f622a32f4e60a8e1e3f350e243f628a7afc9"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (8 lines \u00D7 6): graphiti_core/driver/neptune/operations/search_ops.py:151-158 | graphiti_core/driver/neptune/operations/search_ops.py:341-348 | graphiti_core/driver/neptune/operations/search_ops.py:567-574 | graphiti_core/search/search_utils.py:375-382 | graphiti_core/search/search_utils.py:718-725 | graphiti_core/search/search_utils.py:1111-1118 \u2014 there are 6 copies across 2 file(s) \u2014 more copies than files, so at least one file holds the block twice. Extract it once into a single shared function every call site can reach and call it from all 6 sites; resolving a subset leaves the remainder to drift apart."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neptune/operations/search_ops.py"},"region":{"startLine":151}}}],"partialFingerprints":{"codehealthFindingId/v1":"cc0542f4a08c2bc5a76e8ad17dc81c403e04ae58dcfaa1bc707d43e033b3a2e2"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (8 lines \u00D7 2): graphiti_core/driver/kuzu/operations/record_parsers.py:26-33 | graphiti_core/driver/kuzu/operations/record_parsers.py:38-45 \u2014 both copies are in the same file, so extract the block into one function there and call it from each site \u2014 the copies drift apart the first time only one of them is edited."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/kuzu/operations/record_parsers.py"},"region":{"startLine":26}}}],"partialFingerprints":{"codehealthFindingId/v1":"db5cf75b561a6bb85bd01efc9ff9e87307d205e500e55ddaa4919cdf25609fb0"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (8 lines \u00D7 2): graphiti_core/driver/record_parsers.py:25-32 | graphiti_core/driver/record_parsers.py:54-61 \u2014 both copies are in the same file, so extract the block into one function there and call it from each site \u2014 the copies drift apart the first time only one of them is edited."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/record_parsers.py"},"region":{"startLine":25}}}],"partialFingerprints":{"codehealthFindingId/v1":"f91114def0b06749697e33928a8ec3973ea92ab332ef8a3780bf5f4952d3076f"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (7 lines \u00D7 4): graphiti_core/driver/record_parsers.py:26-32 | graphiti_core/driver/record_parsers.py:55-61 | graphiti_core/edges.py:978-984 | graphiti_core/nodes.py:1098-1104 \u2014 before extracting anything, compare \u0060graphiti_core/driver/record_parsers.py\u0060 and \u0060graphiti_core/nodes.py\u0060 as WHOLE FILES: this scan already matched 3 separate duplicated blocks between them, totalling at least 35 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/record_parsers.py"},"region":{"startLine":26}}}],"partialFingerprints":{"codehealthFindingId/v1":"4c718f333d92f76fd5e46ffbb0bfea938eb2de331164a161e89c81ea36a2af89"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (7 lines \u00D7 2): graphiti_core/llm_client/openai_base_client.py:103-109 | graphiti_core/llm_client/openai_generic_client.py:149-155 \u2014 the copies sit in sibling files of one directory, so a shared home is within easy reach: extract the block into a single shared function the call sites can all reach \u2014 a file they already depend on, or a new one alongside them \u2014 and call it from both call sites, so a change lands once."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/openai_base_client.py"},"region":{"startLine":103}}}],"partialFingerprints":{"codehealthFindingId/v1":"9256f55c5f3e3cc6925f6e469e4b39438df22a8ce59f416630f4b56b5583d9bd"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (7 lines \u00D7 2): mcp_server/src/graphiti_mcp_server.py:1050-1056 | mcp_server/src/graphiti_mcp_server.py:1092-1098 \u2014 both copies are in the same file, so extract the block into one function there and call it from each site \u2014 the copies drift apart the first time only one of them is edited. Read the line range as the matched WINDOW rather than a finished unit: at \u0060mcp_server/src/graphiti_mcp_server.py:1050\u0060 it begins part-way through the construct above it, so those exact lines cannot be lifted as they stand \u2014 widen the region to the smallest complete statement or declaration that contains it, and extract that. The \u0060return\u0060 at the foot of the matched lines is the enclosing body\u0027s own terminal exit, not an early one: it moves with them unchanged, and each site calls the extracted unit from the position that \u0060return\u0060 occupied \u2014 no decision has to be handed back and re-acted on. Note that the copies do not run to the end of the range shown: their LAST lines are different code, not the same code under different names \u2014 the matched region ends inside that line. Extract the lines above it, and read the last line of each site separately."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":1050}}}],"partialFingerprints":{"codehealthFindingId/v1":"0488004a0b108bc9be4af8f2e701ce31cc07fdd10ed2b6b3403de8b57231e6cb"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (305 lines \u00D7 2): graphiti_core/driver/graph_operations/graph_operations.py:41-910 | graphiti_core/driver/search_interface/search_interface.py:44-348 \u2014 the copies span different directories, so extracting a shared function means choosing where it lives: put it somewhere both call sites can already reach \u2014 a location they all depend on today, or a new shared one if there is none \u2014 and call it from each site; until then, every change has to be made twice."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/graph_operations/graph_operations.py"},"region":{"startLine":41}}}],"partialFingerprints":{"codehealthFindingId/v1":"772e3d1da12ebe17bd96ad25d8fafef9cbef862a0d327f477fc47ffeeb4d9520"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (7 lines \u00D7 5): graphiti_core/driver/falkordb/operations/community_edge_ops.py:35-41 | graphiti_core/driver/kuzu/operations/community_edge_ops.py:35-41 | graphiti_core/driver/neo4j/operations/community_edge_ops.py:35-41 | graphiti_core/driver/neptune/operations/community_edge_ops.py:35-41 | graphiti_core/edges.py:1014-1020 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/community_edge_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/community_edge_ops.py\u0060 as WHOLE FILES: this scan already matched 6 separate duplicated blocks between them, totalling at least 76 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. The matched lines also transfer control out of the body holding them, which cannot survive a move into a called unit unchanged: have the extracted unit return that decision and let each site act on it."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/community_edge_ops.py"},"region":{"startLine":35}}}],"partialFingerprints":{"codehealthFindingId/v1":"9f48f4d2dbb6823933727aee5af9b7af9936db76cf832ec64bd5053311b7554f"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (10 lines \u00D7 35): graphiti_core/driver/falkordb/operations/community_edge_ops.py:72-81 | graphiti_core/driver/falkordb/operations/community_node_ops.py:73-86 | graphiti_core/driver/falkordb/operations/entity_edge_ops.py:104-113 | graphiti_core/driver/falkordb/operations/entity_node_ops.py:100-113 | graphiti_core/driver/falkordb/operations/episode_node_ops.py:88-101 | graphiti_core/driver/falkordb/operations/episodic_edge_ops.py:86-95 | graphiti_core/driver/falkordb/operations/has_episode_edge_ops.py:81-90 | graphiti_core/driver/falkordb/operations/next_episode_edge_ops.py:81-90 | graphiti_core/driver/falkordb/operations/saga_node_ops.py:77-86 | graphiti_core/driver/kuzu/operations/community_edge_ops.py:72-81 | graphiti_core/driver/kuzu/operations/community_node_ops.py:74-83 | graphiti_core/driver/kuzu/operations/entity_edge_ops.py:83-92 | graphiti_core/driver/kuzu/operations/episode_node_ops.py:78-87 | graphiti_core/driver/kuzu/operations/episodic_edge_ops.py:94-103 | graphiti_core/driver/kuzu/operations/has_episode_edge_ops.py:80-89 | graphiti_core/driver/kuzu/operations/next_episode_edge_ops.py:80-89 | graphiti_core/driver/kuzu/operations/saga_node_ops.py:78-87 | graphiti_core/driver/neo4j/operations/community_edge_ops.py:72-81 | graphiti_core/driver/neo4j/operations/community_node_ops.py:74-87 | graphiti_core/driver/neo4j/operations/entity_edge_ops.py:104-113 | graphiti_core/driver/neo4j/operations/entity_node_ops.py:96-109 | graphiti_core/driver/neo4j/operations/episode_node_ops.py:88-101 | graphiti_core/driver/neo4j/operations/episodic_edge_ops.py:86-95 | graphiti_core/driver/neo4j/operations/has_episode_edge_ops.py:80-89 | graphiti_core/driver/neo4j/operations/next_episode_edge_ops.py:80-89 | graphiti_core/driver/neo4j/operations/saga_node_ops.py:77-86 | graphiti_core/driver/neptune/operations/community_edge_ops.py:72-81 | graphiti_core/driver/neptune/operations/community_node_ops.py:88-98 | graphiti_core/driver/neptune/operations/entity_edge_ops.py:104-113 | graphiti_core/driver/neptune/operations/entity_node_ops.py:97-107 | graphiti_core/driver/neptune/operations/episode_node_ops.py:88-98 | graphiti_core/driver/neptune/operations/episodic_edge_ops.py:86-95 | graphiti_core/driver/neptune/operations/has_episode_edge_ops.py:80-89 | graphiti_core/driver/neptune/operations/next_episode_edge_ops.py:80-89 | graphiti_core/driver/neptune/operations/saga_node_ops.py:80-89 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/community_edge_ops.py\u0060 and \u0060graphiti_core/driver/falkordb/operations/entity_edge_ops.py\u0060 as WHOLE FILES: this scan already matched 3 separate duplicated blocks between them, totalling at least 44 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. If that is what happened, the fix is to keep one copy and have the other call it (or delete it), which resolves this row and its siblings together \u2014 extracting one helper per block leaves the fork in place."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/community_edge_ops.py"},"region":{"startLine":72}}}],"partialFingerprints":{"codehealthFindingId/v1":"0c6cb2f7cb2504d2fe8960df39f8b70b98351cf057fd2d79bca4638d662d9647"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (8 lines \u00D7 42): graphiti_core/driver/falkordb/operations/community_edge_ops.py:89-97 | graphiti_core/driver/falkordb/operations/community_node_ops.py:95-102 | graphiti_core/driver/falkordb/operations/community_node_ops.py:111-119 | graphiti_core/driver/falkordb/operations/entity_edge_ops.py:121-129 | graphiti_core/driver/falkordb/operations/entity_node_ops.py:122-129 | graphiti_core/driver/falkordb/operations/entity_node_ops.py:138-146 | graphiti_core/driver/falkordb/operations/episode_node_ops.py:110-117 | graphiti_core/driver/falkordb/operations/episode_node_ops.py:126-134 | graphiti_core/driver/falkordb/operations/episodic_edge_ops.py:103-111 | graphiti_core/driver/falkordb/operations/has_episode_edge_ops.py:98-106 | graphiti_core/driver/falkordb/operations/next_episode_edge_ops.py:98-106 | graphiti_core/driver/falkordb/operations/saga_node_ops.py:95-102 | graphiti_core/driver/falkordb/operations/saga_node_ops.py:111-119 | graphiti_core/driver/kuzu/operations/community_edge_ops.py:89-97 | graphiti_core/driver/kuzu/operations/community_node_ops.py:93-100 | graphiti_core/driver/kuzu/operations/community_node_ops.py:110-118 | graphiti_core/driver/kuzu/operations/entity_edge_ops.py:100-108 | graphiti_core/driver/kuzu/operations/episode_node_ops.py:97-104 | graphiti_core/driver/kuzu/operations/episode_node_ops.py:114-122 | graphiti_core/driver/kuzu/operations/episodic_edge_ops.py:111-119 | graphiti_core/driver/kuzu/operations/has_episode_edge_ops.py:97-105 | graphiti_core/driver/kuzu/operations/next_episode_edge_ops.py:97-105 | graphiti_core/driver/kuzu/operations/saga_node_ops.py:97-104 | graphiti_core/driver/kuzu/operations/saga_node_ops.py:114-122 | graphiti_core/driver/neo4j/operations/community_edge_ops.py:89-97 | graphiti_core/driver/neo4j/operations/entity_edge_ops.py:121-129 | graphiti_core/driver/neo4j/operations/episodic_edge_ops.py:103-111 | graphiti_core/driver/neo4j/operations/has_episode_edge_ops.py:97-105 | graphiti_core/driver/neo4j/operations/next_episode_edge_ops.py:97-105 | graphiti_core/driver/neptune/operations/community_edge_ops.py:89-97 | graphiti_core/driver/neptune/operations/community_node_ops.py:107-114 | graphiti_core/driver/neptune/operations/community_node_ops.py:123-131 | graphiti_core/driver/neptune/operations/entity_edge_ops.py:121-129 | graphiti_core/driver/neptune/operations/entity_node_ops.py:116-123 | graphiti_core/driver/neptune/operations/entity_node_ops.py:132-140 | graphiti_core/driver/neptune/operations/episode_node_ops.py:107-114 | graphiti_core/driver/neptune/operations/episode_node_ops.py:123-131 | graphiti_core/driver/neptune/operations/episodic_edge_ops.py:103-111 | graphiti_core/driver/neptune/operations/has_episode_edge_ops.py:97-105 | graphiti_core/driver/neptune/operations/next_episode_edge_ops.py:97-105 | graphiti_core/driver/neptune/operations/saga_node_ops.py:98-105 | graphiti_core/driver/neptune/operations/saga_node_ops.py:114-122 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/community_edge_ops.py\u0060 and \u0060graphiti_core/driver/falkordb/operations/entity_edge_ops.py\u0060 as WHOLE FILES: this scan already matched 3 separate duplicated blocks between them, totalling at least 44 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. If that is what happened, the fix is to keep one copy and have the other call it (or delete it), which resolves this row and its siblings together \u2014 extracting one helper per block leaves the fork in place."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/community_edge_ops.py"},"region":{"startLine":89}}}],"partialFingerprints":{"codehealthFindingId/v1":"6dd21838f3cef505a8602f2a17e14dd4c18fd36704235bc94c464890d1dcf16f"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (12 lines \u00D7 3): graphiti_core/driver/falkordb/operations/community_edge_ops.py:104-115 | graphiti_core/driver/kuzu/operations/community_edge_ops.py:104-115 | graphiti_core/driver/neptune/operations/community_edge_ops.py:104-115 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/community_edge_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/community_edge_ops.py\u0060 as WHOLE FILES: this scan already matched 6 separate duplicated blocks between them, totalling at least 76 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. The \u0060return\u0060 at the foot of the matched lines is the enclosing body\u0027s own terminal exit, not an early one: it moves with them unchanged, and each site calls the extracted unit from the position that \u0060return\u0060 occupied \u2014 no decision has to be handed back and re-acted on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/community_edge_ops.py"},"region":{"startLine":104}}}],"partialFingerprints":{"codehealthFindingId/v1":"e387a67a49676111fe61ea9c98d18751b8ed460b3057b37c2544a0ac2d30b2ce"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (10 lines \u00D7 3): graphiti_core/driver/falkordb/operations/community_edge_ops.py:122-131 | graphiti_core/driver/kuzu/operations/community_edge_ops.py:122-131 | graphiti_core/driver/neptune/operations/community_edge_ops.py:122-131 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/community_edge_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/community_edge_ops.py\u0060 as WHOLE FILES: this scan already matched 6 separate duplicated blocks between them, totalling at least 76 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. The \u0060return\u0060 at the foot of the matched lines is the enclosing body\u0027s own terminal exit, not an early one: it moves with them unchanged, and each site calls the extracted unit from the position that \u0060return\u0060 occupied \u2014 no decision has to be handed back and re-acted on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/community_edge_ops.py"},"region":{"startLine":122}}}],"partialFingerprints":{"codehealthFindingId/v1":"9065266c5df7ef0bb62ab71f954b3f18cb5d92ef0c73ef20a14fdfaf669c3b91"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (12 lines \u00D7 2): graphiti_core/driver/falkordb/operations/community_node_ops.py:126-137 | graphiti_core/driver/kuzu/operations/community_node_ops.py:125-136 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/community_node_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/community_node_ops.py\u0060 as WHOLE FILES: this scan already matched 6 separate duplicated blocks between them, totalling at least 77 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. The \u0060return\u0060 at the foot of the matched lines is the enclosing body\u0027s own terminal exit, not an early one: it moves with them unchanged, and each site calls the extracted unit from the position that \u0060return\u0060 occupied \u2014 no decision has to be handed back and re-acted on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/community_node_ops.py"},"region":{"startLine":126}}}],"partialFingerprints":{"codehealthFindingId/v1":"a217fe2b4afea8ef02b2fd6257a7cd1e2fd9513f25c0e7929349fd4281b7566f"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (10 lines \u00D7 2): graphiti_core/driver/falkordb/operations/community_node_ops.py:144-153 | graphiti_core/driver/kuzu/operations/community_node_ops.py:143-152 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/community_node_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/community_node_ops.py\u0060 as WHOLE FILES: this scan already matched 6 separate duplicated blocks between them, totalling at least 77 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. The \u0060return\u0060 at the foot of the matched lines is the enclosing body\u0027s own terminal exit, not an early one: it moves with them unchanged, and each site calls the extracted unit from the position that \u0060return\u0060 occupied \u2014 no decision has to be handed back and re-acted on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/community_node_ops.py"},"region":{"startLine":144}}}],"partialFingerprints":{"codehealthFindingId/v1":"d05db765e581d7ea964fd042c25a3e85580049969c58a1d5a5c34aa3b5f4cca8"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (8 lines \u00D7 6): graphiti_core/driver/falkordb/operations/community_node_ops.py:215-222 | graphiti_core/driver/falkordb/operations/entity_node_ops.py:236-243 | graphiti_core/driver/kuzu/operations/community_node_ops.py:191-198 | graphiti_core/driver/kuzu/operations/entity_node_ops.py:211-218 | graphiti_core/driver/neptune/operations/community_node_ops.py:204-211 | graphiti_core/driver/neptune/operations/entity_node_ops.py:207-214 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/community_node_ops.py\u0060 and \u0060graphiti_core/driver/falkordb/operations/entity_node_ops.py\u0060 as WHOLE FILES: this scan already matched 4 separate duplicated blocks between them, totalling at least 52 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. If that is what happened, the fix is to keep one copy and have the other call it (or delete it), which resolves this row and its siblings together \u2014 extracting one helper per block leaves the fork in place. Note first that the copies are not typed on the same thing: the declarations holding them bind \u0060node\u0060 to \u0060CommunityNode\u0060 in one and \u0060EntityNode\u0060 in another, and the duplicated lines use it. The extracted unit therefore needs a parameter type that fits BOTH \u2014 their common supertype where they have one, or a new abstraction over them where they do not \u2014 and settling that is the step that comes BEFORE the extraction above. Where the two types are deliberately unrelated, the duplication is the price of that separation and the honest resolution is to record the decision rather than to extract."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/community_node_ops.py"},"region":{"startLine":215}}}],"partialFingerprints":{"codehealthFindingId/v1":"fdcfe4af905c06cdc8176dadd884b71c5cce8d87ff351de2820588ddd064f174"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (8 lines \u00D7 3): graphiti_core/driver/falkordb/operations/entity_edge_ops.py:250-257 | graphiti_core/driver/kuzu/operations/entity_edge_ops.py:206-213 | graphiti_core/driver/neptune/operations/entity_edge_ops.py:227-234 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/entity_edge_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/entity_edge_ops.py\u0060 as WHOLE FILES: this scan already matched 8 separate duplicated blocks between them, totalling at least 77 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/entity_edge_ops.py"},"region":{"startLine":250}}}],"partialFingerprints":{"codehealthFindingId/v1":"76fc317d29a3e3b28f7e51dc93f8417472d2b69728aa77399b886cf52d5d3346"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (12 lines \u00D7 2): graphiti_core/driver/falkordb/operations/episode_node_ops.py:141-152 | graphiti_core/driver/kuzu/operations/episode_node_ops.py:129-140 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/episode_node_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/episode_node_ops.py\u0060 as WHOLE FILES: this scan already matched 9 separate duplicated blocks between them, totalling at least 197 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. The \u0060return\u0060 at the foot of the matched lines is the enclosing body\u0027s own terminal exit, not an early one: it moves with them unchanged, and each site calls the extracted unit from the position that \u0060return\u0060 occupied \u2014 no decision has to be handed back and re-acted on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/episode_node_ops.py"},"region":{"startLine":141}}}],"partialFingerprints":{"codehealthFindingId/v1":"eaee563b190cb10353334a58e61af4f84e4edf305fd40e3cd95819f92a58f928"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (9 lines \u00D7 4): graphiti_core/driver/falkordb/operations/episode_node_ops.py:159-168 | graphiti_core/driver/falkordb/operations/episode_node_ops.py:230-238 | graphiti_core/driver/kuzu/operations/episode_node_ops.py:147-156 | graphiti_core/driver/kuzu/operations/episode_node_ops.py:195-203 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/episode_node_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/episode_node_ops.py\u0060 as WHOLE FILES: this scan already matched 9 separate duplicated blocks between them, totalling at least 197 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. The \u0060return\u0060 at the foot of the matched lines is the enclosing body\u0027s own terminal exit, not an early one: it moves with them unchanged, and each site calls the extracted unit from the position that \u0060return\u0060 occupied \u2014 no decision has to be handed back and re-acted on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/episode_node_ops.py"},"region":{"startLine":159}}}],"partialFingerprints":{"codehealthFindingId/v1":"4ac11fa9e65541afdb704dac297c08718095113b7b4569580af4ebb72eb5de39"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (7 lines \u00D7 5): graphiti_core/driver/falkordb/operations/episodic_edge_ops.py:36-42 | graphiti_core/driver/kuzu/operations/episodic_edge_ops.py:36-42 | graphiti_core/driver/neo4j/operations/episodic_edge_ops.py:36-42 | graphiti_core/driver/neptune/operations/episodic_edge_ops.py:36-42 | graphiti_core/edges.py:964-970 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/episodic_edge_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/episodic_edge_ops.py\u0060 as WHOLE FILES: this scan already matched 7 separate duplicated blocks between them, totalling at least 89 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. The matched lines also transfer control out of the body holding them, which cannot survive a move into a called unit unchanged: have the extracted unit return that decision and let each site act on it."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/episodic_edge_ops.py"},"region":{"startLine":36}}}],"partialFingerprints":{"codehealthFindingId/v1":"8eefc4bda463f5ff3cc934e216a9f1df73517aede87512929fcefdaaea3d0b68"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (13 lines \u00D7 4): graphiti_core/driver/falkordb/operations/episodic_edge_ops.py:52-64 | graphiti_core/driver/kuzu/operations/episodic_edge_ops.py:52-64 | graphiti_core/driver/neo4j/operations/episodic_edge_ops.py:52-64 | graphiti_core/driver/neptune/operations/episodic_edge_ops.py:52-64 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/episodic_edge_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/episodic_edge_ops.py\u0060 as WHOLE FILES: this scan already matched 7 separate duplicated blocks between them, totalling at least 89 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/episodic_edge_ops.py"},"region":{"startLine":52}}}],"partialFingerprints":{"codehealthFindingId/v1":"404b3c87c5cdcf595c06ad9486c08337d968ec2241200b5f0dc32098521cf067"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (12 lines \u00D7 3): graphiti_core/driver/falkordb/operations/episodic_edge_ops.py:118-129 | graphiti_core/driver/kuzu/operations/episodic_edge_ops.py:126-137 | graphiti_core/driver/neptune/operations/episodic_edge_ops.py:118-129 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/episodic_edge_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/episodic_edge_ops.py\u0060 as WHOLE FILES: this scan already matched 7 separate duplicated blocks between them, totalling at least 89 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. The \u0060return\u0060 at the foot of the matched lines is the enclosing body\u0027s own terminal exit, not an early one: it moves with them unchanged, and each site calls the extracted unit from the position that \u0060return\u0060 occupied \u2014 no decision has to be handed back and re-acted on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/episodic_edge_ops.py"},"region":{"startLine":118}}}],"partialFingerprints":{"codehealthFindingId/v1":"915af2dab588d3c98f7720ac4c7fdd3c1d2822dfd0f7e5263d3b9ebb5bc724dd"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (10 lines \u00D7 3): graphiti_core/driver/falkordb/operations/episodic_edge_ops.py:136-145 | graphiti_core/driver/kuzu/operations/episodic_edge_ops.py:144-153 | graphiti_core/driver/neptune/operations/episodic_edge_ops.py:136-145 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/episodic_edge_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/episodic_edge_ops.py\u0060 as WHOLE FILES: this scan already matched 7 separate duplicated blocks between them, totalling at least 89 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. The \u0060return\u0060 at the foot of the matched lines is the enclosing body\u0027s own terminal exit, not an early one: it moves with them unchanged, and each site calls the extracted unit from the position that \u0060return\u0060 occupied \u2014 no decision has to be handed back and re-acted on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/episodic_edge_ops.py"},"region":{"startLine":136}}}],"partialFingerprints":{"codehealthFindingId/v1":"efb043c76a9187eec6f72dbe478516e466c3d6b7dfbc262502fc7879833f5a17"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (12 lines \u00D7 3): graphiti_core/driver/falkordb/operations/graph_ops.py:42-54 | graphiti_core/driver/neo4j/operations/graph_ops.py:42-53 | graphiti_core/driver/neptune/operations/graph_ops.py:48-59 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/graph_ops.py\u0060 and \u0060graphiti_core/driver/neo4j/operations/graph_ops.py\u0060 as WHOLE FILES: this scan already matched 4 separate duplicated blocks between them, totalling at least 90 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/graph_ops.py"},"region":{"startLine":42}}}],"partialFingerprints":{"codehealthFindingId/v1":"3572d0c0446e7a763f8bf8543dffd8fb9aaef21be8a94708660976b7c02a2f08"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (21 lines \u00D7 3): graphiti_core/driver/falkordb/operations/graph_ops.py:185-205 | graphiti_core/driver/kuzu/operations/graph_ops.py:159-180 | graphiti_core/driver/neo4j/operations/graph_ops.py:154-174 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/graph_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/graph_ops.py\u0060 as WHOLE FILES: this scan already matched 4 separate duplicated blocks between them, totalling at least 90 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. The matched lines also transfer control out of the body holding them, which cannot survive a move into a called unit unchanged: have the extracted unit return that decision and let each site act on it."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/graph_ops.py"},"region":{"startLine":185}}}],"partialFingerprints":{"codehealthFindingId/v1":"310bcc0c22d2101e97ae0569a4e64a97eb5993d8563325e17203d233d5679aff"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (13 lines \u00D7 2): graphiti_core/driver/falkordb/operations/graph_ops.py:231-243 | graphiti_core/driver/kuzu/operations/graph_ops.py:206-218 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/graph_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/graph_ops.py\u0060 as WHOLE FILES: this scan already matched 4 separate duplicated blocks between them, totalling at least 90 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. The \u0060return\u0060 at the foot of the matched lines is the enclosing body\u0027s own terminal exit, not an early one: it moves with them unchanged, and each site calls the extracted unit from the position that \u0060return\u0060 occupied \u2014 no decision has to be handed back and re-acted on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/graph_ops.py"},"region":{"startLine":231}}}],"partialFingerprints":{"codehealthFindingId/v1":"ae044b71caa1db73cda9a1d43db7d3f98b5c436cd11a00bc30ccd1c2dedfb95b"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (7 lines \u00D7 5): graphiti_core/driver/falkordb/operations/has_episode_edge_ops.py:35-41 | graphiti_core/driver/kuzu/operations/has_episode_edge_ops.py:34-40 | graphiti_core/driver/neo4j/operations/has_episode_edge_ops.py:34-40 | graphiti_core/driver/neptune/operations/has_episode_edge_ops.py:34-40 | graphiti_core/edges.py:1024-1030 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/has_episode_edge_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/has_episode_edge_ops.py\u0060 as WHOLE FILES: this scan already matched 7 separate duplicated blocks between them, totalling at least 89 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. The matched lines also transfer control out of the body holding them, which cannot survive a move into a called unit unchanged: have the extracted unit return that decision and let each site act on it."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/has_episode_edge_ops.py"},"region":{"startLine":35}}}],"partialFingerprints":{"codehealthFindingId/v1":"f317629f2900559a475eabed29654e723134e0857899f590f56bf9cb780dc5a4"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (13 lines \u00D7 4): graphiti_core/driver/falkordb/operations/has_episode_edge_ops.py:51-63 | graphiti_core/driver/kuzu/operations/has_episode_edge_ops.py:50-62 | graphiti_core/driver/neo4j/operations/has_episode_edge_ops.py:50-62 | graphiti_core/driver/neptune/operations/has_episode_edge_ops.py:50-62 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/has_episode_edge_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/has_episode_edge_ops.py\u0060 as WHOLE FILES: this scan already matched 7 separate duplicated blocks between them, totalling at least 89 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/has_episode_edge_ops.py"},"region":{"startLine":51}}}],"partialFingerprints":{"codehealthFindingId/v1":"5b828d844957e9b9b57557871d890252f07f99dcf5d660c4c952f2e311957ab4"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (12 lines \u00D7 3): graphiti_core/driver/falkordb/operations/has_episode_edge_ops.py:113-124 | graphiti_core/driver/kuzu/operations/has_episode_edge_ops.py:112-123 | graphiti_core/driver/neptune/operations/has_episode_edge_ops.py:112-123 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/has_episode_edge_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/has_episode_edge_ops.py\u0060 as WHOLE FILES: this scan already matched 7 separate duplicated blocks between them, totalling at least 89 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. The \u0060return\u0060 at the foot of the matched lines is the enclosing body\u0027s own terminal exit, not an early one: it moves with them unchanged, and each site calls the extracted unit from the position that \u0060return\u0060 occupied \u2014 no decision has to be handed back and re-acted on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/has_episode_edge_ops.py"},"region":{"startLine":113}}}],"partialFingerprints":{"codehealthFindingId/v1":"3992938311775bf377e53efc31f74dcc16769189d8405e450469cd25868a12d1"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (10 lines \u00D7 3): graphiti_core/driver/falkordb/operations/has_episode_edge_ops.py:131-140 | graphiti_core/driver/kuzu/operations/has_episode_edge_ops.py:130-139 | graphiti_core/driver/neptune/operations/has_episode_edge_ops.py:130-139 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/has_episode_edge_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/has_episode_edge_ops.py\u0060 as WHOLE FILES: this scan already matched 7 separate duplicated blocks between them, totalling at least 89 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. The \u0060return\u0060 at the foot of the matched lines is the enclosing body\u0027s own terminal exit, not an early one: it moves with them unchanged, and each site calls the extracted unit from the position that \u0060return\u0060 occupied \u2014 no decision has to be handed back and re-acted on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/has_episode_edge_ops.py"},"region":{"startLine":131}}}],"partialFingerprints":{"codehealthFindingId/v1":"5e2a2e9569cc613581742b4c6ecbd6b43e7dab95971cee395992076f243eb952"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (7 lines \u00D7 5): graphiti_core/driver/falkordb/operations/next_episode_edge_ops.py:35-41 | graphiti_core/driver/kuzu/operations/next_episode_edge_ops.py:34-40 | graphiti_core/driver/neo4j/operations/next_episode_edge_ops.py:34-40 | graphiti_core/driver/neptune/operations/next_episode_edge_ops.py:34-40 | graphiti_core/edges.py:1034-1040 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/next_episode_edge_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/next_episode_edge_ops.py\u0060 as WHOLE FILES: this scan already matched 7 separate duplicated blocks between them, totalling at least 89 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. The matched lines also transfer control out of the body holding them, which cannot survive a move into a called unit unchanged: have the extracted unit return that decision and let each site act on it."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/next_episode_edge_ops.py"},"region":{"startLine":35}}}],"partialFingerprints":{"codehealthFindingId/v1":"b835bbd327f3732f4c48b3b979e07dfd356405aed87bafb34bf489c79f6259f8"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (13 lines \u00D7 4): graphiti_core/driver/falkordb/operations/next_episode_edge_ops.py:51-63 | graphiti_core/driver/kuzu/operations/next_episode_edge_ops.py:50-62 | graphiti_core/driver/neo4j/operations/next_episode_edge_ops.py:50-62 | graphiti_core/driver/neptune/operations/next_episode_edge_ops.py:50-62 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/next_episode_edge_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/next_episode_edge_ops.py\u0060 as WHOLE FILES: this scan already matched 7 separate duplicated blocks between them, totalling at least 89 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/next_episode_edge_ops.py"},"region":{"startLine":51}}}],"partialFingerprints":{"codehealthFindingId/v1":"5cc7e70ffa870223f87b92b6fc50d23f5b3ea7b6f6662a10f88a6ac051682d89"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (12 lines \u00D7 3): graphiti_core/driver/falkordb/operations/next_episode_edge_ops.py:113-124 | graphiti_core/driver/kuzu/operations/next_episode_edge_ops.py:112-123 | graphiti_core/driver/neptune/operations/next_episode_edge_ops.py:112-123 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/next_episode_edge_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/next_episode_edge_ops.py\u0060 as WHOLE FILES: this scan already matched 7 separate duplicated blocks between them, totalling at least 89 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. The \u0060return\u0060 at the foot of the matched lines is the enclosing body\u0027s own terminal exit, not an early one: it moves with them unchanged, and each site calls the extracted unit from the position that \u0060return\u0060 occupied \u2014 no decision has to be handed back and re-acted on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/next_episode_edge_ops.py"},"region":{"startLine":113}}}],"partialFingerprints":{"codehealthFindingId/v1":"88004457cea23dc93c93f28063a6d70b33c8fb8e93eeb93a7827909d3d18637b"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (10 lines \u00D7 3): graphiti_core/driver/falkordb/operations/next_episode_edge_ops.py:131-140 | graphiti_core/driver/kuzu/operations/next_episode_edge_ops.py:130-139 | graphiti_core/driver/neptune/operations/next_episode_edge_ops.py:130-139 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/next_episode_edge_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/next_episode_edge_ops.py\u0060 as WHOLE FILES: this scan already matched 7 separate duplicated blocks between them, totalling at least 89 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. The \u0060return\u0060 at the foot of the matched lines is the enclosing body\u0027s own terminal exit, not an early one: it moves with them unchanged, and each site calls the extracted unit from the position that \u0060return\u0060 occupied \u2014 no decision has to be handed back and re-acted on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/next_episode_edge_ops.py"},"region":{"startLine":131}}}],"partialFingerprints":{"codehealthFindingId/v1":"9a06fd4ae59cb5683f91477ddeb2a7bf97e4fe169b86c82e9cc622d61e34d7c4"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (6 lines \u00D7 4): graphiti_core/driver/falkordb/operations/saga_node_ops.py:32-37 | graphiti_core/driver/kuzu/operations/saga_node_ops.py:32-37 | graphiti_core/driver/neo4j/operations/saga_node_ops.py:32-37 | graphiti_core/driver/neptune/operations/saga_node_ops.py:35-40 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/saga_node_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/saga_node_ops.py\u0060 as WHOLE FILES: this scan already matched 6 separate duplicated blocks between them, totalling at least 75 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. The matched lines also transfer control out of the body holding them, which cannot survive a move into a called unit unchanged: have the extracted unit return that decision and let each site act on it."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/saga_node_ops.py"},"region":{"startLine":32}}}],"partialFingerprints":{"codehealthFindingId/v1":"11d84756c2cef50bb1767a4414cf5e2fa30c12f3e76ba2690fa8fd64bca060b0"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (12 lines \u00D7 2): graphiti_core/driver/falkordb/operations/saga_node_ops.py:126-137 | graphiti_core/driver/kuzu/operations/saga_node_ops.py:129-140 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/saga_node_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/saga_node_ops.py\u0060 as WHOLE FILES: this scan already matched 6 separate duplicated blocks between them, totalling at least 75 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. The \u0060return\u0060 at the foot of the matched lines is the enclosing body\u0027s own terminal exit, not an early one: it moves with them unchanged, and each site calls the extracted unit from the position that \u0060return\u0060 occupied \u2014 no decision has to be handed back and re-acted on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/saga_node_ops.py"},"region":{"startLine":126}}}],"partialFingerprints":{"codehealthFindingId/v1":"cb1498fa7cb22382fa5ed512d02361763e00dc588ac7c57e07654c6a346f3cc3"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (10 lines \u00D7 2): graphiti_core/driver/falkordb/operations/saga_node_ops.py:144-153 | graphiti_core/driver/kuzu/operations/saga_node_ops.py:147-156 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/saga_node_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/saga_node_ops.py\u0060 as WHOLE FILES: this scan already matched 6 separate duplicated blocks between them, totalling at least 75 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. The \u0060return\u0060 at the foot of the matched lines is the enclosing body\u0027s own terminal exit, not an early one: it moves with them unchanged, and each site calls the extracted unit from the position that \u0060return\u0060 occupied \u2014 no decision has to be handed back and re-acted on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/saga_node_ops.py"},"region":{"startLine":144}}}],"partialFingerprints":{"codehealthFindingId/v1":"af82f1b54f730deb8178d3a6fb46bce24c006556fc1c8314dcbc7f12259bac69"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (10 lines \u00D7 8): graphiti_core/driver/neo4j/operations/community_node_ops.py:96-105 | graphiti_core/driver/neo4j/operations/community_node_ops.py:114-124 | graphiti_core/driver/neo4j/operations/entity_node_ops.py:118-127 | graphiti_core/driver/neo4j/operations/entity_node_ops.py:136-146 | graphiti_core/driver/neo4j/operations/episode_node_ops.py:110-119 | graphiti_core/driver/neo4j/operations/episode_node_ops.py:128-138 | graphiti_core/driver/neo4j/operations/saga_node_ops.py:95-104 | graphiti_core/driver/neo4j/operations/saga_node_ops.py:113-123 \u2014 before extracting anything, compare \u0060graphiti_core/driver/neo4j/operations/community_node_ops.py\u0060 and \u0060graphiti_core/driver/neo4j/operations/entity_node_ops.py\u0060 as WHOLE FILES: this scan already matched 3 separate duplicated blocks between them, totalling at least 33 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. If that is what happened, the fix is to keep one copy and have the other call it (or delete it), which resolves this row and its siblings together \u2014 extracting one helper per block leaves the fork in place."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neo4j/operations/community_node_ops.py"},"region":{"startLine":96}}}],"partialFingerprints":{"codehealthFindingId/v1":"66d719447b21dce6c2d13993f7dc888c2ddb57c8a1ac8c443b7e7cd57cadf73b"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (8 lines \u00D7 2): graphiti_core/driver/neo4j/operations/community_node_ops.py:198-205 | graphiti_core/driver/neo4j/operations/entity_node_ops.py:214-221 \u2014 before extracting anything, compare \u0060graphiti_core/driver/neo4j/operations/community_node_ops.py\u0060 and \u0060graphiti_core/driver/neo4j/operations/entity_node_ops.py\u0060 as WHOLE FILES: this scan already matched 3 separate duplicated blocks between them, totalling at least 33 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. If that is what happened, the fix is to keep one copy and have the other call it (or delete it), which resolves this row and its siblings together \u2014 extracting one helper per block leaves the fork in place. Note first that the copies are not typed on the same thing: the declarations holding them bind \u0060node\u0060 to \u0060CommunityNode\u0060 in one and \u0060EntityNode\u0060 in another, and the duplicated lines use it. The extracted unit therefore needs a parameter type that fits BOTH \u2014 their common supertype where they have one, or a new abstraction over them where they do not \u2014 and settling that is the step that comes BEFORE the extraction above. Where the two types are deliberately unrelated, the duplication is the price of that separation and the honest resolution is to record the decision rather than to extract."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neo4j/operations/community_node_ops.py"},"region":{"startLine":198}}}],"partialFingerprints":{"codehealthFindingId/v1":"91b60e3c5e4df5566c1d5b1e48e1a5e6c97bdc5e90f3ce301907a129df5e2407"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (7 lines \u00D7 2): graphiti_core/llm_client/gemini_client.py:170-176 | graphiti_core/llm_client/openai_base_client.py:118-124 \u2014 the copies sit in sibling files of one directory, so a shared home is within easy reach: extract the block into a single shared function the call sites can all reach \u2014 a file they already depend on, or a new one alongside them \u2014 and call it from both call sites, so a change lands once. The matched lines also transfer control out of the body holding them, which cannot survive a move into a called unit unchanged: have the extracted unit return that decision and let each site act on it."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/gemini_client.py"},"region":{"startLine":170}}}],"partialFingerprints":{"codehealthFindingId/v1":"d2e0cd8b9864010c4e8bc00cfc176b783b9ba90952496804fa33db46afa3a8ef"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (9 lines \u00D7 4): graphiti_core/driver/falkordb/operations/entity_edge_ops.py:136-144 | graphiti_core/driver/falkordb/operations/entity_node_ops.py:153-161 | graphiti_core/driver/kuzu/operations/entity_edge_ops.py:115-123 | graphiti_core/driver/neptune/operations/entity_edge_ops.py:136-144 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/entity_edge_ops.py\u0060 and \u0060graphiti_core/driver/falkordb/operations/entity_node_ops.py\u0060 as WHOLE FILES: this scan already matched 4 separate duplicated blocks between them, totalling at least 39 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. If that is what happened, the fix is to keep one copy and have the other call it (or delete it), which resolves this row and its siblings together \u2014 extracting one helper per block leaves the fork in place. The \u0060return\u0060 at the foot of the matched lines is the enclosing body\u0027s own terminal exit, not an early one: it moves with them unchanged, and each site calls the extracted unit from the position that \u0060return\u0060 occupied \u2014 no decision has to be handed back and re-acted on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/entity_edge_ops.py"},"region":{"startLine":136}}}],"partialFingerprints":{"codehealthFindingId/v1":"29eed81d8d4098822e3677d7098204d742443825cb6a7d13f124bcf2df3a46cb"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (9 lines \u00D7 3): graphiti_core/driver/falkordb/operations/entity_edge_ops.py:151-159 | graphiti_core/driver/kuzu/operations/entity_edge_ops.py:130-138 | graphiti_core/driver/neptune/operations/entity_edge_ops.py:151-159 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/entity_edge_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/entity_edge_ops.py\u0060 as WHOLE FILES: this scan already matched 8 separate duplicated blocks between them, totalling at least 77 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. The matched lines also transfer control out of the body holding them, which cannot survive a move into a called unit unchanged: have the extracted unit return that decision and let each site act on it."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/entity_edge_ops.py"},"region":{"startLine":151}}}],"partialFingerprints":{"codehealthFindingId/v1":"f64a0ec459610774446ec06494ab365474538e4108f028c90c5c13ee5eb099c1"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (10 lines \u00D7 3): graphiti_core/driver/falkordb/operations/entity_edge_ops.py:222-231 | graphiti_core/driver/kuzu/operations/entity_edge_ops.py:178-187 | graphiti_core/driver/neptune/operations/entity_edge_ops.py:199-208 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/entity_edge_ops.py\u0060 and \u0060graphiti_core/driver/kuzu/operations/entity_edge_ops.py\u0060 as WHOLE FILES: this scan already matched 8 separate duplicated blocks between them, totalling at least 77 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. The \u0060return\u0060 at the foot of the matched lines is the enclosing body\u0027s own terminal exit, not an early one: it moves with them unchanged, and each site calls the extracted unit from the position that \u0060return\u0060 occupied \u2014 no decision has to be handed back and re-acted on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/entity_edge_ops.py"},"region":{"startLine":222}}}],"partialFingerprints":{"codehealthFindingId/v1":"f187b4bec10d2c64a64668ea6a723f56fff50f5907a29aeffe5a3f90d00ce083"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (6 lines \u00D7 4): graphiti_core/driver/falkordb/operations/entity_edge_ops.py:238-243 | graphiti_core/driver/falkordb/operations/entity_node_ops.py:168-174 | graphiti_core/driver/kuzu/operations/entity_edge_ops.py:194-199 | graphiti_core/driver/neptune/operations/entity_edge_ops.py:215-220 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/entity_edge_ops.py\u0060 and \u0060graphiti_core/driver/falkordb/operations/entity_node_ops.py\u0060 as WHOLE FILES: this scan already matched 4 separate duplicated blocks between them, totalling at least 39 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. If that is what happened, the fix is to keep one copy and have the other call it (or delete it), which resolves this row and its siblings together \u2014 extracting one helper per block leaves the fork in place. The \u0060return\u0060 at the foot of the matched lines is the enclosing body\u0027s own terminal exit, not an early one: it moves with them unchanged, and each site calls the extracted unit from the position that \u0060return\u0060 occupied \u2014 no decision has to be handed back and re-acted on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/entity_edge_ops.py"},"region":{"startLine":238}}}],"partialFingerprints":{"codehealthFindingId/v1":"24b8da12766a9bb78ff7b7b44520aceda6896ec85f368d6f2248b52b722eee38"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (6 lines \u00D7 3): graphiti_core/driver/falkordb/operations/episodic_edge_ops.py:73-78 | graphiti_core/driver/neo4j/operations/episodic_edge_ops.py:73-78 | graphiti_core/driver/neptune/operations/episodic_edge_ops.py:73-78 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/episodic_edge_ops.py\u0060 and \u0060graphiti_core/driver/neo4j/operations/episodic_edge_ops.py\u0060 as WHOLE FILES: this scan already matched 5 separate duplicated blocks between them, totalling at least 49 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/episodic_edge_ops.py"},"region":{"startLine":73}}}],"partialFingerprints":{"codehealthFindingId/v1":"8fe9bc065360ccfb6df8996700e9ba9e369fa4c8f58c52822a31be1e25c72b64"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (13 lines \u00D7 2): graphiti_core/driver/falkordb/operations/graph_ops.py:212-224 | graphiti_core/driver/neptune/operations/graph_ops.py:188-200 \u2014 before extracting anything, compare \u0060graphiti_core/driver/falkordb/operations/graph_ops.py\u0060 and \u0060graphiti_core/driver/neptune/operations/graph_ops.py\u0060 as WHOLE FILES: this scan already matched 4 separate duplicated blocks between them, totalling at least 81 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. The \u0060return\u0060 at the foot of the matched lines is the enclosing body\u0027s own terminal exit, not an early one: it moves with them unchanged, and each site calls the extracted unit from the position that \u0060return\u0060 occupied \u2014 no decision has to be handed back and re-acted on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/graph_ops.py"},"region":{"startLine":212}}}],"partialFingerprints":{"codehealthFindingId/v1":"b0a65b29a130084f82b7fbac961a251485025f2235b36775729e7fc6656b4fbd"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (8 lines \u00D7 2): graphiti_core/driver/kuzu/operations/graph_ops.py:63-72 | graphiti_core/driver/neo4j/operations/graph_ops.py:60-67 \u2014 before extracting anything, compare \u0060graphiti_core/driver/kuzu/operations/graph_ops.py\u0060 and \u0060graphiti_core/driver/neo4j/operations/graph_ops.py\u0060 as WHOLE FILES: this scan already matched 4 separate duplicated blocks between them, totalling at least 87 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/kuzu/operations/graph_ops.py"},"region":{"startLine":63}}}],"partialFingerprints":{"codehealthFindingId/v1":"5e51bfd43066efb515e09844d87004aaa7859f7b3bec1dbe5627ae5c1af82c17"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (12 lines \u00D7 4): graphiti_core/driver/neo4j/operations/community_edge_ops.py:104-115 | graphiti_core/driver/neo4j/operations/episodic_edge_ops.py:118-129 | graphiti_core/driver/neo4j/operations/has_episode_edge_ops.py:112-123 | graphiti_core/driver/neo4j/operations/next_episode_edge_ops.py:112-123 \u2014 before extracting anything, compare \u0060graphiti_core/driver/neo4j/operations/community_edge_ops.py\u0060 and \u0060graphiti_core/driver/neo4j/operations/episodic_edge_ops.py\u0060 as WHOLE FILES: this scan already matched 4 separate duplicated blocks between them, totalling at least 45 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. If that is what happened, the fix is to keep one copy and have the other call it (or delete it), which resolves this row and its siblings together \u2014 extracting one helper per block leaves the fork in place. The \u0060return\u0060 at the foot of the matched lines is the enclosing body\u0027s own terminal exit, not an early one: it moves with them unchanged, and each site calls the extracted unit from the position that \u0060return\u0060 occupied \u2014 no decision has to be handed back and re-acted on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neo4j/operations/community_edge_ops.py"},"region":{"startLine":104}}}],"partialFingerprints":{"codehealthFindingId/v1":"1071e9532d0333ae7b0965cce1a92d14237fdd71ac049fc02bc3187348aec671"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (10 lines \u00D7 5): graphiti_core/driver/neo4j/operations/community_edge_ops.py:122-131 | graphiti_core/driver/neo4j/operations/episodic_edge_ops.py:136-145 | graphiti_core/driver/neo4j/operations/has_episode_edge_ops.py:130-139 | graphiti_core/driver/neo4j/operations/next_episode_edge_ops.py:130-139 | graphiti_core/driver/neo4j/operations/saga_node_ops.py:148-157 \u2014 before extracting anything, compare \u0060graphiti_core/driver/neo4j/operations/community_edge_ops.py\u0060 and \u0060graphiti_core/driver/neo4j/operations/episodic_edge_ops.py\u0060 as WHOLE FILES: this scan already matched 4 separate duplicated blocks between them, totalling at least 45 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. If that is what happened, the fix is to keep one copy and have the other call it (or delete it), which resolves this row and its siblings together \u2014 extracting one helper per block leaves the fork in place. The \u0060return\u0060 at the foot of the matched lines is the enclosing body\u0027s own terminal exit, not an early one: it moves with them unchanged, and each site calls the extracted unit from the position that \u0060return\u0060 occupied \u2014 no decision has to be handed back and re-acted on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neo4j/operations/community_edge_ops.py"},"region":{"startLine":122}}}],"partialFingerprints":{"codehealthFindingId/v1":"403dc40513030c75f0423223e047ab22e2181d4a80f177c2452c8ed3cd7f4bcd"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (6 lines \u00D7 2): graphiti_core/driver/neo4j/operations/entity_edge_ops.py:217-222 | graphiti_core/driver/neo4j/operations/entity_node_ops.py:168-174 \u2014 the copies sit in sibling files of one directory, so a shared home is within easy reach: extract the block into a single shared function the call sites can all reach \u2014 a file they already depend on, or a new one alongside them \u2014 and call it from both call sites, so a change lands once. The \u0060return\u0060 at the foot of the matched lines is the enclosing body\u0027s own terminal exit, not an early one: it moves with them unchanged, and each site calls the extracted unit from the position that \u0060return\u0060 occupied \u2014 no decision has to be handed back and re-acted on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neo4j/operations/entity_edge_ops.py"},"region":{"startLine":217}}}],"partialFingerprints":{"codehealthFindingId/v1":"9027cb3ddec1408b37af81247ba682cabec961e3a21bd0b9cf32c87bffd7c940"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (15 lines \u00D7 3): graphiti_core/edges.py:716-730 | graphiti_core/edges.py:849-865 | graphiti_core/nodes.py:947-961 \u2014 before extracting anything, compare \u0060graphiti_core/edges.py\u0060 and \u0060graphiti_core/nodes.py\u0060 as WHOLE FILES: this scan already matched 3 separate duplicated blocks between them, totalling at least 34 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. If that is what happened, the fix is to keep one copy and have the other call it (or delete it), which resolves this row and its siblings together \u2014 extracting one helper per block leaves the fork in place. The matched lines also transfer control out of the body holding them, which cannot survive a move into a called unit unchanged: have the extracted unit return that decision and let each site act on it."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/edges.py"},"region":{"startLine":716}}}],"partialFingerprints":{"codehealthFindingId/v1":"f32c38f37f04402f018ed920fc3ffc4f14071b403e13ba00e51e6c9d1406eb53"}},{"ruleId":"D4","level":"warning","message":{"text":"Edited copy of a member (169 corresponding lines): examples/quickstart/quickstart_falkordb.py:75-252 | examples/quickstart/quickstart_neo4j.py:67-235 \u2014 These two members are one piece of code written twice and then edited apart: 169 consecutive lines correspond almost exactly, broken only by small local edits. Most of that correspondence is NOT reported as duplicated blocks below \u2014 the edits cut it into fragments and only the largest of them clear the block floor, so the rows below understate it. The repair is at the members\u0027 grain \u2014 factor the shared implementation into one the two call with their differences as parameters or as an injected step, or, where the difference is systematic (an extra return value, one transport against another), generate one from the other. Left alone, the next edit has to be made twice and the two will drift further apart."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"examples/quickstart/quickstart_falkordb.py"},"region":{"startLine":75}}}],"partialFingerprints":{"codehealthFindingId/v1":"2d78a1aeb49202677b74d8eedfef42c66d1e722458877bb5fcdb278bef9c9a09"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (66 lines \u00D7 3): examples/quickstart/quickstart_falkordb.py:167-232 | examples/quickstart/quickstart_neo4j.py:150-215 | examples/quickstart/quickstart_neptune.py:163-228 \u2014 before extracting anything, compare \u0060examples/quickstart/quickstart_falkordb.py\u0060 and \u0060examples/quickstart/quickstart_neo4j.py\u0060 as WHOLE FILES: this scan already matched 7 separate duplicated blocks between them, totalling at least 168 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. If that is what happened, the fix is to keep one copy and have the other call it (or delete it), which resolves this row and its siblings together \u2014 extracting one helper per block leaves the fork in place."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"examples/quickstart/quickstart_falkordb.py"},"region":{"startLine":167}}}],"partialFingerprints":{"codehealthFindingId/v1":"372ab7f1dd42fd6c09e5f82f8929a7bafe6c055f00a9762dc1a199791801ed07"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (59 lines \u00D7 2): examples/jev/jev_dedupe_falkordb.py:133-191 | examples/openai_decisions/openai_decisions_dedupe_falkordb.py:136-194 \u2014 \u0060examples/jev/jev_dedupe_falkordb.py\u0060 and \u0060examples/openai_decisions/openai_decisions_dedupe_falkordb.py\u0060 are one unit implemented once per sibling directory, so they are most likely parallel implementations of one contract rather than a copy of each other \u2014 this scan matched 3 separate duplicated blocks between them, totalling at least 87 lines. If both are selected at run time, neither can be retired in favour of the other, and the lines that DIFFER between them are the reason both exist. The move that pays here is to hoist the identical part into a shared location the whole family can reach and give what differs a parameter or a seam, so a change lands once instead of once per sibling; extracting one helper per block leaves every sibling to drift on its own. Read the line range as the matched WINDOW rather than a finished unit: at \u0060examples/jev/jev_dedupe_falkordb.py:133\u0060 it begins part-way through the construct above it, so those exact lines cannot be lifted as they stand \u2014 widen the region to the smallest complete statement or declaration that contains it, and extract that."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"examples/jev/jev_dedupe_falkordb.py"},"region":{"startLine":133}}}],"partialFingerprints":{"codehealthFindingId/v1":"fe53dbc5668e24fad719a7475f5d3a865cd70d77975c09f89320f00a99270ca2"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (32 lines \u00D7 4): examples/azure-openai/azure_openai_neo4j.py:163-194 | examples/quickstart/quickstart_falkordb.py:156-187 | examples/quickstart/quickstart_neo4j.py:139-170 | examples/quickstart/quickstart_neptune.py:152-183 \u2014 before extracting anything, compare \u0060examples/azure-openai/azure_openai_neo4j.py\u0060 and \u0060examples/quickstart/quickstart_falkordb.py\u0060 as WHOLE FILES: this scan already matched 4 separate duplicated blocks between them, totalling at least 71 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. Read the line range as the matched WINDOW rather than a finished unit: at \u0060examples/azure-openai/azure_openai_neo4j.py:163\u0060 it does not close everything it opens, so those exact lines cannot be lifted as they stand \u2014 widen the region to the smallest complete statement or declaration that contains it, and extract that. Note that the copies do not run to the end of the range shown: their LAST lines are different code, not the same code under different names \u2014 the matched region ends inside that line. Extract the lines above it, and read the last line of each site separately. \u2605 These copies have DRIFTED, and that is worth reading before extracting anything: just before the matched lines, \u0060examples/quickstart/quickstart_neptune.py:140\u0060 calls \u0060build_communities\u0060 and \u0060examples/azure-openai/azure_openai_neo4j.py:151\u0060 does not \u2014 after which the two agree again for 4 more lines. One of those two behaviours is the intended one and the other is what a copy-paste left behind, so decide which BEFORE unifying them: extracting the shared part will silently settle it, and if the copy that skips the call is the wrong one, that bug is already live."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"examples/azure-openai/azure_openai_neo4j.py"},"region":{"startLine":163}}}],"partialFingerprints":{"codehealthFindingId/v1":"aa14b32b0bb2a9db93f445a9d49d8162f8e0e197f9de1be835daa03ea4d1dba0"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (22\u201323 lines \u00D7 2): examples/jev/jev_dedupe_falkordb.py:79-100 | examples/openai_decisions/openai_decisions_dedupe_falkordb.py:81-103 \u2014 \u0060examples/jev/jev_dedupe_falkordb.py\u0060 and \u0060examples/openai_decisions/openai_decisions_dedupe_falkordb.py\u0060 are one unit implemented once per sibling directory, so they are most likely parallel implementations of one contract rather than a copy of each other \u2014 this scan matched 3 separate duplicated blocks between them, totalling at least 87 lines. If both are selected at run time, neither can be retired in favour of the other, and the lines that DIFFER between them are the reason both exist. The move that pays here is to hoist the identical part into a shared location the whole family can reach and give what differs a parameter or a seam, so a change lands once instead of once per sibling; extracting one helper per block leaves every sibling to drift on its own. Read the line range as the matched WINDOW rather than a finished unit: at \u0060examples/jev/jev_dedupe_falkordb.py:79\u0060 it does not close everything it opens, so those exact lines cannot be lifted as they stand \u2014 widen the region to the smallest complete statement or declaration that contains it, and extract that."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"examples/jev/jev_dedupe_falkordb.py"},"region":{"startLine":79}}}],"partialFingerprints":{"codehealthFindingId/v1":"1bb688c85e6a39c6276f52d792fb9ecbf605060fef4915bea47cdee6657f1b4b"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (10\u201322 lines \u00D7 4): examples/azure-openai/azure_openai_neo4j.py:146-167 | examples/opentelemetry/otel_stdout_example.py:94-103 | examples/quickstart/quickstart_falkordb.py:140-160 | examples/quickstart/quickstart_neo4j.py:123-143 \u2014 before extracting anything, compare \u0060examples/azure-openai/azure_openai_neo4j.py\u0060 and \u0060examples/quickstart/quickstart_falkordb.py\u0060 as WHOLE FILES: this scan already matched 4 separate duplicated blocks between them, totalling at least 71 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. Read the line range as the matched WINDOW rather than a finished unit: at \u0060examples/azure-openai/azure_openai_neo4j.py:146\u0060 it begins part-way through the construct above it, so those exact lines cannot be lifted as they stand \u2014 widen the region to the smallest complete statement or declaration that contains it, and extract that. Note that the copies do not run to the end of the range shown: their LAST lines are different code, not the same code under different names \u2014 the matched region ends inside that line. Extract the lines above it, and read the last line of each site separately."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"examples/azure-openai/azure_openai_neo4j.py"},"region":{"startLine":146}}}],"partialFingerprints":{"codehealthFindingId/v1":"0f80abe6d866d308f389480c23551ac3cd4a515de353f6132d42a50904eaff83"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (20 lines \u00D7 3): examples/quickstart/quickstart_falkordb.py:233-252 | examples/quickstart/quickstart_neo4j.py:216-235 | examples/quickstart/quickstart_neptune.py:229-248 \u2014 before extracting anything, compare \u0060examples/quickstart/quickstart_falkordb.py\u0060 and \u0060examples/quickstart/quickstart_neo4j.py\u0060 as WHOLE FILES: this scan already matched 7 separate duplicated blocks between them, totalling at least 168 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. If that is what happened, the fix is to keep one copy and have the other call it (or delete it), which resolves this row and its siblings together \u2014 extracting one helper per block leaves the fork in place."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"examples/quickstart/quickstart_falkordb.py"},"region":{"startLine":233}}}],"partialFingerprints":{"codehealthFindingId/v1":"e99b6cbfa5aa3ffa8c31318ee3926616a4c17317d2b1270c9cae9863f6646182"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (8\u201311 lines \u00D7 8): examples/azure-openai/azure_openai_neo4j.py:167-174 | examples/azure-openai/azure_openai_neo4j.py:196-206 | examples/quickstart/quickstart_falkordb.py:160-167 | examples/quickstart/quickstart_falkordb.py:188-198 | examples/quickstart/quickstart_neo4j.py:143-150 | examples/quickstart/quickstart_neo4j.py:171-181 | examples/quickstart/quickstart_neptune.py:156-163 | examples/quickstart/quickstart_neptune.py:184-194 \u2014 before extracting anything, compare \u0060examples/azure-openai/azure_openai_neo4j.py\u0060 and \u0060examples/quickstart/quickstart_falkordb.py\u0060 as WHOLE FILES: this scan already matched 4 separate duplicated blocks between them, totalling at least 71 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. Read the line range as the matched WINDOW rather than a finished unit: at \u0060examples/azure-openai/azure_openai_neo4j.py:196\u0060 it begins part-way through the construct above it, so those exact lines cannot be lifted as they stand \u2014 widen the region to the smallest complete statement or declaration that contains it, and extract that. \u2605 These copies have DRIFTED, and that is worth reading before extracting anything: just before the matched lines, \u0060examples/quickstart/quickstart_neptune.py:140\u0060 calls \u0060build_communities\u0060 and \u0060examples/azure-openai/azure_openai_neo4j.py:151\u0060 does not \u2014 after which the two agree again for 2 more lines. One of those two behaviours is the intended one and the other is what a copy-paste left behind, so decide which BEFORE unifying them: extracting the shared part will silently settle it, and if the copy that skips the call is the wrong one, that bug is already live."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"examples/azure-openai/azure_openai_neo4j.py"},"region":{"startLine":167}}}],"partialFingerprints":{"codehealthFindingId/v1":"050f70ffbe1d4165f7192b5b7a0ab307e74d63c276d7ca2799597abb37903910"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (11 lines \u00D7 4): examples/opentelemetry/otel_stdout_example.py:88-98 | examples/quickstart/quickstart_falkordb.py:134-144 | examples/quickstart/quickstart_neo4j.py:117-127 | examples/quickstart/quickstart_neptune.py:128-138 \u2014 before extracting anything, compare \u0060examples/quickstart/quickstart_falkordb.py\u0060 and \u0060examples/quickstart/quickstart_neo4j.py\u0060 as WHOLE FILES: this scan already matched 7 separate duplicated blocks between them, totalling at least 168 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. If that is what happened, the fix is to keep one copy and have the other call it (or delete it), which resolves this row and its siblings together \u2014 extracting one helper per block leaves the fork in place. Note that the copies do not run to the end of the range shown: their LAST lines are different code, not the same code under different names \u2014 the matched region ends inside that line. Extract the lines above it, and read the last line of each site separately. \u2605 These copies have DRIFTED, and that is worth reading before extracting anything: just after the matched lines, \u0060examples/quickstart/quickstart_neptune.py:140\u0060 calls \u0060build_communities\u0060 and \u0060examples/quickstart/quickstart_falkordb.py:156\u0060 does not \u2014 after which the two agree again for 4 more lines. One of those two behaviours is the intended one and the other is what a copy-paste left behind, so decide which BEFORE unifying them: extracting the shared part will silently settle it, and if the copy that skips the call is the wrong one, that bug is already live."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"examples/opentelemetry/otel_stdout_example.py"},"region":{"startLine":88}}}],"partialFingerprints":{"codehealthFindingId/v1":"49eb93ef9e710c597856960c2a61b68dfeac616d9a39c20ac8229ef7bbaff8f4"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (9\u201310 lines \u00D7 2): examples/azure-openai/azure_openai_neo4j.py:140-149 | examples/gliner2/gliner2_neo4j.py:252-260 \u2014 the copies span different directories, so extracting a shared function means choosing where it lives: put it somewhere both call sites can already reach \u2014 a location they all depend on today, or a new shared one if there is none \u2014 and call it from each site; until then, every change has to be made twice. Read the line range as the matched WINDOW rather than a finished unit: at \u0060examples/azure-openai/azure_openai_neo4j.py:140\u0060 it does not close everything it opens, so those exact lines cannot be lifted as they stand \u2014 widen the region to the smallest complete statement or declaration that contains it, and extract that."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"examples/azure-openai/azure_openai_neo4j.py"},"region":{"startLine":140}}}],"partialFingerprints":{"codehealthFindingId/v1":"294e61f16f1e847558b241e726179bdb32a511b06f0dbe1d7791be2273988c6f"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (6 lines \u00D7 9): examples/azure-openai/azure_openai_neo4j.py:170-175 | examples/azure-openai/azure_openai_neo4j.py:202-207 | examples/gliner2/gliner2_neo4j.py:302-307 | examples/quickstart/quickstart_falkordb.py:163-168 | examples/quickstart/quickstart_falkordb.py:194-199 | examples/quickstart/quickstart_neo4j.py:146-151 | examples/quickstart/quickstart_neo4j.py:177-182 | examples/quickstart/quickstart_neptune.py:159-164 | examples/quickstart/quickstart_neptune.py:190-195 \u2014 before extracting anything, compare \u0060examples/azure-openai/azure_openai_neo4j.py\u0060 and \u0060examples/quickstart/quickstart_falkordb.py\u0060 as WHOLE FILES: this scan already matched 4 separate duplicated blocks between them, totalling at least 71 lines, which is the signature of one file having been copied from the other rather than of a helper waiting to be extracted. The two sit in different directories, so one cannot simply be deleted in favour of the other while both are reached separately: hoist the shared part into a location both already depend on and have each file call it, and retire whichever file turns out to have no caller of its own left. Extracting one helper per block leaves the fork in place. Note that the copies do not run to the end of the range shown: their LAST lines are different code, not the same code under different names \u2014 the matched region ends inside that line. Extract the lines above it, and read the last line of each site separately."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"examples/azure-openai/azure_openai_neo4j.py"},"region":{"startLine":170}}}],"partialFingerprints":{"codehealthFindingId/v1":"f2069ba0d5cf875cbe0d9712485213901b0dd4dcc82c49e810dba62ed46c1c74"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (5 lines \u00D7 3): examples/gliner2/gliner2_neo4j.py:266-270 | examples/jev/jev_dedupe_falkordb.py:147-151 | examples/openai_decisions/openai_decisions_dedupe_falkordb.py:150-154 \u2014 \u0060examples/jev/jev_dedupe_falkordb.py\u0060 and \u0060examples/openai_decisions/openai_decisions_dedupe_falkordb.py\u0060 are one unit implemented once per sibling directory, so they are most likely parallel implementations of one contract rather than a copy of each other \u2014 this scan matched 3 separate duplicated blocks between them, totalling at least 87 lines. If both are selected at run time, neither can be retired in favour of the other, and the lines that DIFFER between them are the reason both exist. The move that pays here is to hoist the identical part into a shared location the whole family can reach and give what differs a parameter or a seam, so a change lands once instead of once per sibling; extracting one helper per block leaves every sibling to drift on its own."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"examples/gliner2/gliner2_neo4j.py"},"region":{"startLine":266}}}],"partialFingerprints":{"codehealthFindingId/v1":"0e779537860dcbbeb9467053792d9b6c5c786f7b3d02708614bc688ab0b7cf51"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (17 lines \u00D7 3): examples/ecommerce/runner.py:41-57 | examples/podcast/podcast_runner.py:42-58 | examples/wizard_of_oz/runner.py:40-56 \u2014 the copies span different directories, so extracting a shared function means choosing where it lives: put it somewhere all 3 call sites can already reach \u2014 a location they all depend on today, or a new shared one if there is none \u2014 and call it from each site; until then, every change has to be made 3 times. The \u0060return\u0060 at the foot of the matched lines is the enclosing body\u0027s own terminal exit, not an early one: it moves with them unchanged, and each site calls the extracted unit from the position that \u0060return\u0060 occupied \u2014 no decision has to be handed back and re-acted on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"examples/ecommerce/runner.py"},"region":{"startLine":41}}}],"partialFingerprints":{"codehealthFindingId/v1":"28047f7e360d6a16f4d831d225d7a629d00ea16c76d196625e5b8cb6338ba335"}},{"ruleId":"D15","level":"warning","message":{"text":"Hotspot: graphiti_core/graphiti.py: graphiti_core/graphiti.py changed 8 times in last 90 days, and the most complex body those changes touched has cyclomatic complexity 21 in Graphiti.summarize_saga at line 590. 6 of those changes were fix/bug commits, so the churn is repair rather than feature work. Before the next change lands here, make sure the area it touches is under test, then split that area out of the file so the following change is smaller than this one \u2014 a file this often edited pays the complexity back every time. Counted over 2026-07-22..2026-10-20, the 90 days ending at the analysed commit. Reproduce with \u0060git log --since=\u00272026-07-22 05:55:01 \u002B00:00\u0027 --until=\u00272026-10-20 05:55:01 \u002B00:00\u0027 --full-history --no-merges -- graphiti_core/graphiti.py\u0060: merges are excluded because a merge re-states changes already counted at their own commits, and history is NOT path-simplified because a change that reached the file through a merged branch is still a change to it. That command counts raw commits and can read HIGHER than this row, which counts a cherry-picked re-land, and a revert together with the commit it undoes, once each \u2014 a difference of several commits on a file whose history was re-landed or reverted inside the window."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/graphiti.py"},"region":{"startLine":590}}}],"partialFingerprints":{"codehealthFindingId/v1":"2fc5e76dc11f7efbce11172191cf2e2af51226bee48e77014a02572a8721bfa6"}},{"ruleId":"D15","level":"warning","message":{"text":"Hotspot: graphiti_core/utils/maintenance/edge_operations.py: graphiti_core/utils/maintenance/edge_operations.py changed 3 times in last 90 days, and the most complex body those changes touched has cyclomatic complexity 38 in edge_operations.extract_edges at line 134. 2 of those changes were fix/bug commits, so the churn is repair rather than feature work. Before the next change lands here, make sure the area it touches is under test, then split that area out of the file so the following change is smaller than this one \u2014 a file this often edited pays the complexity back every time. Counted over 2026-07-22..2026-10-20, the 90 days ending at the analysed commit. Reproduce with \u0060git log --since=\u00272026-07-22 05:55:01 \u002B00:00\u0027 --until=\u00272026-10-20 05:55:01 \u002B00:00\u0027 --full-history --no-merges -- graphiti_core/utils/maintenance/edge_operations.py\u0060: merges are excluded because a merge re-states changes already counted at their own commits, and history is NOT path-simplified because a change that reached the file through a merged branch is still a change to it. That command counts raw commits and can read HIGHER than this row, which counts a cherry-picked re-land, and a revert together with the commit it undoes, once each \u2014 a difference of several commits on a file whose history was re-landed or reverted inside the window."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/edge_operations.py"},"region":{"startLine":134}}}],"partialFingerprints":{"codehealthFindingId/v1":"1a3b9e6ff14ee69b8374c2e50dd497255873fe39f3fd9c380cb59477d18a5e98"}},{"ruleId":"D15","level":"warning","message":{"text":"Hotspot: graphiti_core/utils/maintenance/combined_extraction.py: graphiti_core/utils/maintenance/combined_extraction.py changed 2 times in last 90 days, and the most complex body those changes touched has cyclomatic complexity 48 in combined_extraction.extract_nodes_and_edges at line 58. 1 of those changes was a fix/bug commit, and the other 1 changed it for other reasons \u2014 this file is under both repair and feature pressure. Before the next change lands here, make sure the area it touches is under test, then split that area out of the file so the following change is smaller than this one \u2014 a file this often edited pays the complexity back every time. Counted over 2026-07-22..2026-10-20, the 90 days ending at the analysed commit. Reproduce with \u0060git log --since=\u00272026-07-22 05:55:01 \u002B00:00\u0027 --until=\u00272026-10-20 05:55:01 \u002B00:00\u0027 --full-history --no-merges -- graphiti_core/utils/maintenance/combined_extraction.py\u0060: merges are excluded because a merge re-states changes already counted at their own commits, and history is NOT path-simplified because a change that reached the file through a merged branch is still a change to it. That command counts raw commits and can read HIGHER than this row, which counts a cherry-picked re-land, and a revert together with the commit it undoes, once each \u2014 a difference of several commits on a file whose history was re-landed or reverted inside the window."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/combined_extraction.py"},"region":{"startLine":58}}}],"partialFingerprints":{"codehealthFindingId/v1":"c1702a881eccd011d8173c435dd0b22af9ed6c201f9c86a04870b5997e95d936"}},{"ruleId":"D15","level":"warning","message":{"text":"Hotspot: mcp_server/src/graphiti_mcp_server.py: mcp_server/src/graphiti_mcp_server.py changed 5 times in last 90 days, and the most complex body those changes touched has cyclomatic complexity 15 in GraphitiService.initialize at line 239. 2 of those changes were fix/bug commits, and the other 3 changed it for other reasons \u2014 this file is under both repair and feature pressure. Before the next change lands here, make sure the area it touches is under test, then split that area out of the file so the following change is smaller than this one \u2014 a file this often edited pays the complexity back every time. Counted over 2026-07-22..2026-10-20, the 90 days ending at the analysed commit. Reproduce with \u0060git log --since=\u00272026-07-22 05:55:01 \u002B00:00\u0027 --until=\u00272026-10-20 05:55:01 \u002B00:00\u0027 --full-history --no-merges -- mcp_server/src/graphiti_mcp_server.py\u0060: merges are excluded because a merge re-states changes already counted at their own commits, and history is NOT path-simplified because a change that reached the file through a merged branch is still a change to it. That command counts raw commits and can read HIGHER than this row, which counts a cherry-picked re-land, and a revert together with the commit it undoes, once each \u2014 a difference of several commits on a file whose history was re-landed or reverted inside the window."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":239}}}],"partialFingerprints":{"codehealthFindingId/v1":"f53e45d8d77c201f7b8c0e892c7b86413cec00cee2358c6c6e23507fa1fe758a"}},{"ruleId":"D15","level":"warning","message":{"text":"Hotspot: graphiti_core/utils/bulk_utils.py: graphiti_core/utils/bulk_utils.py changed 3 times in last 90 days, and the most complex body those changes touched has cyclomatic complexity 21 in bulk_utils.dedupe_edges_bulk at line 504. 2 of those changes were fix/bug commits, so the churn is repair rather than feature work. Before the next change lands here, make sure the area it touches is under test, then split that area out of the file so the following change is smaller than this one \u2014 a file this often edited pays the complexity back every time. Counted over 2026-07-22..2026-10-20, the 90 days ending at the analysed commit. Reproduce with \u0060git log --since=\u00272026-07-22 05:55:01 \u002B00:00\u0027 --until=\u00272026-10-20 05:55:01 \u002B00:00\u0027 --full-history --no-merges -- graphiti_core/utils/bulk_utils.py\u0060: merges are excluded because a merge re-states changes already counted at their own commits, and history is NOT path-simplified because a change that reached the file through a merged branch is still a change to it. That command counts raw commits and can read HIGHER than this row, which counts a cherry-picked re-land, and a revert together with the commit it undoes, once each \u2014 a difference of several commits on a file whose history was re-landed or reverted inside the window."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/bulk_utils.py"},"region":{"startLine":504}}}],"partialFingerprints":{"codehealthFindingId/v1":"c6525d57174e1a4c017477fd6f7a3907044d120995837245de117c085c98cc0a"}},{"ruleId":"D15","level":"warning","message":{"text":"Hotspot: graphiti_core/utils/maintenance/node_operations.py: graphiti_core/utils/maintenance/node_operations.py changed 3 times in last 90 days, and the most complex body those changes touched has cyclomatic complexity 19 in node_operations._resolve_with_llm at line 649. 2 of those changes were fix/bug commits, so the churn is repair rather than feature work. Before the next change lands here, make sure the area it touches is under test, then split that area out of the file so the following change is smaller than this one \u2014 a file this often edited pays the complexity back every time. Counted over 2026-07-22..2026-10-20, the 90 days ending at the analysed commit. Reproduce with \u0060git log --since=\u00272026-07-22 05:55:01 \u002B00:00\u0027 --until=\u00272026-10-20 05:55:01 \u002B00:00\u0027 --full-history --no-merges -- graphiti_core/utils/maintenance/node_operations.py\u0060: merges are excluded because a merge re-states changes already counted at their own commits, and history is NOT path-simplified because a change that reached the file through a merged branch is still a change to it. That command counts raw commits and can read HIGHER than this row, which counts a cherry-picked re-land, and a revert together with the commit it undoes, once each \u2014 a difference of several commits on a file whose history was re-landed or reverted inside the window."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/node_operations.py"},"region":{"startLine":649}}}],"partialFingerprints":{"codehealthFindingId/v1":"3e0d671251734995f74e0fb3364e8ed10be9d00b21ea8db13a0b81c8cfb72f8b"}},{"ruleId":"D15","level":"warning","message":{"text":"Hotspot: graphiti_core/search/search.py: graphiti_core/search/search.py changed 2 times in last 90 days, and the most complex body those changes touched has cyclomatic complexity 21 in search.edge_search at line 259. 2 of those changes were fix/bug commits, so the churn is repair rather than feature work. Before the next change lands here, make sure the area it touches is under test, then split that area out of the file so the following change is smaller than this one \u2014 a file this often edited pays the complexity back every time. Counted over 2026-07-22..2026-10-20, the 90 days ending at the analysed commit. Reproduce with \u0060git log --since=\u00272026-07-22 05:55:01 \u002B00:00\u0027 --until=\u00272026-10-20 05:55:01 \u002B00:00\u0027 --full-history --no-merges -- graphiti_core/search/search.py\u0060: merges are excluded because a merge re-states changes already counted at their own commits, and history is NOT path-simplified because a change that reached the file through a merged branch is still a change to it. That command counts raw commits and can read HIGHER than this row, which counts a cherry-picked re-land, and a revert together with the commit it undoes, once each \u2014 a difference of several commits on a file whose history was re-landed or reverted inside the window."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/search/search.py"},"region":{"startLine":259}}}],"partialFingerprints":{"codehealthFindingId/v1":"2f5848ae5c3653e83fe566743a8157dbff07a81a3ee4ce1c2a31264382a25ed5"}},{"ruleId":"D15","level":"warning","message":{"text":"Repeated repair: graphiti_core/driver/falkordb_driver.py: graphiti_core/driver/falkordb_driver.py changed 6 times in last 90 days and 6 of those changes were fix/bug commits, so repair is the majority of this file\u0027s churn. Its max cyclomatic complexity is 9 (its worst body is FalkorDriver.delete_all_indexes at line 292), UNDER the 15 threshold, so this is deliberately not filed as a churn \u00D7 complexity hotspot \u2014 the difficulty here is in the behaviour the file has to get right, not in its control flow, and refactoring it for complexity would be the wrong move. The repairs counted were: \u201Cfix(falkordb): create node fulltext indices with DDL form for FalkorDB 6.x (#1963)\u201D; \u201CLand contributor fixes from #1686 #1689 #1720 #1761 (#1856)\u201D; \u201CConsolidate overlapping backend fixes (#1695)\u201D; \u201Cfix: capture init task ref \u002B cancel on close for Neo4jDriver and FalkorDriver (#1636)\u201D. Each one is a case this code did not handle. Before the next change lands here, check that every one of them is pinned by a test that fails without its fix; where the same area keeps coming back, the durable fix is usually at the interface that keeps being misused rather than at the line that was last corrected. Counted over 2026-07-22..2026-10-20, the 90 days ending at the analysed commit. Reproduce with \u0060git log --since=\u00272026-07-22 05:55:01 \u002B00:00\u0027 --until=\u00272026-10-20 05:55:01 \u002B00:00\u0027 --full-history --no-merges -- graphiti_core/driver/falkordb_driver.py\u0060: merges are excluded because a merge re-states changes already counted at their own commits, and history is NOT path-simplified because a change that reached the file through a merged branch is still a change to it. That command counts raw commits and can read HIGHER than this row, which counts a cherry-picked re-land, and a revert together with the commit it undoes, once each \u2014 a difference of several commits on a file whose history was re-landed or reverted inside the window."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb_driver.py"},"region":{"startLine":292}}}],"partialFingerprints":{"codehealthFindingId/v1":"1044beb1c945adc5440dc2605561faf447df25e669cb3bb33f962d6ad5a37029"}},{"ruleId":"D15","level":"warning","message":{"text":"Repeated repair: graphiti_core/search/search_utils.py: graphiti_core/search/search_utils.py changed 5 times in last 90 days and 5 of those changes were fix/bug commits, so repair is the majority of this file\u0027s churn. Its max cyclomatic complexity is 13 (its worst body is search_utils.edge_similarity_search at line 300), UNDER the 15 threshold, so this is deliberately not filed as a churn \u00D7 complexity hotspot \u2014 the difficulty here is in the behaviour the file has to get right, not in its control flow, and refactoring it for complexity would be the wrong move. The repairs counted were: \u201Cfix(neptune): apply group filters in fulltext search and bind list parameters (#2031)\u201D; \u201CLand contributor fixes from #1686 #1689 #1720 #1761 (#1856)\u201D; \u201Cfix(falkordb): avoid a full :Entity scan per hit in edge_fulltext_search (#1711)\u201D; \u201CConsolidate overlapping backend fixes (#1695)\u201D. Each one is a case this code did not handle. Before the next change lands here, check that every one of them is pinned by a test that fails without its fix; where the same area keeps coming back, the durable fix is usually at the interface that keeps being misused rather than at the line that was last corrected. Counted over 2026-07-22..2026-10-20, the 90 days ending at the analysed commit. Reproduce with \u0060git log --since=\u00272026-07-22 05:55:01 \u002B00:00\u0027 --until=\u00272026-10-20 05:55:01 \u002B00:00\u0027 --full-history --no-merges -- graphiti_core/search/search_utils.py\u0060: merges are excluded because a merge re-states changes already counted at their own commits, and history is NOT path-simplified because a change that reached the file through a merged branch is still a change to it. That command counts raw commits and can read HIGHER than this row, which counts a cherry-picked re-land, and a revert together with the commit it undoes, once each \u2014 a difference of several commits on a file whose history was re-landed or reverted inside the window."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/search/search_utils.py"},"region":{"startLine":300}}}],"partialFingerprints":{"codehealthFindingId/v1":"cb2c761fc2f15f0fba2ee8c2a575ccfc0a64f54b22091ab6696304c87b9ccbaf"}},{"ruleId":"D15","level":"warning","message":{"text":"Repeated repair: graphiti_core/driver/falkordb/operations/graph_ops.py: graphiti_core/driver/falkordb/operations/graph_ops.py changed 4 times in last 90 days and 4 of those changes were fix/bug commits, so repair is the majority of this file\u0027s churn. Its max cyclomatic complexity is 9 (its worst body is FalkorGraphMaintenanceOperations.delete_all_indexes at line 73), UNDER the 15 threshold, so this is deliberately not filed as a churn \u00D7 complexity hotspot \u2014 the difficulty here is in the behaviour the file has to get right, not in its control flow, and refactoring it for complexity would be the wrong move. The repairs counted were: \u201Cfix(falkordb): create node fulltext indices with DDL form for FalkorDB 6.x (#1963)\u201D; \u201Cfix: include Saga nodes when clearing data by group_ids (#1688)\u201D; \u201CConsolidate overlapping backend fixes (#1695)\u201D; \u201Cfix(communities): scope remove_communities to the group_ids being rebuilt (#1658)\u201D. Each one is a case this code did not handle. Before the next change lands here, check that every one of them is pinned by a test that fails without its fix; where the same area keeps coming back, the durable fix is usually at the interface that keeps being misused rather than at the line that was last corrected. Counted over 2026-07-22..2026-10-20, the 90 days ending at the analysed commit. Reproduce with \u0060git log --since=\u00272026-07-22 05:55:01 \u002B00:00\u0027 --until=\u00272026-10-20 05:55:01 \u002B00:00\u0027 --full-history --no-merges -- graphiti_core/driver/falkordb/operations/graph_ops.py\u0060: merges are excluded because a merge re-states changes already counted at their own commits, and history is NOT path-simplified because a change that reached the file through a merged branch is still a change to it. That command counts raw commits and can read HIGHER than this row, which counts a cherry-picked re-land, and a revert together with the commit it undoes, once each \u2014 a difference of several commits on a file whose history was re-landed or reverted inside the window."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/graph_ops.py"},"region":{"startLine":73}}}],"partialFingerprints":{"codehealthFindingId/v1":"4526180ec3ef64de20f3f88e790b484976550e5a9702dbd28018db7ff135ba1d"}},{"ruleId":"D15","level":"warning","message":{"text":"Repeated repair: graphiti_core/utils/maintenance/community_operations.py: graphiti_core/utils/maintenance/community_operations.py changed 4 times in last 90 days and 3 of those changes were fix/bug commits, so repair is the majority of this file\u0027s churn. Its max cyclomatic complexity is 11 (its worst body is community_operations.determine_entity_community at line 331), UNDER the 15 threshold, so this is deliberately not filed as a churn \u00D7 complexity hotspot \u2014 the difficulty here is in the behaviour the file has to get right, not in its control flow, and refactoring it for complexity would be the wrong move. The repairs counted were: \u201CUpstream extraction, dedup, and temporal fixes from the Zep fork (#1964)\u201D; \u201CConsolidate overlapping backend fixes (#1695)\u201D; \u201Cfix(communities): scope remove_communities to the group_ids being rebuilt (#1658)\u201D. Each one is a case this code did not handle. Before the next change lands here, check that every one of them is pinned by a test that fails without its fix; where the same area keeps coming back, the durable fix is usually at the interface that keeps being misused rather than at the line that was last corrected. Counted over 2026-07-22..2026-10-20, the 90 days ending at the analysed commit. Reproduce with \u0060git log --since=\u00272026-07-22 05:55:01 \u002B00:00\u0027 --until=\u00272026-10-20 05:55:01 \u002B00:00\u0027 --full-history --no-merges -- graphiti_core/utils/maintenance/community_operations.py\u0060: merges are excluded because a merge re-states changes already counted at their own commits, and history is NOT path-simplified because a change that reached the file through a merged branch is still a change to it. That command counts raw commits and can read HIGHER than this row, which counts a cherry-picked re-land, and a revert together with the commit it undoes, once each \u2014 a difference of several commits on a file whose history was re-landed or reverted inside the window."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/community_operations.py"},"region":{"startLine":331}}}],"partialFingerprints":{"codehealthFindingId/v1":"ed0533bf4738eafcb0f8ee2e054dd2aa0d530a43a70d4937aa4915ef4cdb5a34"}},{"ruleId":"D15","level":"warning","message":{"text":"Repeated repair: graphiti_core/driver/kuzu/operations/graph_ops.py: graphiti_core/driver/kuzu/operations/graph_ops.py changed 3 times in last 90 days and 3 of those changes were fix/bug commits, so repair is the majority of this file\u0027s churn. Its max cyclomatic complexity is 8 (its worst body is KuzuGraphMaintenanceOperations.get_community_clusters at line 81), UNDER the 15 threshold, so this is deliberately not filed as a churn \u00D7 complexity hotspot \u2014 the difficulty here is in the behaviour the file has to get right, not in its control flow, and refactoring it for complexity would be the wrong move. The repairs counted were: \u201Cfix: include Saga nodes when clearing data by group_ids (#1688)\u201D; \u201CConsolidate overlapping backend fixes (#1695)\u201D; \u201Cfix(communities): scope remove_communities to the group_ids being rebuilt (#1658)\u201D. Each one is a case this code did not handle. Before the next change lands here, check that every one of them is pinned by a test that fails without its fix; where the same area keeps coming back, the durable fix is usually at the interface that keeps being misused rather than at the line that was last corrected. Counted over 2026-07-22..2026-10-20, the 90 days ending at the analysed commit. Reproduce with \u0060git log --since=\u00272026-07-22 05:55:01 \u002B00:00\u0027 --until=\u00272026-10-20 05:55:01 \u002B00:00\u0027 --full-history --no-merges -- graphiti_core/driver/kuzu/operations/graph_ops.py\u0060: merges are excluded because a merge re-states changes already counted at their own commits, and history is NOT path-simplified because a change that reached the file through a merged branch is still a change to it. That command counts raw commits and can read HIGHER than this row, which counts a cherry-picked re-land, and a revert together with the commit it undoes, once each \u2014 a difference of several commits on a file whose history was re-landed or reverted inside the window."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/kuzu/operations/graph_ops.py"},"region":{"startLine":81}}}],"partialFingerprints":{"codehealthFindingId/v1":"f9d15c67716b6844b098be92272fc59c1c76b5bb5a3a889e3b3235325ac6f683"}},{"ruleId":"D15","level":"warning","message":{"text":"Repeated repair: graphiti_core/driver/neo4j/operations/graph_ops.py: graphiti_core/driver/neo4j/operations/graph_ops.py changed 3 times in last 90 days and 3 of those changes were fix/bug commits, so repair is the majority of this file\u0027s churn. Its max cyclomatic complexity is 8 (its worst body is Neo4jGraphMaintenanceOperations.get_community_clusters at line 75), UNDER the 15 threshold, so this is deliberately not filed as a churn \u00D7 complexity hotspot \u2014 the difficulty here is in the behaviour the file has to get right, not in its control flow, and refactoring it for complexity would be the wrong move. The repairs counted were: \u201Cfix: include Saga nodes when clearing data by group_ids (#1688)\u201D; \u201CConsolidate overlapping backend fixes (#1695)\u201D; \u201Cfix(communities): scope remove_communities to the group_ids being rebuilt (#1658)\u201D. Each one is a case this code did not handle. Before the next change lands here, check that every one of them is pinned by a test that fails without its fix; where the same area keeps coming back, the durable fix is usually at the interface that keeps being misused rather than at the line that was last corrected. Counted over 2026-07-22..2026-10-20, the 90 days ending at the analysed commit. Reproduce with \u0060git log --since=\u00272026-07-22 05:55:01 \u002B00:00\u0027 --until=\u00272026-10-20 05:55:01 \u002B00:00\u0027 --full-history --no-merges -- graphiti_core/driver/neo4j/operations/graph_ops.py\u0060: merges are excluded because a merge re-states changes already counted at their own commits, and history is NOT path-simplified because a change that reached the file through a merged branch is still a change to it. That command counts raw commits and can read HIGHER than this row, which counts a cherry-picked re-land, and a revert together with the commit it undoes, once each \u2014 a difference of several commits on a file whose history was re-landed or reverted inside the window."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neo4j/operations/graph_ops.py"},"region":{"startLine":75}}}],"partialFingerprints":{"codehealthFindingId/v1":"7af408eee157c29d7a9962d1f3e81c9c4d8e3c63d9013fb9aaf8fa3ed4d269dd"}},{"ruleId":"D15","level":"warning","message":{"text":"Repeated repair: graphiti_core/driver/neptune/operations/graph_ops.py: graphiti_core/driver/neptune/operations/graph_ops.py changed 3 times in last 90 days and 3 of those changes were fix/bug commits, so repair is the majority of this file\u0027s churn. Its max cyclomatic complexity is 8 (its worst body is NeptuneGraphMaintenanceOperations.get_community_clusters at line 82), UNDER the 15 threshold, so this is deliberately not filed as a churn \u00D7 complexity hotspot \u2014 the difficulty here is in the behaviour the file has to get right, not in its control flow, and refactoring it for complexity would be the wrong move. The repairs counted were: \u201Cfix: include Saga nodes when clearing data by group_ids (#1688)\u201D; \u201CConsolidate overlapping backend fixes (#1695)\u201D; \u201Cfix(communities): scope remove_communities to the group_ids being rebuilt (#1658)\u201D. Each one is a case this code did not handle. Before the next change lands here, check that every one of them is pinned by a test that fails without its fix; where the same area keeps coming back, the durable fix is usually at the interface that keeps being misused rather than at the line that was last corrected. Counted over 2026-07-22..2026-10-20, the 90 days ending at the analysed commit. Reproduce with \u0060git log --since=\u00272026-07-22 05:55:01 \u002B00:00\u0027 --until=\u00272026-10-20 05:55:01 \u002B00:00\u0027 --full-history --no-merges -- graphiti_core/driver/neptune/operations/graph_ops.py\u0060: merges are excluded because a merge re-states changes already counted at their own commits, and history is NOT path-simplified because a change that reached the file through a merged branch is still a change to it. That command counts raw commits and can read HIGHER than this row, which counts a cherry-picked re-land, and a revert together with the commit it undoes, once each \u2014 a difference of several commits on a file whose history was re-landed or reverted inside the window."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neptune/operations/graph_ops.py"},"region":{"startLine":82}}}],"partialFingerprints":{"codehealthFindingId/v1":"e363dc6d877c62639343b913ad8c15da05aa35aea7ed1b254edd7d73ead8b284"}},{"ruleId":"D15","level":"warning","message":{"text":"Repeated repair: graphiti_core/driver/falkordb/operations/search_ops.py: graphiti_core/driver/falkordb/operations/search_ops.py changed 3 times in last 90 days and 3 of those changes were fix/bug commits, so repair is the majority of this file\u0027s churn. Its max cyclomatic complexity is 9 (its worst body is FalkorSearchOperations.node_distance_reranker at line 518), UNDER the 15 threshold, so this is deliberately not filed as a churn \u00D7 complexity hotspot \u2014 the difficulty here is in the behaviour the file has to get right, not in its control flow, and refactoring it for complexity would be the wrong move. The repairs counted were: \u201CConsolidate overlapping backend fixes (#1695)\u201D; \u201Cfix: handle backtick tokens and empty queries in FalkorDB fulltext search (#1475)\u201D; \u201Cfix(falkordb): short-circuit fulltext query when every token was a stopword (#1590)\u201D. Each one is a case this code did not handle. Before the next change lands here, check that every one of them is pinned by a test that fails without its fix; where the same area keeps coming back, the durable fix is usually at the interface that keeps being misused rather than at the line that was last corrected. Counted over 2026-07-22..2026-10-20, the 90 days ending at the analysed commit. Reproduce with \u0060git log --since=\u00272026-07-22 05:55:01 \u002B00:00\u0027 --until=\u00272026-10-20 05:55:01 \u002B00:00\u0027 --full-history --no-merges -- graphiti_core/driver/falkordb/operations/search_ops.py\u0060: merges are excluded because a merge re-states changes already counted at their own commits, and history is NOT path-simplified because a change that reached the file through a merged branch is still a change to it. That command counts raw commits and can read HIGHER than this row, which counts a cherry-picked re-land, and a revert together with the commit it undoes, once each \u2014 a difference of several commits on a file whose history was re-landed or reverted inside the window."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/search_ops.py"},"region":{"startLine":518}}}],"partialFingerprints":{"codehealthFindingId/v1":"70a91f736af7fd823f24fe6a096dc63ac220153cc9323471a65565b22153ebf7"}},{"ruleId":"D16","level":"note","message":{"text":"Off-boarding risk: anonymized user #1: If anonymized user #1 becomes unavailable, 1 significant file(s) lose their only recent owner: mcp_server/src/utils/type_config.py. Pair on, review, or document these before any departure."},"locations":[],"partialFingerprints":{"codehealthFindingId/v1":"dd9b1ce0e12389571cd944d501e09aa1cdad3f955a39bb184b0fa79969b5edf2"}},{"ruleId":"D17","level":"warning","message":{"text":"FixmeComment: # FIXME: Kuzu currently does not support using variables such as \u0060node.fulltext_query\u0060 as an input to FTS, which means \u0060get_relevant_nodes()\u0060 won\u0027t work with Kuzu as the graph driver. \u2014 source code is not a task system: move the work to your tracker and leave a reference instead (e.g. \u0060# REF: #123\u0060), so the task is planned where tasks live and the ticket links back to the code."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/search/search_utils.py"},"region":{"startLine":1293}}}],"partialFingerprints":{"codehealthFindingId/v1":"606e834b2c298bfca844f52b2072ac6c6d62bf052f16ad492a4dd8a199803d36"}},{"ruleId":"D17","level":"warning","message":{"text":"FixmeComment: # FIXME: Kuzu\u0027s UNWIND does not currently support STRUCT[] type properly, so we insert the data one by one instead for now. \u2014 source code is not a task system: move the work to your tracker and leave a reference instead (e.g. \u0060# REF: #123\u0060), so the task is planned where tasks live and the ticket links back to the code."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/bulk_utils.py"},"region":{"startLine":234}}}],"partialFingerprints":{"codehealthFindingId/v1":"505c446ad4db8555dc7efe72f15ee2ddc1e70f5856489dec8f09c9286d95b776"}},{"ruleId":"D17","level":"warning","message":{"text":"TodoComment: # TODO: Add Azure AD authentication support for v1 API compatibility \u2014 source code is not a task system: move the work to your tracker and leave a reference instead (e.g. \u0060# REF: #123\u0060), so the task is planned where tasks live and the ticket links back to the code."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/services/factories.py"},"region":{"startLine":199}}}],"partialFingerprints":{"codehealthFindingId/v1":"74e7c165ffad496e6514d96f4a3973b34b28b0b0c73782305f9f6b1b6cce3aa9"}},{"ruleId":"D17","level":"warning","message":{"text":"TodoComment: # TODO: Add Azure AD authentication support for v1 API compatibility \u2014 source code is not a task system: move the work to your tracker and leave a reference instead (e.g. \u0060# REF: #123\u0060), so the task is planned where tasks live and the ticket links back to the code."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/services/factories.py"},"region":{"startLine":350}}}],"partialFingerprints":{"codehealthFindingId/v1":"00b58f485857d6fce5e3078d1b12cddd0d4b47b93ed66046f21b63276a1c89aa"}},{"ruleId":"D26","level":"note","message":{"text":"Projects may be oversized for their cohesion: 1 of 3 project(s) overshoot their size bounds, lowering Project Cohesion to 3.3/10. The most over is \u0060(repository root)\u0060 (36351 LoC, 368 module-visible types across 30 directories). Review these for cohesion \u2014 draw the boundary inside the module first (group each responsibility into its own package or directory and keep the cross-boundary members non-public), since splitting a published package moves types between packages and breaks consumers."},"locations":[],"partialFingerprints":{"codehealthFindingId/v1":"d5a94650dc74886a0f1f08eb9fb6775f1fc395279ef7e901c970c9d26f767a72"}},{"ruleId":"D29","level":"error","message":{"text":"A security finding was recorded here. Its details are withheld on the public artifact \u2014 ask the repository owner for the full report."},"partialFingerprints":{"codehealthFindingId/v1":"37b9ef30b65273a0ffcb3072144cca5d9e51aa1b238ea9702fa1ae059384a3f9"},"taxa":[{"id":"CWE-494","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},{"id":"A08:2025","toolComponent":{"name":"OWASP Top 10","guid":"7d1f0c2e-4b6a-4e39-9a51-0b2e8c3d5f61"}}]},{"ruleId":"D29","level":"error","message":{"text":"A security finding was recorded here. Its details are withheld on the public artifact \u2014 ask the repository owner for the full report."},"partialFingerprints":{"codehealthFindingId/v1":"d5b8f11381d61a58459ffc7c595fd52d09c03c711894a63a9578043d2338412b"},"taxa":[{"id":"CWE-829","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},{"id":"A08:2025","toolComponent":{"name":"OWASP Top 10","guid":"7d1f0c2e-4b6a-4e39-9a51-0b2e8c3d5f61"}}]},{"ruleId":"D29","level":"error","message":{"text":"A security finding was recorded here. Its details are withheld on the public artifact \u2014 ask the repository owner for the full report."},"partialFingerprints":{"codehealthFindingId/v1":"ae67076d3079e465106215c21ab6e9464408bcce69fddfe6ca4de5e93c10e2d3"},"taxa":[{"id":"CWE-829","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},{"id":"A08:2025","toolComponent":{"name":"OWASP Top 10","guid":"7d1f0c2e-4b6a-4e39-9a51-0b2e8c3d5f61"}}]},{"ruleId":"D29","level":"error","message":{"text":"A security finding was recorded here. Its details are withheld on the public artifact \u2014 ask the repository owner for the full report."},"partialFingerprints":{"codehealthFindingId/v1":"68780de24041b5646f7471734da70fb6e17346031aab23565e6782d17bf457e9"},"taxa":[{"id":"CWE-78","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},{"id":"A05:2025","toolComponent":{"name":"OWASP Top 10","guid":"7d1f0c2e-4b6a-4e39-9a51-0b2e8c3d5f61"}}]},{"ruleId":"D29","level":"error","message":{"text":"A security finding was recorded here. Its details are withheld on the public artifact \u2014 ask the repository owner for the full report."},"partialFingerprints":{"codehealthFindingId/v1":"35cfc9691cf2f4597d8150f41810bc854777c84cc0ac7ba9349d2685387ad81a"},"taxa":[{"id":"CWE-78","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},{"id":"A05:2025","toolComponent":{"name":"OWASP Top 10","guid":"7d1f0c2e-4b6a-4e39-9a51-0b2e8c3d5f61"}}]},{"ruleId":"D29","level":"error","message":{"text":"A security finding was recorded here. Its details are withheld on the public artifact \u2014 ask the repository owner for the full report."},"partialFingerprints":{"codehealthFindingId/v1":"9900dd22a213a748e1d016c7d127a1a637d760bbc808d9b7141376df84f5538d"},"taxa":[{"id":"CWE-78","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},{"id":"A05:2025","toolComponent":{"name":"OWASP Top 10","guid":"7d1f0c2e-4b6a-4e39-9a51-0b2e8c3d5f61"}}]},{"ruleId":"D29","level":"error","message":{"text":"A security finding was recorded here. Its details are withheld on the public artifact \u2014 ask the repository owner for the full report."},"partialFingerprints":{"codehealthFindingId/v1":"6fd8171c1cff73f9b8068c3c2eeed62f02775b38a60c25770d65bdfbcd4cf124"},"taxa":[{"id":"CWE-78","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},{"id":"A05:2025","toolComponent":{"name":"OWASP Top 10","guid":"7d1f0c2e-4b6a-4e39-9a51-0b2e8c3d5f61"}}]},{"ruleId":"D29","level":"error","message":{"text":"A security finding was recorded here. Its details are withheld on the public artifact \u2014 ask the repository owner for the full report."},"partialFingerprints":{"codehealthFindingId/v1":"143a6f0c4d9e92250e371d9ba9f33830e7d98c30b14dad5d8f7fa2ceacbd2f05"},"taxa":[{"id":"CWE-78","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},{"id":"A05:2025","toolComponent":{"name":"OWASP Top 10","guid":"7d1f0c2e-4b6a-4e39-9a51-0b2e8c3d5f61"}}]},{"ruleId":"D29","level":"warning","message":{"text":"A security finding was recorded here. Its details are withheld on the public artifact \u2014 ask the repository owner for the full report."},"partialFingerprints":{"codehealthFindingId/v1":"af4d14255ecea7b6ebf274f44cc64977425e99c92f91581ed2dd6e34292738b7"},"taxa":[{"id":"CWE-522","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},{"id":"CWE-552","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},{"id":"A06:2025","toolComponent":{"name":"OWASP Top 10","guid":"7d1f0c2e-4b6a-4e39-9a51-0b2e8c3d5f61"}}]},{"ruleId":"D29","level":"note","message":{"text":"A security finding was recorded here. Its details are withheld on the public artifact \u2014 ask the repository owner for the full report."},"partialFingerprints":{"codehealthFindingId/v1":"5568f1691344ac4d5da819f3b645a7d9f3b524520acc45d00c719a9baab961f7"},"taxa":[{"id":"CWE-939","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}}]},{"ruleId":"D29","level":"note","message":{"text":"A security finding was recorded here. Its details are withheld on the public artifact \u2014 ask the repository owner for the full report."},"partialFingerprints":{"codehealthFindingId/v1":"f6afe1ebb699310505cb90b630d923c45b0f478b1f9948c106d26f83248ce8e5"},"taxa":[{"id":"CWE-939","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}}]},{"ruleId":"D30","level":"error","message":{"text":"A security finding was recorded here. Its details are withheld on the public artifact \u2014 ask the repository owner for the full report."},"partialFingerprints":{"codehealthFindingId/v1":"1e9a30364fe27588d78ab951a87538150608978a0cc8e90bc56d7a617ccfeeaa"},"properties":{"dependency":{"package":"aiohttp","version":"3.14.3","advisory":"[GHSA redacted]","aliases":["[CVE redacted]","[CVE redacted]","[GHSA redacted]","[GHSA redacted]"],"reachability":{"kind":"not-imported"}}}},{"ruleId":"D30","level":"error","message":{"text":"A security finding was recorded here. Its details are withheld on the public artifact \u2014 ask the repository owner for the full report."},"partialFingerprints":{"codehealthFindingId/v1":"575e71672c9901048ef5423bc0fd582e533de9919d1255fe21985ccc907301c5"},"properties":{"dependency":{"package":"jupyter-server","version":"2.20.0","advisory":"[GHSA redacted]","aliases":["[CVE redacted]","PYSEC-2026-4054"],"reachability":{"kind":"unknown"}}}},{"ruleId":"D31","level":"error","message":{"text":"A security finding was recorded here. Its details are withheld on the public artifact \u2014 ask the repository owner for the full report."},"partialFingerprints":{"codehealthFindingId/v1":"cbe53bf54818678b03b37b9cd5094dbb4e8886c78d5f7a642430137b4c336172"},"taxa":[{"id":"CWE-494","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},{"id":"A08:2025","toolComponent":{"name":"OWASP Top 10","guid":"7d1f0c2e-4b6a-4e39-9a51-0b2e8c3d5f61"}},{"id":"CICD-SEC-3","toolComponent":{"name":"OWASP Top 10 CI/CD Security Risks","guid":"3c9b2a6e-5d14-4f7b-8e02-6a1d9c4b7f35"}}]},{"ruleId":"D31","level":"error","message":{"text":"A security finding was recorded here. Its details are withheld on the public artifact \u2014 ask the repository owner for the full report."},"partialFingerprints":{"codehealthFindingId/v1":"a572fbba69b98fd48a44a85b2d036721b02ddc98157b91249cc3e5cf4ca1ba59"},"taxa":[{"id":"CWE-494","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},{"id":"A08:2025","toolComponent":{"name":"OWASP Top 10","guid":"7d1f0c2e-4b6a-4e39-9a51-0b2e8c3d5f61"}},{"id":"CICD-SEC-3","toolComponent":{"name":"OWASP Top 10 CI/CD Security Risks","guid":"3c9b2a6e-5d14-4f7b-8e02-6a1d9c4b7f35"}}]},{"ruleId":"D31","level":"error","message":{"text":"A security finding was recorded here. Its details are withheld on the public artifact \u2014 ask the repository owner for the full report."},"partialFingerprints":{"codehealthFindingId/v1":"e83b518b7f52f2dcaa5ae6040062bb11c1eb9b8af4b47d50866778e1f843e3bd"},"taxa":[{"id":"CWE-1357","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},{"id":"CWE-353","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},{"id":"A03:2025","toolComponent":{"name":"OWASP Top 10","guid":"7d1f0c2e-4b6a-4e39-9a51-0b2e8c3d5f61"}}]},{"ruleId":"D31","level":"error","message":{"text":"A security finding was recorded here. Its details are withheld on the public artifact \u2014 ask the repository owner for the full report."},"partialFingerprints":{"codehealthFindingId/v1":"0df214bd573ccedffd24b1ef051cb34fb464ec34bbec9d540c2d719668e18d0f"}},{"ruleId":"D31","level":"error","message":{"text":"A security finding was recorded here. Its details are withheld on the public artifact \u2014 ask the repository owner for the full report."},"partialFingerprints":{"codehealthFindingId/v1":"b4a66aef6f18a4acf7b1eaff15dad5d10a11ea8b255be4c8dc82097353b3c76d"},"taxa":[{"id":"CWE-494","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},{"id":"A08:2025","toolComponent":{"name":"OWASP Top 10","guid":"7d1f0c2e-4b6a-4e39-9a51-0b2e8c3d5f61"}},{"id":"CICD-SEC-3","toolComponent":{"name":"OWASP Top 10 CI/CD Security Risks","guid":"3c9b2a6e-5d14-4f7b-8e02-6a1d9c4b7f35"}}]},{"ruleId":"D31","level":"error","message":{"text":"A security finding was recorded here. Its details are withheld on the public artifact \u2014 ask the repository owner for the full report."},"partialFingerprints":{"codehealthFindingId/v1":"c7f2d05871d145f232885a5617056588c4855616d43d504eb1d984b02817fee0"},"taxa":[{"id":"CWE-494","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},{"id":"A08:2025","toolComponent":{"name":"OWASP Top 10","guid":"7d1f0c2e-4b6a-4e39-9a51-0b2e8c3d5f61"}},{"id":"CICD-SEC-3","toolComponent":{"name":"OWASP Top 10 CI/CD Security Risks","guid":"3c9b2a6e-5d14-4f7b-8e02-6a1d9c4b7f35"}}]},{"ruleId":"D31","level":"error","message":{"text":"A security finding was recorded here. Its details are withheld on the public artifact \u2014 ask the repository owner for the full report."},"partialFingerprints":{"codehealthFindingId/v1":"15aa24f8b91e6814a72c641806c27f8b2a240e0fa12fd1db442bc30b6b2fc9a4"}},{"ruleId":"D31","level":"error","message":{"text":"A security finding was recorded here. Its details are withheld on the public artifact \u2014 ask the repository owner for the full report."},"partialFingerprints":{"codehealthFindingId/v1":"783e4efc427420a6571a31180017cd2dedf434bf3622e1b7092cf48381c0763f"},"taxa":[{"id":"CWE-494","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},{"id":"A08:2025","toolComponent":{"name":"OWASP Top 10","guid":"7d1f0c2e-4b6a-4e39-9a51-0b2e8c3d5f61"}},{"id":"CICD-SEC-3","toolComponent":{"name":"OWASP Top 10 CI/CD Security Risks","guid":"3c9b2a6e-5d14-4f7b-8e02-6a1d9c4b7f35"}}]},{"ruleId":"D31","level":"error","message":{"text":"A security finding was recorded here. Its details are withheld on the public artifact \u2014 ask the repository owner for the full report."},"partialFingerprints":{"codehealthFindingId/v1":"8a75ea80a29be47a83cbabdf426387d0da965370b5ae89d9d88e165d36aa05e6"},"taxa":[{"id":"CWE-494","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},{"id":"A08:2025","toolComponent":{"name":"OWASP Top 10","guid":"7d1f0c2e-4b6a-4e39-9a51-0b2e8c3d5f61"}},{"id":"CICD-SEC-3","toolComponent":{"name":"OWASP Top 10 CI/CD Security Risks","guid":"3c9b2a6e-5d14-4f7b-8e02-6a1d9c4b7f35"}}]},{"ruleId":"D31","level":"error","message":{"text":"A security finding was recorded here. Its details are withheld on the public artifact \u2014 ask the repository owner for the full report."},"partialFingerprints":{"codehealthFindingId/v1":"dbb258ffd3950963414877a11acdc9a2aa2a4cf8d857959981aa12f5aee7bc84"},"taxa":[{"id":"CWE-1357","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},{"id":"CWE-353","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},{"id":"A03:2025","toolComponent":{"name":"OWASP Top 10","guid":"7d1f0c2e-4b6a-4e39-9a51-0b2e8c3d5f61"}}]},{"ruleId":"D31","level":"warning","message":{"text":"A security finding was recorded here. Its details are withheld on the public artifact \u2014 ask the repository owner for the full report."},"partialFingerprints":{"codehealthFindingId/v1":"5cfed0ee4e80785ce23c7083cffdead9acc6c64f9dce56a00ca7178bd3d7bee5"},"taxa":[{"id":"CWE-1357","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},{"id":"CWE-353","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},{"id":"A03:2025","toolComponent":{"name":"OWASP Top 10","guid":"7d1f0c2e-4b6a-4e39-9a51-0b2e8c3d5f61"}}]},{"ruleId":"D31","level":"warning","message":{"text":"A security finding was recorded here. Its details are withheld on the public artifact \u2014 ask the repository owner for the full report."},"partialFingerprints":{"codehealthFindingId/v1":"c622bab8f3dace0cacee804422dc55592a6dcb7fd3b196d5086cd478627fe00f"}},{"ruleId":"D31","level":"warning","message":{"text":"A security finding was recorded here. Its details are withheld on the public artifact \u2014 ask the repository owner for the full report."},"partialFingerprints":{"codehealthFindingId/v1":"e07236652f8b728a4f423f9f90ec9ac0d9c3e804a5589474fd12cabca32d26a6"},"taxa":[{"id":"CWE-1357","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},{"id":"CWE-353","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},{"id":"A03:2025","toolComponent":{"name":"OWASP Top 10","guid":"7d1f0c2e-4b6a-4e39-9a51-0b2e8c3d5f61"}}]},{"ruleId":"D31","level":"warning","message":{"text":"A security finding was recorded here. Its details are withheld on the public artifact \u2014 ask the repository owner for the full report."},"partialFingerprints":{"codehealthFindingId/v1":"476176a40f7dde5968156df41a3ad51fabec36db7b9ad2c78af7fdee298fccfb"},"taxa":[{"id":"CWE-1357","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},{"id":"CWE-353","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},{"id":"A03:2025","toolComponent":{"name":"OWASP Top 10","guid":"7d1f0c2e-4b6a-4e39-9a51-0b2e8c3d5f61"}}]},{"ruleId":"D31","level":"warning","message":{"text":"A security finding was recorded here. Its details are withheld on the public artifact \u2014 ask the repository owner for the full report."},"partialFingerprints":{"codehealthFindingId/v1":"60f69ace7b9ba4f3b45da934a121b351e367cc07f4f7fe7eaff03340ca58968e"}},{"ruleId":"D31","level":"warning","message":{"text":"A security finding was recorded here. Its details are withheld on the public artifact \u2014 ask the repository owner for the full report."},"partialFingerprints":{"codehealthFindingId/v1":"0e0aff5f904e16b0ddd10b7265fe442088d105b2f97e00c08e902a8350038d9a"}},{"ruleId":"D31","level":"warning","message":{"text":"A security finding was recorded here. Its details are withheld on the public artifact \u2014 ask the repository owner for the full report."},"partialFingerprints":{"codehealthFindingId/v1":"8bad6a108fd0fe3af8266e9e2cc1451f9f8690e7ebb97fab426bfc0ab6a32221"},"taxa":[{"id":"CWE-1357","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},{"id":"CWE-353","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},{"id":"A03:2025","toolComponent":{"name":"OWASP Top 10","guid":"7d1f0c2e-4b6a-4e39-9a51-0b2e8c3d5f61"}}]},{"ruleId":"D31","level":"warning","message":{"text":"A security finding was recorded here. Its details are withheld on the public artifact \u2014 ask the repository owner for the full report."},"partialFingerprints":{"codehealthFindingId/v1":"cb6f91ba3c57d1c4f2648728e12cd22c78aea1367c87157ee35729c0ad70c562"}},{"ruleId":"D31","level":"warning","message":{"text":"A security finding was recorded here. Its details are withheld on the public artifact \u2014 ask the repository owner for the full report."},"partialFingerprints":{"codehealthFindingId/v1":"ecd9ad040acc56f62def0be2e552534023ccf3272d5c5d742e74b2f3c04dda88"},"taxa":[{"id":"CWE-1357","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},{"id":"CWE-353","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},{"id":"A03:2025","toolComponent":{"name":"OWASP Top 10","guid":"7d1f0c2e-4b6a-4e39-9a51-0b2e8c3d5f61"}}]},{"ruleId":"D31","level":"note","message":{"text":"A security finding was recorded here. Its details are withheld on the public artifact \u2014 ask the repository owner for the full report."},"partialFingerprints":{"codehealthFindingId/v1":"1f51a163f26d94ce6b6f473117dde579d091c8cbf9e59810475c94d9fc921120"}},{"ruleId":"D35","level":"warning","message":{"text":"Change coupling: summarize_nodes.py \u2194 node_operations.py: \u0060graphiti_core/prompts/summarize_nodes.py\u0060 and \u0060graphiti_core/utils/maintenance/node_operations.py\u0060 change together 60% of the time (6 of the 10 commits that touched whichever of the two files changed less often, counting a file under its earlier names as well \u2014 a repo-wide or module-wide sweep is evidence about the sweep rather than about any pair inside it and is left out of BOTH sides of this ratio, while a dependency bump, a formatter/rename sweep, or a commit whose edit to one of the two files was a tool directive such as //go:generate or whitespace only is left out of the shared count ONLY, so the two sides are not taken over identical commit sets) with no explicit dependency \u2014 the edge is real but nothing declares it. Read the pair before acting: if one registers itself into the other through a hook or an initialiser, the missing dependency is DELIBERATE \u2014 the registration is the link, and it is meant not to be an import \u2014 and the thing to add is a comment on each side naming the other, not a merge; if they simply belong together, co-locate them; if neither holds, the coupling is hidden and worth breaking. You can check this without leaving the row: of the 6 shared commits counted here, the most recent 3 are \u0060fd9969b5\u0060 Update dedupe prompt (#457); \u00605274970b\u0060 reduce entity type attribute hallucinations (#365); \u006004203506\u0060 fix bug with updating node type attributes (#305) \u2014 run \u0060git show\u0060 on any of them."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/prompts/summarize_nodes.py"},"region":{"startLine":1}}}],"partialFingerprints":{"codehealthFindingId/v1":"5df2a70515a013353ef92ec405c3230d0c484962eccceda9575d95121495f3fa"}},{"ruleId":"D35","level":"warning","message":{"text":"Change coupling: extract_edges.py \u2194 node_operations.py: \u0060graphiti_core/prompts/extract_edges.py\u0060 and \u0060graphiti_core/utils/maintenance/node_operations.py\u0060 change together 58% of the time (11 of the 19 commits that touched whichever of the two files changed less often, counting a file under its earlier names as well \u2014 a repo-wide or module-wide sweep is evidence about the sweep rather than about any pair inside it and is left out of BOTH sides of this ratio, while a dependency bump, a formatter/rename sweep, or a commit whose edit to one of the two files was a tool directive such as //go:generate or whitespace only is left out of the shared count ONLY, so the two sides are not taken over identical commit sets) with no explicit dependency \u2014 the edge is real but nothing declares it. Read the pair before acting: if one registers itself into the other through a hook or an initialiser, the missing dependency is DELIBERATE \u2014 the registration is the link, and it is meant not to be an import \u2014 and the thing to add is a comment on each side naming the other, not a merge; if they simply belong together, co-locate them; if neither holds, the coupling is hidden and worth breaking. You can check this without leaving the row: of the 11 shared commits counted here, the most recent 3 are \u0060673902cd\u0060 forward-port: combined node\u002Bedge extraction \u002B prompt refinements (#1432); \u0060d9aae867\u0060 Add extracted edge facts to entity summaries (#1182); \u0060be224cf1\u0060 Custom prompt (#1122) \u2014 run \u0060git show\u0060 on any of them."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/prompts/extract_edges.py"},"region":{"startLine":1}}}],"partialFingerprints":{"codehealthFindingId/v1":"48e7c5bf4563eb788cd4fcb5844acab8a15c5ae14ad8686db4d39516747b893b"}},{"ruleId":"D36","level":"note","message":{"text":"A security finding was recorded here. Its details are withheld on the public artifact \u2014 ask the repository owner for the full report."},"partialFingerprints":{"codehealthFindingId/v1":"eb00976a698a5d68999b7ee6d27206fd374e916853e7ff1ead5eed42386f043f"},"taxa":[{"id":"CICD-SEC-9","toolComponent":{"name":"OWASP Top 10 CI/CD Security Risks","guid":"3c9b2a6e-5d14-4f7b-8e02-6a1d9c4b7f35"}}]},{"ruleId":"D36","level":"warning","message":{"text":"A security finding was recorded here. Its details are withheld on the public artifact \u2014 ask the repository owner for the full report."},"partialFingerprints":{"codehealthFindingId/v1":"9d74adbf44cd991fa5a1c56a6987f1ce9f90f21dbd0ce2c95259ea82da90c2b3"}},{"ruleId":"C2","level":"warning","message":{"text":"No authorization: This repository serves requests \u2014 a request handler (@app.get() (server/graph_service/main.py:27) \u2014 and no route guard, auth-gate middleware, global guard or imperative check that refuses a caller (401/403, an Unauthorized/Forbidden error) was found: its endpoints may be unprotected."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"server/graph_service/main.py"},"region":{"startLine":27}}}],"partialFingerprints":{"codehealthFindingId/v1":"0f4cd50534573a1e7fcf0d6f77245509eae43aaa125e27a3b991b1721e2601d6"}},{"ruleId":"M2","level":"note","message":{"text":"No ADRs: No Architecture Decision Records found \u2014 no conventional ADR directory, no numbered \u0060NNNN-title\u0060 documents in any markup this check reads, and nothing ADR-shaped by content. Design rationale recorded elsewhere (a design-notes tree, a mailing list, pull-request discussion) is not visible to this check and is not re-findable per decision, so a future maintainer cannot ask why one choice was made and get an answer."},"locations":[],"partialFingerprints":{"codehealthFindingId/v1":"670b3d6e36a756d63097d0dfbf90afd5fc761308800b9354894a07c3f4e4aa14"}},{"ruleId":"M3","level":"note","message":{"text":"No src/ separation: Production code isn\u0027t grouped under a src/ folder \u2014 it\u0027s spread across several top-level directories, so there\u0027s no one place that says \u0027this is the product\u0027."},"locations":[],"partialFingerprints":{"codehealthFindingId/v1":"fdf7f5b24e313364d10170a5c2542959902fe0d26ed70edef3eaa10ec5bcdb1d"}},{"ruleId":"M4","level":"note","message":{"text":"README/code drift: mcp_server/README.md changes into \u0060graphiti/mcp_server\u0060, but no such folder exists. Checked without a model against the repository: its files and folders, the scripts and dependencies of every package.json, the projects, namespaces, package references and target frameworks of the .NET build, the SDK and Node pins, and the identifiers of every file that is not a document; the documents\u0027 own text never counts. A footprint generated at build time, read only by a platform, or living outside this repository is not seen, and this row is then wrong."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/README.md"},"region":{"startLine":65}}}],"partialFingerprints":{"codehealthFindingId/v1":"0fc66586e2214558c85e967c913eddd3a50f94301621e39b60475e426c5ca1a6"}},{"ruleId":"M4","level":"note","message":{"text":"README/code drift: mcp_server/README.md runs \u0060docker compose -f docker/docker-compose.neo4j.yaml up\u0060, but \u0060docker/docker-compose.neo4j.yaml\u0060 does not exist. Checked without a model against the repository: its files and folders, the scripts and dependencies of every package.json, the projects, namespaces, package references and target frameworks of the .NET build, the SDK and Node pins, and the identifiers of every file that is not a document; the documents\u0027 own text never counts. A footprint generated at build time, read only by a platform, or living outside this repository is not seen, and this row is then wrong."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/README.md"},"region":{"startLine":290}}}],"partialFingerprints":{"codehealthFindingId/v1":"9ef6110b18751912bb584e092a4234377b625be303cf8a6fa1b76b83d53a67e9"}},{"ruleId":"M4","level":"note","message":{"text":"README/code drift: mcp_server/README.md runs \u0060docker compose -f docker/docker-compose.falkordb.yaml up\u0060, but \u0060docker/docker-compose.falkordb.yaml\u0060 does not exist. Checked without a model against the repository: its files and folders, the scripts and dependencies of every package.json, the projects, namespaces, package references and target frameworks of the .NET build, the SDK and Node pins, and the identifiers of every file that is not a document; the documents\u0027 own text never counts. A footprint generated at build time, read only by a platform, or living outside this repository is not seen, and this row is then wrong."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/README.md"},"region":{"startLine":319}}}],"partialFingerprints":{"codehealthFindingId/v1":"7a61ce208872f4612f4a941e6c5621d06cb41ce6a2677e7c336b24af08b60096"}},{"ruleId":"M4","level":"note","message":{"text":"README/code drift: mcp_server/README.md links to \u0060cursor_rules.md\u0060, but \u0060mcp_server/cursor_rules.md\u0060 does not exist. Checked without a model against the repository: its files and folders, the scripts and dependencies of every package.json, the projects, namespaces, package references and target frameworks of the .NET build, the SDK and Node pins, and the identifiers of every file that is not a document; the documents\u0027 own text never counts. A footprint generated at build time, read only by a platform, or living outside this repository is not seen, and this row is then wrong."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/README.md"},"region":{"startLine":640}}}],"partialFingerprints":{"codehealthFindingId/v1":"da464a1a72b506368d1131fbef1ec830c7029b3dcb9ea0b0e51159dfc62f13ee"}},{"ruleId":"M4","level":"note","message":{"text":"README/code drift: mcp_server/docker/README-falkordb-combined.md runs \u0060docker compose -f docker/docker-compose-falkordb-combined.yml up\u0060, but \u0060docker/docker-compose-falkordb-combined.yml\u0060 does not exist. Checked without a model against the repository: its files and folders, the scripts and dependencies of every package.json, the projects, namespaces, package references and target frameworks of the .NET build, the SDK and Node pins, and the identifiers of every file that is not a document; the documents\u0027 own text never counts. A footprint generated at build time, read only by a platform, or living outside this repository is not seen, and this row is then wrong."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/docker/README-falkordb-combined.md"},"region":{"startLine":34}}}],"partialFingerprints":{"codehealthFindingId/v1":"a97ad2a672261675b6a0fa8965b83d59af03edfcd81ccf3c14a2b8d22bd4ced5"}},{"ruleId":"M4","level":"note","message":{"text":"README/code drift: mcp_server/docker/README-falkordb-combined.md runs \u0060docker compose -f docker/docker-compose-falkordb-combined.yml logs -f graphiti-falkordb\u0060, but \u0060graphiti-falkordb\u0060 does not exist. Checked without a model against the repository: its files and folders, the scripts and dependencies of every package.json, the projects, namespaces, package references and target frameworks of the .NET build, the SDK and Node pins, and the identifiers of every file that is not a document; the documents\u0027 own text never counts. A footprint generated at build time, read only by a platform, or living outside this repository is not seen, and this row is then wrong."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/docker/README-falkordb-combined.md"},"region":{"startLine":111}}}],"partialFingerprints":{"codehealthFindingId/v1":"3281994b19e5623e1ff8bf5cd9157e7ab74c0663387fbd11321b5c6c58750288"}},{"ruleId":"M4","level":"note","message":{"text":"README/code drift: mcp_server/docker/README-falkordb-combined.md names \u0060docker-compose-falkordb-combined.yml\u0060, but no file or folder \u0060docker-compose-falkordb-combined.yml\u0060 exists. Checked without a model against the repository: its files and folders, the scripts and dependencies of every package.json, the projects, namespaces, package references and target frameworks of the .NET build, the SDK and Node pins, and the identifiers of every file that is not a document; the documents\u0027 own text never counts. A footprint generated at build time, read only by a platform, or living outside this repository is not seen, and this row is then wrong."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/docker/README-falkordb-combined.md"},"region":{"startLine":222}}}],"partialFingerprints":{"codehealthFindingId/v1":"857b347138d4a0e98ec86678354f9ded52f0010d951778370c42fead8ba1c649"}},{"ruleId":"P6","level":"note","message":{"text":"No changelog: No CHANGELOG/HISTORY/RELEASES file \u2014 what shipped when isn\u0027t easy to reconstruct for support or audit. (Versioning/tagging makes releases traceable, but a changelog records the what.)"},"locations":[],"partialFingerprints":{"codehealthFindingId/v1":"dda5aa5aed8cbb292c3ef2b733bc138f614293ae6426c85e98bf31ef330d9415"}},{"ruleId":"X1","level":"warning","message":{"text":"Task from \u0060asyncio.create_task\u0060 is not kept: The task this creates is not kept anywhere. The event loop holds only a weak reference to its tasks, so a task nobody references can be garbage-collected before it finishes (Python\u0027s documentation for \u0060asyncio.create_task\u0060: save a reference to the result); and nothing ever awaits it, so an exception it raises is never handled \u2014 it surfaces, if at all, as \u0022Task exception was never retrieved\u0022 in a log. Keep the task: \u0060await\u0060 it, gather it, start it in an \u0060asyncio.TaskGroup\u0060 (\u0060async with asyncio.TaskGroup() as tg: tg.create_task(\u2026)\u0060), or, for genuine fire-and-forget, add it to a set and \u0060task.add_done_callback(tasks.discard)\u0060 with a callback that also reads its exception."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/services/queue_service.py"},"region":{"startLine":45}}}],"partialFingerprints":{"codehealthFindingId/v1":"937d65dfda28523e3ea207d61a6c4d0c27fd0fc0a33a293f62e46e91d29e060b"}},{"ruleId":"X10","level":"note","message":{"text":"Duplicated predicate: \u0060\u0027rate limit\u0027 in error_message or \u0027quota\u0027 in error_message or \u0027resource_exhausted\u0027 in error_message or \u0027429\u0027 in str(e)\u0060 appears character-identically in 2 files \u2014 graphiti_core/cross_encoder/gemini_reranker_client.py, graphiti_core/llm_client/gemini_client.py. It is one line, so the duplication detector\u0027s token window never sees it; the copies drift when only one is corrected. Give the condition a name and one home."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/cross_encoder/gemini_reranker_client.py"},"region":{"startLine":153}}}],"partialFingerprints":{"codehealthFindingId/v1":"4e54d1885bf8e5254f6450dad005e84430b309b1284ccb030b7f9a0676315e46"}},{"ruleId":"X10","level":"note","message":{"text":"Duplicated predicate: \u0060allowed_edge_type_names is not None and edge_data.relation_type not in allowed_edge_type_names\u0060 appears character-identically in 2 files \u2014 graphiti_core/utils/maintenance/combined_extraction.py, graphiti_core/utils/maintenance/edge_operations.py. It is one line, so the duplication detector\u0027s token window never sees it; the copies drift when only one is corrected. Give the condition a name and one home."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/combined_extraction.py"},"region":{"startLine":251}}}],"partialFingerprints":{"codehealthFindingId/v1":"388609e2c1f927984f16d75807c3e3eebbc82660083ef7723dbc0a607e4ff6e4"}},{"ruleId":"X10","level":"note","message":{"text":"Duplicated predicate: \u0060community_candidate != -1 and candidate_rank \u003E 1\u0060 appears character-identically in 2 files \u2014 graphiti_core/driver/operations/graph_utils.py, graphiti_core/utils/maintenance/community_operations.py. It is one line, so the duplication detector\u0027s token window never sees it; the copies drift when only one is corrected. Give the condition a name and one home."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/operations/graph_utils.py"},"region":{"startLine":46}}}],"partialFingerprints":{"codehealthFindingId/v1":"b445b0d284019d717110ca92328c938cb34dfe629bcaefb801e595b6cb3c8771"}},{"ruleId":"X10","level":"note","message":{"text":"Duplicated predicate: \u0060edge_data.episode_indices and 0 \u003C= edge_data.episode_indices[0] \u003C len(episodes)\u0060 appears character-identically in 2 files \u2014 graphiti_core/utils/maintenance/combined_extraction.py, graphiti_core/utils/maintenance/edge_operations.py. It is one line, so the duplication detector\u0027s token window never sees it; the copies drift when only one is corrected. Give the condition a name and one home."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/combined_extraction.py"},"region":{"startLine":241}}}],"partialFingerprints":{"codehealthFindingId/v1":"c8748774cb0f3f7e4636fcd417ba87bc58368c28403f7e41e7a6be64611f9191"}},{"ruleId":"X10","level":"note","message":{"text":"Duplicated predicate: \u0060excluded_entity_types and entity_type_name in excluded_entity_types\u0060 appears character-identically in 2 files \u2014 graphiti_core/utils/maintenance/combined_extraction.py, graphiti_core/utils/maintenance/node_operations.py. It is one line, so the duplication detector\u0027s token window never sees it; the copies drift when only one is corrected. Give the condition a name and one home."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/combined_extraction.py"},"region":{"startLine":199}}}],"partialFingerprints":{"codehealthFindingId/v1":"8efd9e2d184d9c6c0f384176b5cf4affdaabf2d7f634fd7ec6a8ec10bd27f8ef"}},{"ruleId":"X10","level":"note","message":{"text":"Duplicated predicate: \u0060hasattr(response, \u0027refusal\u0027) and response.refusal\u0060 appears character-identically in 2 files \u2014 graphiti_core/llm_client/azure_openai_client.py, graphiti_core/llm_client/openai_base_client.py. It is one line, so the duplication detector\u0027s token window never sees it; the copies drift when only one is corrected. Give the condition a name and one home."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/azure_openai_client.py"},"region":{"startLine":155}}}],"partialFingerprints":{"codehealthFindingId/v1":"b9298dfba02cc5da78d27f0b53d31429713bb83aef6b375db696ec58dd0926b9"}},{"ruleId":"X10","level":"note","message":{"text":"Duplicated predicate: \u0060isinstance(executor, GraphDriver) and executor.provider == GraphProvider.FALKORDB and group_ids\u0060 appears character-identically in 9 files \u2014 graphiti_core/driver/falkordb/operations/community_edge_ops.py, graphiti_core/driver/falkordb/operations/community_node_ops.py, graphiti_core/driver/falkordb/operations/entity_edge_ops.py, graphiti_core/driver/falkordb/operations/entity_node_ops.py. It is one line, so the duplication detector\u0027s token window never sees it; the copies drift when only one is corrected. Give the condition a name and one home."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/community_edge_ops.py"},"region":{"startLine":145}}}],"partialFingerprints":{"codehealthFindingId/v1":"fbc3e4e0152f54d74b79c6dedafb612c221c16c42364df372e29f0467d194aba"}},{"ruleId":"X10","level":"note","message":{"text":"Duplicated predicate: \u0060isinstance(label, dict) and isinstance(label.get(\u0027name\u0027), str)\u0060 appears character-identically in 2 files \u2014 .github/intake/apply.py, .github/intake/decide.py. It is one line, so the duplication detector\u0027s token window never sees it; the copies drift when only one is corrected. Give the condition a name and one home."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":".github/intake/apply.py"},"region":{"startLine":404}}}],"partialFingerprints":{"codehealthFindingId/v1":"52143982fba6d3707a3e0737a122ebf6a8b2865f37758cbedade7eeeefab44bb"}},{"ruleId":"X10","level":"note","message":{"text":"Duplicated predicate: \u0060isinstance(last_error, httpx.HTTPStatusError) and last_error.response.status_code == 429\u0060 appears character-identically in 2 files \u2014 graphiti_core/llm_client/jev_client.py, graphiti_core/llm_client/openai_decisions_client.py. It is one line, so the duplication detector\u0027s token window never sees it; the copies drift when only one is corrected. Give the condition a name and one home."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/jev_client.py"},"region":{"startLine":248}}}],"partialFingerprints":{"codehealthFindingId/v1":"010a1e396570dac16290d55188ded8a8c6e0123afa4ae14d9ab040ebd370885f"}},{"ruleId":"X10","level":"note","message":{"text":"Duplicated predicate: \u0060last_error or Exception(\u0027Max retries exceeded with no specific error\u0027)\u0060 appears character-identically in 2 files \u2014 graphiti_core/llm_client/anthropic_client.py, graphiti_core/llm_client/openai_base_client.py. It is one line, so the duplication detector\u0027s token window never sees it; the copies drift when only one is corrected. Give the condition a name and one home."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/anthropic_client.py"},"region":{"startLine":462}}}],"partialFingerprints":{"codehealthFindingId/v1":"874b7a7daf48dee9a92a34e57d3752ce1bdb7cfbafd70276138b79b88e42723f"}},{"ruleId":"X10","level":"note","message":{"text":"Duplicated predicate: \u0060major == 5 and minor is not None and int(minor) \u003E= 1\u0060 appears character-identically in 2 files \u2014 graphiti_core/llm_client/openai_base_client.py, mcp_server/src/services/factories.py. It is one line, so the duplication detector\u0027s token window never sees it; the copies drift when only one is corrected. Give the condition a name and one home."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/openai_base_client.py"},"region":{"startLine":157}}}],"partialFingerprints":{"codehealthFindingId/v1":"23c1a62fde1fb0b8196e797d2f842a7a729f6c0309d8f88a81f7b64b7fbc0df7"}},{"ruleId":"X10","level":"note","message":{"text":"Duplicated predicate: \u0060response.choices[0].message.content or \u0027\u0027\u0060 appears character-identically in 2 files \u2014 graphiti_core/llm_client/groq_client.py, graphiti_core/llm_client/openai_generic_client.py. It is one line, so the duplication detector\u0027s token window never sees it; the copies drift when only one is corrected. Give the condition a name and one home."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/groq_client.py"},"region":{"startLine":81}}}],"partialFingerprints":{"codehealthFindingId/v1":"faa4fc1dc059d73d9a1a24cf7ba79e1a696f22baa032d7a35701a5c096079ca6"}},{"ruleId":"X10","level":"note","message":{"text":"Duplicated predicate: \u0060saga is not None and group_ids is not None and len(group_ids) \u003E 0\u0060 appears character-identically in 4 files \u2014 graphiti_core/driver/falkordb/operations/episode_node_ops.py, graphiti_core/driver/kuzu/operations/episode_node_ops.py, graphiti_core/driver/neo4j/operations/episode_node_ops.py, graphiti_core/driver/neptune/operations/episode_node_ops.py. It is one line, so the duplication detector\u0027s token window never sees it; the copies drift when only one is corrected. Give the condition a name and one home."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/episode_node_ops.py"},"region":{"startLine":249}}}],"partialFingerprints":{"codehealthFindingId/v1":"dce738156559a42cbd81b35a96f8976927d75b97a07c671dfac970f3b82b4fcc"}},{"ruleId":"X10","level":"note","message":{"text":"Duplicated predicate: \u0060self.cache_enabled and self.cache_dir is not None\u0060 appears character-identically in 2 files \u2014 graphiti_core/llm_client/client.py, graphiti_core/llm_client/gliner2_client.py. It is one line, so the duplication detector\u0027s token window never sees it; the copies drift when only one is corrected. Give the condition a name and one home."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/client.py"},"region":{"startLine":254}}}],"partialFingerprints":{"codehealthFindingId/v1":"9f0d1df89c8e9dc378955b911450dfac5bde17a5d2a40cfcc68a860789543538"}},{"ruleId":"X10","level":"note","message":{"text":"Duplicated predicate: \u0060strict_edge_types and not edge_types_context\u0060 appears character-identically in 2 files \u2014 graphiti_core/utils/maintenance/combined_extraction.py, graphiti_core/utils/maintenance/edge_operations.py. It is one line, so the duplication detector\u0027s token window never sees it; the copies drift when only one is corrected. Give the condition a name and one home."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/combined_extraction.py"},"region":{"startLine":141}}}],"partialFingerprints":{"codehealthFindingId/v1":"635f933c544f757b953d8861093df7f0f1ede50b68e3cf5c2a522d1da998d7d7"}},{"ruleId":"X23","level":"note","message":{"text":"Diagnostic log message is built whether or not it is wanted: \u0060debug\u0060 at line 161 is called with an argument built by \u0060[n.uuid for n in extracted_nodes]\u0060 (line 161), whose cost grows with the collection it walks. Python evaluates a call\u0027s arguments BEFORE entering the method, so that join/projection runs in full every time this line is reached \u2014 and then the level check inside the logger discards the string it produced, because debug is a level a shipped configuration normally leaves off. Nothing here decides beforehand whether the message is wanted: no \u0060isEnabledFor(logging.DEBUG)\u0060 test, no debug flag, no \u0060lambda\u0060 the logger calls only when the level is on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/node_operations.py"},"region":{"startLine":161}}}],"partialFingerprints":{"codehealthFindingId/v1":"a4dd3e629dd18597e443bdedece034d4ecba147002e16b8d3b1602bb8f85d9d8"}},{"ruleId":"X23","level":"note","message":{"text":"Diagnostic log message is built whether or not it is wanted: \u0060debug\u0060 at line 691 is called with an argument built by \u0060[ctx[\u0022\u2026\u0022] for ctx in extracted_nodes_context[: sample_size]]\u0060 (line 694), whose cost grows with the collection it walks. Python evaluates a call\u0027s arguments BEFORE entering the method, so that join/projection runs in full every time this line is reached \u2014 and then the level check inside the logger discards the string it produced, because debug is a level a shipped configuration normally leaves off. Nothing here decides beforehand whether the message is wanted: no \u0060isEnabledFor(logging.DEBUG)\u0060 test, no debug flag, no \u0060lambda\u0060 the logger calls only when the level is on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/node_operations.py"},"region":{"startLine":691}}}],"partialFingerprints":{"codehealthFindingId/v1":"e1a6917fe5ee4f1899da6a0f5549ec8afd8159483788f8b802c4874999f8c1e8"}},{"ruleId":"X23","level":"note","message":{"text":"Diagnostic log message is built whether or not it is wanted: \u0060debug\u0060 at line 697 is called with an argument built by \u0060[ctx[\u0022\u2026\u0022] for ctx in extracted_nodes_context[- sample_size :]]\u0060 (line 700), whose cost grows with the collection it walks. Python evaluates a call\u0027s arguments BEFORE entering the method, so that join/projection runs in full every time this line is reached \u2014 and then the level check inside the logger discards the string it produced, because debug is a level a shipped configuration normally leaves off. Nothing here decides beforehand whether the message is wanted: no \u0060isEnabledFor(logging.DEBUG)\u0060 test, no debug flag, no \u0060lambda\u0060 the logger calls only when the level is on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/node_operations.py"},"region":{"startLine":697}}}],"partialFingerprints":{"codehealthFindingId/v1":"797ad36e3f7c621fa9da5d21061883bb22e5215b52f985ef0ed3d9cfd1775bbc"}},{"ruleId":"X23","level":"note","message":{"text":"Diagnostic log message is built whether or not it is wanted: \u0060debug\u0060 at line 901 is called with an argument built by \u0060[node.uuid for node in state.resolved_nodes if node is not None]\u0060 (line 903), whose cost grows with the collection it walks. Python evaluates a call\u0027s arguments BEFORE entering the method, so that join/projection runs in full every time this line is reached \u2014 and then the level check inside the logger discards the string it produced, because debug is a level a shipped configuration normally leaves off. Nothing here decides beforehand whether the message is wanted: no \u0060isEnabledFor(logging.DEBUG)\u0060 test, no debug flag, no \u0060lambda\u0060 the logger calls only when the level is on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/node_operations.py"},"region":{"startLine":901}}}],"partialFingerprints":{"codehealthFindingId/v1":"1a17142d5ddaaafb8a2b53acad12a6b66ffe780bb166ee1454af6d699d36591a"}},{"ruleId":"X23","level":"note","message":{"text":"Diagnostic log message is built whether or not it is wanted: \u0060debug\u0060 at line 379 is called with an argument built by \u0060[e.uuid for e in edges]\u0060 (line 379), whose cost grows with the collection it walks. Python evaluates a call\u0027s arguments BEFORE entering the method, so that join/projection runs in full every time this line is reached \u2014 and then the level check inside the logger discards the string it produced, because debug is a level a shipped configuration normally leaves off. Nothing here decides beforehand whether the message is wanted: no \u0060isEnabledFor(logging.DEBUG)\u0060 test, no debug flag, no \u0060lambda\u0060 the logger calls only when the level is on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/edge_operations.py"},"region":{"startLine":379}}}],"partialFingerprints":{"codehealthFindingId/v1":"516914b2aada89b841c0fbfcb4ea0c5854b17c26795ae3dc12ff036f34431d5a"}},{"ruleId":"X23","level":"note","message":{"text":"Diagnostic log message is built whether or not it is wanted: \u0060debug\u0060 at line 587 is called with an argument built by \u0060[e.uuid for edges_lst in related_edges_lists for e in edges_lst]\u0060 (line 588), whose cost grows with the collection it walks. Python evaluates a call\u0027s arguments BEFORE entering the method, so that join/projection runs in full every time this line is reached \u2014 and then the level check inside the logger discards the string it produced, because debug is a level a shipped configuration normally leaves off. Nothing here decides beforehand whether the message is wanted: no \u0060isEnabledFor(logging.DEBUG)\u0060 test, no debug flag, no \u0060lambda\u0060 the logger calls only when the level is on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/edge_operations.py"},"region":{"startLine":587}}}],"partialFingerprints":{"codehealthFindingId/v1":"ce1e9f5939d868e8b01f710000bde790670b6405ffdcd2858b9c942271206136"}},{"ruleId":"X23","level":"note","message":{"text":"Diagnostic log message is built whether or not it is wanted: \u0060debug\u0060 at line 628 is called with an argument built by \u0060[e.uuid for e in resolved_edges]\u0060 (line 628), whose cost grows with the collection it walks. Python evaluates a call\u0027s arguments BEFORE entering the method, so that join/projection runs in full every time this line is reached \u2014 and then the level check inside the logger discards the string it produced, because debug is a level a shipped configuration normally leaves off. Nothing here decides beforehand whether the message is wanted: no \u0060isEnabledFor(logging.DEBUG)\u0060 test, no debug flag, no \u0060lambda\u0060 the logger calls only when the level is on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/edge_operations.py"},"region":{"startLine":628}}}],"partialFingerprints":{"codehealthFindingId/v1":"29bdfa13e453dc63ed8e256689ca82e0fa3e77f8555918e2c357be55fd2f735b"}},{"ruleId":"X23","level":"note","message":{"text":"Diagnostic log message is built whether or not it is wanted: \u0060debug\u0060 at line 629 is called with an argument built by \u0060[e.uuid for e in new_edges]\u0060 (line 629), whose cost grows with the collection it walks. Python evaluates a call\u0027s arguments BEFORE entering the method, so that join/projection runs in full every time this line is reached \u2014 and then the level check inside the logger discards the string it produced, because debug is a level a shipped configuration normally leaves off. Nothing here decides beforehand whether the message is wanted: no \u0060isEnabledFor(logging.DEBUG)\u0060 test, no debug flag, no \u0060lambda\u0060 the logger calls only when the level is on."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/edge_operations.py"},"region":{"startLine":629}}}],"partialFingerprints":{"codehealthFindingId/v1":"5185993f3c9cd0b7325eb3b944359160cfd94bd4479f75d6ac664ff5d13b6eb4"}},{"ruleId":"X3","level":"warning","message":{"text":"Swallowed exception (empty catch): An empty catch block silently discards the error \u2014 failures vanish with no log and no rethrow. Log it, handle it, or don\u0027t catch it."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/gemini_client.py"},"region":{"startLine":232}}}],"partialFingerprints":{"codehealthFindingId/v1":"3dcd9ab416a268b8589321e37ac63d4ca0cc215c6f765913f7115e3da8b6912b"}},{"ruleId":"X3","level":"warning","message":{"text":"Swallowed exception (empty catch): An empty catch block silently discards the error \u2014 failures vanish with no log and no rethrow. Log it, handle it, or don\u0027t catch it."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/gemini_client.py"},"region":{"startLine":239}}}],"partialFingerprints":{"codehealthFindingId/v1":"c3477885ab8bb31fe27db5d2e070873ca9d5e89f12b0aedb71db3e13f59815d6"}},{"ruleId":"X3","level":"warning","message":{"text":"Swallowed exception (caught, then discarded): \u0060except Exception\u0060 takes every exception and records none of it \u2014 the body neither logs it, rethrows it, nor even names it, so the failure is discarded as completely as by an empty catch and only the substituted value survives. Log it through whatever this codebase already uses to report problems, narrow the catch to the exception this call can actually raise, or say in a comment on the catch why the failure genuinely cannot matter."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/decision_prompts.py"},"region":{"startLine":96}}}],"partialFingerprints":{"codehealthFindingId/v1":"5c1b1a1c98c0acfecd40f2a1224e5e227c44f4dc88c9837946289a01c8bcb4b5"}},{"ruleId":"X3","level":"warning","message":{"text":"Swallowed exception (empty catch): An empty catch block silently discards the error \u2014 failures vanish with no log and no rethrow. Log it, handle it, or don\u0027t catch it."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/telemetry/telemetry.py"},"region":{"startLine":50}}}],"partialFingerprints":{"codehealthFindingId/v1":"29644e3c4ed30c343cb2d8158b7349de0331ed725c51000c7e451faf79912d98"}},{"ruleId":"X3","level":"warning","message":{"text":"Swallowed exception (caught, then discarded): \u0060except Exception\u0060 takes every exception and records none of it \u2014 the body neither logs it, rethrows it, nor even names it, so the failure is discarded as completely as by an empty catch and only the substituted value survives. Log it through whatever this codebase already uses to report problems, narrow the catch to the exception this call can actually raise, or say in a comment on the catch why the failure genuinely cannot matter."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/telemetry/telemetry.py"},"region":{"startLine":61}}}],"partialFingerprints":{"codehealthFindingId/v1":"c49c319919cb858b8611532b8a8e4c82cd773dc73dc688ab769ae06ae85472f5"}},{"ruleId":"X3","level":"warning","message":{"text":"Swallowed exception (caught, then discarded): \u0060except Exception\u0060 takes every exception and records none of it \u2014 the body neither logs it, rethrows it, nor even names it, so the failure is discarded as completely as by an empty catch and only the substituted value survives. Log it through whatever this codebase already uses to report problems, narrow the catch to the exception this call can actually raise, or say in a comment on the catch why the failure genuinely cannot matter."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/telemetry/telemetry.py"},"region":{"startLine":72}}}],"partialFingerprints":{"codehealthFindingId/v1":"1042f80288feaee5ea80b0c8b480cb7a16e080c75c68a283fb7c70aff735cabe"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060error\u0060 at line 89 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.error(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.error(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/nodes.py"},"region":{"startLine":89}}}],"partialFingerprints":{"codehealthFindingId/v1":"6d17d4d77e4823cf93b0f561e1ac4b3503cc55f9fccb6fba140313871b26c553"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 167 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/nodes.py"},"region":{"startLine":167}}}],"partialFingerprints":{"codehealthFindingId/v1":"63ec8b2ed85d4a4cb3f7536a87b87ccfc743a5466a6438fab9a78293b27b6ce1"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 357 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/nodes.py"},"region":{"startLine":357}}}],"partialFingerprints":{"codehealthFindingId/v1":"58483f2a5a2fb41584f556fd53ab626b2e3753a7b93ff4b74dff503cbc4507d3"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 511 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/nodes.py"},"region":{"startLine":511}}}],"partialFingerprints":{"codehealthFindingId/v1":"730c268113d9fa5bd7052baae831f15e639198b98e31bcfa05f73e3002d67414"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 580 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/nodes.py"},"region":{"startLine":580}}}],"partialFingerprints":{"codehealthFindingId/v1":"0ac5c265acddb4907dbdcce2357e504a3250bbd1e146d128270596e0a14674fb"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 753 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/nodes.py"},"region":{"startLine":753}}}],"partialFingerprints":{"codehealthFindingId/v1":"ab6863d6268c44684541d4341c7328855e0bfb8bbf6d8d9f1af48ebb2e4b865f"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 762 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/nodes.py"},"region":{"startLine":762}}}],"partialFingerprints":{"codehealthFindingId/v1":"1ccbae1321951a5570d8537ef5049c50233706e14b2191fe2fe057a48ba0b6d5"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 942 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/nodes.py"},"region":{"startLine":942}}}],"partialFingerprints":{"codehealthFindingId/v1":"2cf40a5f2caa37d4d9c5f04500b307a99c837d91c87e2b4a050feb3f253cba65"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 961 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/nodes.py"},"region":{"startLine":961}}}],"partialFingerprints":{"codehealthFindingId/v1":"5eb561c9c2136d3f2e2ecaf88c139c2c45f15130d6f1c33d0037953a90bfb949"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060info\u0060 at line 687 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.info(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.info(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/graphiti.py"},"region":{"startLine":687}}}],"partialFingerprints":{"codehealthFindingId/v1":"d211bdc1e852ef374d3f40764da52e2d84a9b245780b8a6df6e97bb6ec470fdb"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060info\u0060 at line 699 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.info(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.info(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/graphiti.py"},"region":{"startLine":699}}}],"partialFingerprints":{"codehealthFindingId/v1":"a9876d6622fe5ab241317abdf8cd8fd4ae51343b28bd931cfe6d22e21a20cfa7"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060info\u0060 at line 763 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.info(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.info(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/graphiti.py"},"region":{"startLine":763}}}],"partialFingerprints":{"codehealthFindingId/v1":"e8cb8e7abcc3b67bb4773cb636ec17f3aaf8baab78ea1bda0dfb40c5a12179a9"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060info\u0060 at line 801 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.info(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.info(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/graphiti.py"},"region":{"startLine":801}}}],"partialFingerprints":{"codehealthFindingId/v1":"16d11e9f1ec914e79e0d8a0424a00e442de841f1db055c8bc5ab1e32b8bb6d8d"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060info\u0060 at line 1572 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.info(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.info(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/graphiti.py"},"region":{"startLine":1572}}}],"partialFingerprints":{"codehealthFindingId/v1":"77d10a70244f36131fce87cfeb2b565c309e7ef5339ad73e9a7b2eabc2e59e8b"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060info\u0060 at line 1850 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.info(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.info(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/graphiti.py"},"region":{"startLine":1850}}}],"partialFingerprints":{"codehealthFindingId/v1":"3e89e22e700f59f860cb544343a80583a93c780aa578fabc9eb49caba4bb8be6"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060info\u0060 at line 2101 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.info(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.info(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/graphiti.py"},"region":{"startLine":2101}}}],"partialFingerprints":{"codehealthFindingId/v1":"4c9d0d8ce8185f82098875da58fa9a2644c11babc0159af4ee14ecbf8ff4d729"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 90 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/edges.py"},"region":{"startLine":90}}}],"partialFingerprints":{"codehealthFindingId/v1":"ee8c450aa6cd404dadcfa4b2e9b562d5cd2572b6ad4f1e4cb65fd48bffc40f8f"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 129 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/edges.py"},"region":{"startLine":129}}}],"partialFingerprints":{"codehealthFindingId/v1":"55fa5354f574ae6441295682d70451295cc6670a5b9746af4a819db4bcd38f96"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 160 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/edges.py"},"region":{"startLine":160}}}],"partialFingerprints":{"codehealthFindingId/v1":"2b6440394ac55851fa18da062adee00d74b449c76de70a5b1be5e7acf6643859"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 299 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/edges.py"},"region":{"startLine":299}}}],"partialFingerprints":{"codehealthFindingId/v1":"2d3ccd7f7ca74c2d50f9cae873081a6be9daa7f9542ad4c1ab154dfcccc61f21"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 378 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/edges.py"},"region":{"startLine":378}}}],"partialFingerprints":{"codehealthFindingId/v1":"9615299561039760a0dcf74441ccc14207ab764c19d0a6b96098c500df2de03b"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 597 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/edges.py"},"region":{"startLine":597}}}],"partialFingerprints":{"codehealthFindingId/v1":"4ac8ec544c6d3d12a7ab9bafb7b300517d9a9a0154d8162e4641d439fea2fe46"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 711 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/edges.py"},"region":{"startLine":711}}}],"partialFingerprints":{"codehealthFindingId/v1":"c97404b07b9a7b98bd37e2c572a2e3b995e21a740c4bcc01ebac01ba098fbb15"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 730 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/edges.py"},"region":{"startLine":730}}}],"partialFingerprints":{"codehealthFindingId/v1":"39baa7b3ff5fad4e84cf3b6b526b42a80eb2aee4b93221f3c7ff9a5aa7e01360"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 844 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/edges.py"},"region":{"startLine":844}}}],"partialFingerprints":{"codehealthFindingId/v1":"4f7640f1cc4d14d538bcbc273dc02bb5e1b966be33b1fa3d6ee1d876833e47c5"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 865 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/edges.py"},"region":{"startLine":865}}}],"partialFingerprints":{"codehealthFindingId/v1":"757a56c272b6fa9a43a9ab1357f609fd67902589cb7ad2a7a8bf1730e0c67945"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060error\u0060 at line 123 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.error(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.error(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/cross_encoder/openai_reranker_client.py"},"region":{"startLine":123}}}],"partialFingerprints":{"codehealthFindingId/v1":"9e9bac9254fc04ffdb9dec8c60916a59d6970b030be7364f2e4f7d426554740c"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060warning\u0060 at line 134 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.warning(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.warning(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/cross_encoder/gemini_reranker_client.py"},"region":{"startLine":134}}}],"partialFingerprints":{"codehealthFindingId/v1":"d8912f4dd2dd6352a358c1546ed922ea1175791639580417bd2b68da6dc88a33"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060warning\u0060 at line 142 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.warning(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.warning(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/cross_encoder/gemini_reranker_client.py"},"region":{"startLine":142}}}],"partialFingerprints":{"codehealthFindingId/v1":"3acbf4b6c18b92751da90bfbfe449b4f15ec8ffd614f3a25909006f9aa422f73"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060error\u0060 at line 160 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.error(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.error(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/cross_encoder/gemini_reranker_client.py"},"region":{"startLine":160}}}],"partialFingerprints":{"codehealthFindingId/v1":"07c7de2bad1132b9932e227c5cbe05f1682e788144b5ed2739b3ff1a4c8cb816"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060error\u0060 at line 174 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.error(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.error(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neo4j_driver.py"},"region":{"startLine":174}}}],"partialFingerprints":{"codehealthFindingId/v1":"9ff3d9ded754970e45748cdb4dc74d20a66ea36f383e5c533f2162e921a0cff5"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 211 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neo4j_driver.py"},"region":{"startLine":211}}}],"partialFingerprints":{"codehealthFindingId/v1":"f5fffc6e9a9bc22c7616cad955bd93d7388b159f252260db584f16395cc7a82d"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060error\u0060 at line 230 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.error(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.error(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/kuzu_driver.py"},"region":{"startLine":230}}}],"partialFingerprints":{"codehealthFindingId/v1":"2254e45aa8e3ccb271eacc0a91c5e442900480f0b300450bb1fdd62f5886cfe8"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060info\u0060 at line 250 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.info(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.info(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb_driver.py"},"region":{"startLine":250}}}],"partialFingerprints":{"codehealthFindingId/v1":"ef8aed5a390a06e171c1812b9cb3a90138a7f1118115c68f88ffdcec5a5f596e"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060error\u0060 at line 252 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.error(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.error(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb_driver.py"},"region":{"startLine":252}}}],"partialFingerprints":{"codehealthFindingId/v1":"df78508b687a8f87ab22eb204cbe646404c13816b28360432bd4f081275a2174"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 59 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/saga_node_ops.py"},"region":{"startLine":59}}}],"partialFingerprints":{"codehealthFindingId/v1":"72a94c42ebe3ad1127b1d04034e310382ac1924211b4c03b6019a1f50c0e6a17"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 86 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/saga_node_ops.py"},"region":{"startLine":86}}}],"partialFingerprints":{"codehealthFindingId/v1":"3bace78340015ff35e1d46a3db5dcf8c87612625b70b5d547dd4e3620d6e69db"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 63 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/next_episode_edge_ops.py"},"region":{"startLine":63}}}],"partialFingerprints":{"codehealthFindingId/v1":"1e865cc5c5fe8cc2023e294118f922fe8b4b76edb8a8c97c172ce31ffdd2040f"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 90 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/next_episode_edge_ops.py"},"region":{"startLine":90}}}],"partialFingerprints":{"codehealthFindingId/v1":"c371549aa874c6edce9da27f929f35286e00cb492519e9b8e4deb565d1ca4371"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 63 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/has_episode_edge_ops.py"},"region":{"startLine":63}}}],"partialFingerprints":{"codehealthFindingId/v1":"42a4e308ca78b1fcbb34393c0ad5993990f8aad41d04ca7333ff5a28e6691f3a"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 90 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/has_episode_edge_ops.py"},"region":{"startLine":90}}}],"partialFingerprints":{"codehealthFindingId/v1":"997251909a86198d234274a5f6cc7c5be31225b888276c928fbb6ad46d7ab70d"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 64 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/episodic_edge_ops.py"},"region":{"startLine":64}}}],"partialFingerprints":{"codehealthFindingId/v1":"4f8c754612a48e83030a035415a613ee2b4505ee9411af1c5648cb0213953da8"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 95 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/episodic_edge_ops.py"},"region":{"startLine":95}}}],"partialFingerprints":{"codehealthFindingId/v1":"d18f6c8647ec9ca3e7373f3d3f47d022cb59a972ae713cf2e88d630d4c8808d0"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 60 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/episode_node_ops.py"},"region":{"startLine":60}}}],"partialFingerprints":{"codehealthFindingId/v1":"6b5d3bcf7631b85a15ca0137aac9cd904067b19c0bfd1ac2e7be150e2f9b5b19"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 101 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/episode_node_ops.py"},"region":{"startLine":101}}}],"partialFingerprints":{"codehealthFindingId/v1":"93d8e1054189726a147b9cfe5ebdfd6f1349fd77b8016f01c83d91599ea9a05e"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 60 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/entity_node_ops.py"},"region":{"startLine":60}}}],"partialFingerprints":{"codehealthFindingId/v1":"36ad9bab7a11a8c235d889fa29d076b904bd36b2b968453f4000d1e7e7655f87"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 113 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/entity_node_ops.py"},"region":{"startLine":113}}}],"partialFingerprints":{"codehealthFindingId/v1":"c0c4488130faf0b670878edc5ea64e1809ca869d742dd8eafd88a3221e25c60c"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 64 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/entity_edge_ops.py"},"region":{"startLine":64}}}],"partialFingerprints":{"codehealthFindingId/v1":"6de150f4dcd6ee3aacb0bb8a7ad5254ec9d17b8e6b5926c07eded7a4ab1d0076"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 113 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/entity_edge_ops.py"},"region":{"startLine":113}}}],"partialFingerprints":{"codehealthFindingId/v1":"91e65b68b254454baa283b99b6dba58be1446e2b08b202ee95a7f0142973c19f"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 55 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/community_node_ops.py"},"region":{"startLine":55}}}],"partialFingerprints":{"codehealthFindingId/v1":"cf59d26c131f1319f10739b15484e4e1ee625a0ea5b0e387486a0438f1f3123e"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 86 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/community_node_ops.py"},"region":{"startLine":86}}}],"partialFingerprints":{"codehealthFindingId/v1":"c4c9a9836cc63753201012de181b41f4714fc63c5a7f0932095c2f0415c3df2b"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 64 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/community_edge_ops.py"},"region":{"startLine":64}}}],"partialFingerprints":{"codehealthFindingId/v1":"64c90cb1e00865704330d9888fa8e572512f91bc15ed493657c94b658f631d37"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 81 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/falkordb/operations/community_edge_ops.py"},"region":{"startLine":81}}}],"partialFingerprints":{"codehealthFindingId/v1":"f9416d162d11038c6efe15d245863b26b34197be32fb18921dff7644f3591c66"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 59 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/kuzu/operations/saga_node_ops.py"},"region":{"startLine":59}}}],"partialFingerprints":{"codehealthFindingId/v1":"104f0c18beed913a36cfdc5d2d540d3b5a60ded63e6821bdcabbc3e4772cb00d"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 87 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/kuzu/operations/saga_node_ops.py"},"region":{"startLine":87}}}],"partialFingerprints":{"codehealthFindingId/v1":"cdff66807a04db5dc85141bf4e6aa5b7bcdc9bf54fa2f10fcdd35c0a0ef631b8"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 62 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/kuzu/operations/next_episode_edge_ops.py"},"region":{"startLine":62}}}],"partialFingerprints":{"codehealthFindingId/v1":"258df70e6ebfbb17cefe925ae20c82f701b381ead37619c96c5543d7db20b430"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 89 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/kuzu/operations/next_episode_edge_ops.py"},"region":{"startLine":89}}}],"partialFingerprints":{"codehealthFindingId/v1":"82969b58180e36771dc1069f7347ce1ac7b73d7e104b8b05d23c8a300883b95d"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 62 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/kuzu/operations/has_episode_edge_ops.py"},"region":{"startLine":62}}}],"partialFingerprints":{"codehealthFindingId/v1":"af8899b02d0607e88e88ed3d7c8455fbc3ecc14c67c9fe00c311b5a1bf5b0733"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 89 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/kuzu/operations/has_episode_edge_ops.py"},"region":{"startLine":89}}}],"partialFingerprints":{"codehealthFindingId/v1":"7cc0868237c1316c1a056708994ef07647e6f9c13a10feb0b63250c888f5c75c"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 64 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/kuzu/operations/episodic_edge_ops.py"},"region":{"startLine":64}}}],"partialFingerprints":{"codehealthFindingId/v1":"3e54cafb8dc97e030c55c7a669a73b6b16edaf70608d1428b5eb727451d46710"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 103 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/kuzu/operations/episodic_edge_ops.py"},"region":{"startLine":103}}}],"partialFingerprints":{"codehealthFindingId/v1":"c1b0a53818fc396b37d84cccb11ad5e4eee6bf3f1cce4f620cf1fb06705bbb3d"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 59 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/kuzu/operations/episode_node_ops.py"},"region":{"startLine":59}}}],"partialFingerprints":{"codehealthFindingId/v1":"3aee4a9fc0e458d51192233f413c406b341ed2ae03a64f2859fa9b9331ee8a34"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 87 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/kuzu/operations/episode_node_ops.py"},"region":{"startLine":87}}}],"partialFingerprints":{"codehealthFindingId/v1":"e8ea7e3ed774916ff1dcf310473f18f18c6681a6884b0b179c522955715fe4ba"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 62 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/kuzu/operations/entity_node_ops.py"},"region":{"startLine":62}}}],"partialFingerprints":{"codehealthFindingId/v1":"e47d5815612c422118274211886154b1d0642fa57cda72526f00a94e19e51e43"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 97 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/kuzu/operations/entity_node_ops.py"},"region":{"startLine":97}}}],"partialFingerprints":{"codehealthFindingId/v1":"1e9e98e6ac0290ba5d6afaad5108e30217fa87b82ab3042f904d7797cbab11df"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 64 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/kuzu/operations/entity_edge_ops.py"},"region":{"startLine":64}}}],"partialFingerprints":{"codehealthFindingId/v1":"e4454ef018138b3292f8703b04a7cc0277b7840d36b65bbb60b71f58cbddbceb"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 92 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/kuzu/operations/entity_edge_ops.py"},"region":{"startLine":92}}}],"partialFingerprints":{"codehealthFindingId/v1":"03e7a4d241bbe8dc09b6a826f2941b7102e5b65ca496d271f68bc2fdd24e1d89"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 55 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/kuzu/operations/community_node_ops.py"},"region":{"startLine":55}}}],"partialFingerprints":{"codehealthFindingId/v1":"f272de977d206d8cf230baf923e612c2b7688c2ad8250e657dddec6210892b12"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 83 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/kuzu/operations/community_node_ops.py"},"region":{"startLine":83}}}],"partialFingerprints":{"codehealthFindingId/v1":"39ee5c9f10ed03fcb02e43d77ec4124abcc04c5a41ae7bfcfc6d779dfbe77e73"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 64 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/kuzu/operations/community_edge_ops.py"},"region":{"startLine":64}}}],"partialFingerprints":{"codehealthFindingId/v1":"48540e99f5f285033f43470c974ba47a77b26b5992e4e0bd54cdb3bf1e3238af"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 81 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/kuzu/operations/community_edge_ops.py"},"region":{"startLine":81}}}],"partialFingerprints":{"codehealthFindingId/v1":"adcf7412898a70a8c9b3749e80dfd84a1c38f78ceb6d757fef7e4cb43559a395"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 59 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neo4j/operations/saga_node_ops.py"},"region":{"startLine":59}}}],"partialFingerprints":{"codehealthFindingId/v1":"83433afc3bbdb9ac3921e5905fd714a12569e1f6e3d7ae557a3d144f38558070"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 86 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neo4j/operations/saga_node_ops.py"},"region":{"startLine":86}}}],"partialFingerprints":{"codehealthFindingId/v1":"834030046cf0d3be6e8f51fe7fccaf57f8a4bc40ebad458192f40b1f0e8caf6f"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 62 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neo4j/operations/next_episode_edge_ops.py"},"region":{"startLine":62}}}],"partialFingerprints":{"codehealthFindingId/v1":"b01aeaf509dbc3e73f7e0ac65936f88ff920537801f2e4ff70adf19e2bec8c6c"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 89 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neo4j/operations/next_episode_edge_ops.py"},"region":{"startLine":89}}}],"partialFingerprints":{"codehealthFindingId/v1":"353a241d43d7b21fd5f806bb1abe0f8b0fb8f089c4f1bf3bea0111e3eab65941"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 62 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neo4j/operations/has_episode_edge_ops.py"},"region":{"startLine":62}}}],"partialFingerprints":{"codehealthFindingId/v1":"cf832b59b116d71ba4a880af616617b7ed86d292831277f21cc72e8cff104440"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 89 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neo4j/operations/has_episode_edge_ops.py"},"region":{"startLine":89}}}],"partialFingerprints":{"codehealthFindingId/v1":"8635eef9506f0556544ed21d27e613687781134cf0c92463f7297212beea5723"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 64 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neo4j/operations/episodic_edge_ops.py"},"region":{"startLine":64}}}],"partialFingerprints":{"codehealthFindingId/v1":"c58406a1858dcc8d66104c8932044252d6917ad68895af784477189ca97c1876"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 95 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neo4j/operations/episodic_edge_ops.py"},"region":{"startLine":95}}}],"partialFingerprints":{"codehealthFindingId/v1":"c3416c7aa7230329a56d819d3e019fc0120cdb1dcb0f27edc698ef78a7853fe3"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 60 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neo4j/operations/episode_node_ops.py"},"region":{"startLine":60}}}],"partialFingerprints":{"codehealthFindingId/v1":"f05ee52f1cd4c995ec2178eea7038bf5322c1916257ce3490c441f3771e61d82"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 101 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neo4j/operations/episode_node_ops.py"},"region":{"startLine":101}}}],"partialFingerprints":{"codehealthFindingId/v1":"f0ca51f0b951da055663f03e40588f0fa73981a8c24b8d44b0156e83f957d2d8"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 60 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neo4j/operations/entity_node_ops.py"},"region":{"startLine":60}}}],"partialFingerprints":{"codehealthFindingId/v1":"d0ae57077f4f210a1890c90232b7e0b8df959a2aec78cd206c3b14df7c437e84"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 109 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neo4j/operations/entity_node_ops.py"},"region":{"startLine":109}}}],"partialFingerprints":{"codehealthFindingId/v1":"f4c45cbf562f9c05ad358b2ecc0d77fae99723f929064f739d8d423052357a6a"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 64 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neo4j/operations/entity_edge_ops.py"},"region":{"startLine":64}}}],"partialFingerprints":{"codehealthFindingId/v1":"173a24f6d79dbc5c2f83bdcc31eb90213fd73f65ae7e2d153a1fb19eed67c4e8"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 113 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neo4j/operations/entity_edge_ops.py"},"region":{"startLine":113}}}],"partialFingerprints":{"codehealthFindingId/v1":"f29aa331a53c2bf4768df679c43260f8aee06f6c1b8cb5ab9c3b6ecb50752b47"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 55 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neo4j/operations/community_node_ops.py"},"region":{"startLine":55}}}],"partialFingerprints":{"codehealthFindingId/v1":"8ee2bc0389db81c89698f6cb3ece945f3f3060711dd02b902a633f2b3864c22e"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 87 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neo4j/operations/community_node_ops.py"},"region":{"startLine":87}}}],"partialFingerprints":{"codehealthFindingId/v1":"cc59e552cd25618f4914aca1ce79ff164d0c4e529d936efc42b5ea2094f0db3b"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 64 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neo4j/operations/community_edge_ops.py"},"region":{"startLine":64}}}],"partialFingerprints":{"codehealthFindingId/v1":"a1958a4c73e148bb079d7c3fd43bfa5d3eadef8cc991ca6c61946ce1237a0c4c"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 81 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neo4j/operations/community_edge_ops.py"},"region":{"startLine":81}}}],"partialFingerprints":{"codehealthFindingId/v1":"98873af791f898be3eb76c2bc97d59b7df1b8d004806364d3d7600f00f063540"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 62 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neptune/operations/saga_node_ops.py"},"region":{"startLine":62}}}],"partialFingerprints":{"codehealthFindingId/v1":"4772bcc7f88fe2853c80feba5af10f754ad2caeb4ff262dbde82b171a2aa4872"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 89 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neptune/operations/saga_node_ops.py"},"region":{"startLine":89}}}],"partialFingerprints":{"codehealthFindingId/v1":"60dce4188aa4d754f2df0468387dc9854f91bedec28c5b336780a91c85fa8210"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 62 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neptune/operations/next_episode_edge_ops.py"},"region":{"startLine":62}}}],"partialFingerprints":{"codehealthFindingId/v1":"70a04d01ec1dddd3a587db5d8af6686590abc13e9a0c84104d63ea4d1f078e90"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 89 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neptune/operations/next_episode_edge_ops.py"},"region":{"startLine":89}}}],"partialFingerprints":{"codehealthFindingId/v1":"391f6fd792589e1bcb8cf675935f9a437f2c6eaafcb5f0157deb6d523960bc22"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 62 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neptune/operations/has_episode_edge_ops.py"},"region":{"startLine":62}}}],"partialFingerprints":{"codehealthFindingId/v1":"d8ff00dda7d819c3bd75e29dd22a146b8eb49cbec392e2aa5466ae887d33cde1"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 89 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neptune/operations/has_episode_edge_ops.py"},"region":{"startLine":89}}}],"partialFingerprints":{"codehealthFindingId/v1":"da47182eef281b730ee0429ddb9fa7cf95a18857a580392fdde2e5ebf2581e09"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 64 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neptune/operations/episodic_edge_ops.py"},"region":{"startLine":64}}}],"partialFingerprints":{"codehealthFindingId/v1":"72663e4305fa66463836093729c892a56ebceb9bf0176d820fa3b410e50a6ed0"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 95 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neptune/operations/episodic_edge_ops.py"},"region":{"startLine":95}}}],"partialFingerprints":{"codehealthFindingId/v1":"ceaf3783d438ac97053166d1c3fb17485cf85d638b673a8c7b9705dad3fff9da"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 60 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neptune/operations/episode_node_ops.py"},"region":{"startLine":60}}}],"partialFingerprints":{"codehealthFindingId/v1":"b017d6b262de5626f2dacf4096f711dfe61b98657a0008f3df8ffed4c17cbc85"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 98 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neptune/operations/episode_node_ops.py"},"region":{"startLine":98}}}],"partialFingerprints":{"codehealthFindingId/v1":"010436a677313a1273fe0aee3525b4efcab461fdbd32d02c1650219d09f8ad28"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 60 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neptune/operations/entity_node_ops.py"},"region":{"startLine":60}}}],"partialFingerprints":{"codehealthFindingId/v1":"589aefc1e301c36ac406eaf2c93c456cdc9e96c491691ad2ae2fba811fe60877"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 107 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neptune/operations/entity_node_ops.py"},"region":{"startLine":107}}}],"partialFingerprints":{"codehealthFindingId/v1":"b6427011f0b356769f4a49dbac7af832942427a52ce7f3c97065dc674025c572"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 64 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neptune/operations/entity_edge_ops.py"},"region":{"startLine":64}}}],"partialFingerprints":{"codehealthFindingId/v1":"b12a9a0139baef252a9f84407f07d93786dd298d4b2df6ad34e4ff60dfef03a9"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 113 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neptune/operations/entity_edge_ops.py"},"region":{"startLine":113}}}],"partialFingerprints":{"codehealthFindingId/v1":"f4023ac05fbd6dfc5b0524c63f5265bebff620ca27c7fcaed4f34634b3fe66e4"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 69 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neptune/operations/community_node_ops.py"},"region":{"startLine":69}}}],"partialFingerprints":{"codehealthFindingId/v1":"79afb0dbffe18f5629b5b358c9543ec03e565890e610e2e94fe6cadd38b4a8a2"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 98 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neptune/operations/community_node_ops.py"},"region":{"startLine":98}}}],"partialFingerprints":{"codehealthFindingId/v1":"c5477fcb3f4e76ee0443b898cd32dff4229235b22838545e7a57f368c2f1fe9d"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 64 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neptune/operations/community_edge_ops.py"},"region":{"startLine":64}}}],"partialFingerprints":{"codehealthFindingId/v1":"67bda74b1ac4c1bf4b9b23db16ab4307919b9188dff04bb7681b0280ee00844f"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 81 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/driver/neptune/operations/community_edge_ops.py"},"region":{"startLine":81}}}],"partialFingerprints":{"codehealthFindingId/v1":"75746dfaa41a51384c0515bce00a0452a17dbc7ec6a374c8a816f7f75d7ed0a4"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060warning\u0060 at line 157 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.warning(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.warning(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/embedder/gemini.py"},"region":{"startLine":157}}}],"partialFingerprints":{"codehealthFindingId/v1":"a089e9bcf972ea6b86dfb9192ffcf7f4614cb6a53064abe6fa76df6388e3aecb"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060error\u0060 at line 180 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.error(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.error(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/embedder/gemini.py"},"region":{"startLine":180}}}],"partialFingerprints":{"codehealthFindingId/v1":"87f4cd21fa37e9ec5667a1c501548faf91d453c19972b097f8974601dba439b1"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060error\u0060 at line 58 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.error(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.error(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/embedder/azure_openai.py"},"region":{"startLine":58}}}],"partialFingerprints":{"codehealthFindingId/v1":"3524ad4ceb76bea0bee5379709edce237ef3e5a272f08ef902a5f1e493cfb874"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060error\u0060 at line 70 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.error(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.error(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/embedder/azure_openai.py"},"region":{"startLine":70}}}],"partialFingerprints":{"codehealthFindingId/v1":"53fcbfbb487d71a5f83139f3239c76122f830c678b89f36261e19f273d0bee3e"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 32 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/utils.py"},"region":{"startLine":32}}}],"partialFingerprints":{"codehealthFindingId/v1":"558c2a34f12e3129d6bbb269f03fdb342c93f2848a3b10b4d6f1284002d9f70c"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060error\u0060 at line 175 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.error(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.error(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/openai_generic_client.py"},"region":{"startLine":175}}}],"partialFingerprints":{"codehealthFindingId/v1":"35f41f6135e7136511d26a396709cdd6169bf9297f4032a785a7a9f0eab3d81a"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060error\u0060 at line 243 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.error(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.error(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/openai_base_client.py"},"region":{"startLine":243}}}],"partialFingerprints":{"codehealthFindingId/v1":"a8aaa822264ed15fa46231280ebdddbf0a9272327d74d333a0cff9c756da1053"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060error\u0060 at line 251 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.error(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.error(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/openai_base_client.py"},"region":{"startLine":251}}}],"partialFingerprints":{"codehealthFindingId/v1":"ec1644f8fc95361976da188421d794ceff2f67849ec15bd5e129771ddcfba216"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060error\u0060 at line 255 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.error(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.error(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/openai_base_client.py"},"region":{"startLine":255}}}],"partialFingerprints":{"codehealthFindingId/v1":"e7b6b239fe41b4222edb7c26878b34a485a6242fc1bb250e9d89322547ba49da"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060error\u0060 at line 332 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.error(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.error(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/openai_base_client.py"},"region":{"startLine":332}}}],"partialFingerprints":{"codehealthFindingId/v1":"a47d7d7da952cdc398941fc25add58a1dd8e996d7f4baf296be9b4696469cbd0"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060warning\u0060 at line 350 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.warning(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.warning(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/openai_base_client.py"},"region":{"startLine":350}}}],"partialFingerprints":{"codehealthFindingId/v1":"77c64a80a48dcb7c6113dbaabb7d82178da839d8789ff82e0fd3c684b11f47d6"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060error\u0060 at line 86 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.error(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.error(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/groq_client.py"},"region":{"startLine":86}}}],"partialFingerprints":{"codehealthFindingId/v1":"8bb86ae6363876ad552b2827110b3e2b3668453890c43c622d9af03767ace556"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060error\u0060 at line 369 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.error(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.error(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/gemini_client.py"},"region":{"startLine":369}}}],"partialFingerprints":{"codehealthFindingId/v1":"4ff4561677fd02df5266197a92dac0ed1b1b4ec0b5a88c483ac8d3941fd4e80c"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060warning\u0060 at line 454 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.warning(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.warning(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/gemini_client.py"},"region":{"startLine":454}}}],"partialFingerprints":{"codehealthFindingId/v1":"b19cc8bba746d191081fbaca61eabc99c27817a047678e1c6b200b21bffbe998"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060warning\u0060 at line 471 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.warning(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.warning(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/gemini_client.py"},"region":{"startLine":471}}}],"partialFingerprints":{"codehealthFindingId/v1":"a07112c6bbed0dce94b133604a2638cd7f0bf7e21004307d31f95006abeda890"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060error\u0060 at line 478 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.error(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.error(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/gemini_client.py"},"region":{"startLine":478}}}],"partialFingerprints":{"codehealthFindingId/v1":"f917deac0dd31a297aa452c816bbd20dc707200d67d4299c9fe88286755afe02"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060warning\u0060 at line 124 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.warning(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.warning(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/client.py"},"region":{"startLine":124}}}],"partialFingerprints":{"codehealthFindingId/v1":"1c8aa7a51bc275ca49c523cfe4db0c6d7d839be6fe7003a1ef5616538f98654a"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 258 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/client.py"},"region":{"startLine":258}}}],"partialFingerprints":{"codehealthFindingId/v1":"9fa65356896e6fadbaffb9cf6ebbe17f550d0c39e05066a012df15147b7fa10c"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060warning\u0060 at line 48 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.warning(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.warning(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/cache.py"},"region":{"startLine":48}}}],"partialFingerprints":{"codehealthFindingId/v1":"b83e9d5f9213985e41bc3b0f3fb9d6d4ef3704db2e46ca50748ddb21b40ed0f1"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060warning\u0060 at line 55 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.warning(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.warning(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/cache.py"},"region":{"startLine":55}}}],"partialFingerprints":{"codehealthFindingId/v1":"6e9dd7f71d40631c8b2876e37c422581d58c936e3434747ffe0be4a985233a1b"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060error\u0060 at line 433 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.error(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.error(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/anthropic_client.py"},"region":{"startLine":433}}}],"partialFingerprints":{"codehealthFindingId/v1":"d853622d366d4310323c415422470edaad2997b3f441fe2f018188b029188f9b"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060error\u0060 at line 437 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.error(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.error(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/anthropic_client.py"},"region":{"startLine":437}}}],"partialFingerprints":{"codehealthFindingId/v1":"041a004c17ee1b79fa575a730c03c618fc38f19aa04fa6cc734b7dbd2839fae9"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060warning\u0060 at line 456 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.warning(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.warning(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/llm_client/anthropic_client.py"},"region":{"startLine":456}}}],"partialFingerprints":{"codehealthFindingId/v1":"95f7ff2811bfa0a6de6f87b8e1aff6a494961a47ee12755957d4d30302653bdf"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 1255 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/search/search_utils.py"},"region":{"startLine":1255}}}],"partialFingerprints":{"codehealthFindingId/v1":"a79014135b98e3d29b1f638e3448872c7fe65d2947f50d07f7da111cfcd3c9a8"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 1946 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/search/search_utils.py"},"region":{"startLine":1946}}}],"partialFingerprints":{"codehealthFindingId/v1":"40035b1cc147544594ccfbd745d5bc62da104f2c9d7038add910cf6bae3c7e9b"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 254 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/search/search.py"},"region":{"startLine":254}}}],"partialFingerprints":{"codehealthFindingId/v1":"27e034af9a9492491bfaaf262dbb285e9ec827bb93b6c952fbd816ae1d9c44b0"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 151 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/node_operations.py"},"region":{"startLine":151}}}],"partialFingerprints":{"codehealthFindingId/v1":"d6ebaf2a1449dde5427f71012fd95d6854517d38b54b68e6d8f58ac8cb455428"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 161 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/node_operations.py"},"region":{"startLine":161}}}],"partialFingerprints":{"codehealthFindingId/v1":"25df69d0b9e850f4311d8ee97ff18c08debc33907978a5a661f9640f6f1fdfbe"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 359 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/node_operations.py"},"region":{"startLine":359}}}],"partialFingerprints":{"codehealthFindingId/v1":"3f44a07a75adc3449981ea9024d6adb5ede4f352ddfb2de97a774ad53a08a286"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 380 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/node_operations.py"},"region":{"startLine":380}}}],"partialFingerprints":{"codehealthFindingId/v1":"993aa8e51dddabdec2603874af6c058e0ae2855428cb134edbb031c60736a075"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 111 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/edge_operations.py"},"region":{"startLine":111}}}],"partialFingerprints":{"codehealthFindingId/v1":"e11ba8f6bc51d238e07f45bc124eab20b2e48020c09f19e601e5ee30412167ad"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 295 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/edge_operations.py"},"region":{"startLine":295}}}],"partialFingerprints":{"codehealthFindingId/v1":"ef0fc47faf571256560241693343cef5ec92c11420566f0d75e6a7f7931c533c"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060warning\u0060 at line 339 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.warning(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.warning(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/edge_operations.py"},"region":{"startLine":339}}}],"partialFingerprints":{"codehealthFindingId/v1":"999f2d038f6dcf0fa801763a65301394fd444ab43d96e9d6a3189c5b78732493"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 375 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/edge_operations.py"},"region":{"startLine":375}}}],"partialFingerprints":{"codehealthFindingId/v1":"5e65602bef7134aefe7a2ef207a17df80a98033c8d49a9e245b6b205c67e5488"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 379 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/edge_operations.py"},"region":{"startLine":379}}}],"partialFingerprints":{"codehealthFindingId/v1":"500c27efab438ccc55902684a0f1c5f9a1c04103519669a729a4c0a82cfafa0c"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 587 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/edge_operations.py"},"region":{"startLine":587}}}],"partialFingerprints":{"codehealthFindingId/v1":"afe891b0ae1799175606b209616df6e252359d5c0a7729458f2d1b98d80a2e7c"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 628 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/edge_operations.py"},"region":{"startLine":628}}}],"partialFingerprints":{"codehealthFindingId/v1":"157d9f1995e0dc25bbbde3fd8605a445c07126d6cee27196f666110ba1651e58"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 629 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/edge_operations.py"},"region":{"startLine":629}}}],"partialFingerprints":{"codehealthFindingId/v1":"835b4487f58ac2df7ebedfcc167b9c684e14fd9d860d0bbfdcc92f0d8d9c03d5"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 1083 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/edge_operations.py"},"region":{"startLine":1083}}}],"partialFingerprints":{"codehealthFindingId/v1":"27c10f5333a9cd41779405295500e47c7cac271586e66a3e6a701ff6a5f1916f"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 251 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/community_operations.py"},"region":{"startLine":251}}}],"partialFingerprints":{"codehealthFindingId/v1":"f2058e7c5034871c582c2934daf3fee956987a6efcce803d23e3ea153756b798"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 178 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/combined_extraction.py"},"region":{"startLine":178}}}],"partialFingerprints":{"codehealthFindingId/v1":"fe2e700d67f35e4b6b3ea793b0ae6320755c548b51ad8db7a2a3d89accb8c743"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 201 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/combined_extraction.py"},"region":{"startLine":201}}}],"partialFingerprints":{"codehealthFindingId/v1":"ccaf53719519d7e0294928b9a50c4e65aab6e8d302d10112083dc6ece79a4bf8"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060debug\u0060 at line 400 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.debug(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.debug(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/utils/maintenance/combined_extraction.py"},"region":{"startLine":400}}}],"partialFingerprints":{"codehealthFindingId/v1":"30a9c1e9d26ec5d7cecec1118afec185abb45c02a215795c6881e18cb912400b"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060warning\u0060 at line 250 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.warning(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.warning(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":250}}}],"partialFingerprints":{"codehealthFindingId/v1":"119896d50b7d7ba8edf7adc36f233252294a2209f08116ace9475e5aa9044026"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060warning\u0060 at line 256 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.warning(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.warning(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":256}}}],"partialFingerprints":{"codehealthFindingId/v1":"e940c6ef56bc0066422d4754f8f587a3e2ed6cae8688ec04d53dc9f363f061d8"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060info\u0060 at line 363 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.info(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.info(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":363}}}],"partialFingerprints":{"codehealthFindingId/v1":"83ca016831bb7be2433eb97e035e95f3151cf58e4174e264ddc738304cbc5c7f"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060info\u0060 at line 370 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.info(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.info(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":370}}}],"partialFingerprints":{"codehealthFindingId/v1":"8f9af58885c31e190b860483afa71e5b993a135007122fafc8f472b0b4022914"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060info\u0060 at line 376 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.info(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.info(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":376}}}],"partialFingerprints":{"codehealthFindingId/v1":"24aed5e2c12ee153d24a502790ebe39d8148806d99241871d4a815803fe29319"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060info\u0060 at line 382 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.info(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.info(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":382}}}],"partialFingerprints":{"codehealthFindingId/v1":"9cd110133f573ab86730aab4fc022a2856445bac235700b1639e859bfa4f8238"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060info\u0060 at line 386 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.info(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.info(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":386}}}],"partialFingerprints":{"codehealthFindingId/v1":"5f47f66e6607c060c6669c73963ef45d75736cb457463f04478f463652181b89"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060info\u0060 at line 387 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.info(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.info(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":387}}}],"partialFingerprints":{"codehealthFindingId/v1":"d8a9c4b2895daa19056b319e0eaff3edf85d8762e5c0eea3406c122a1e18e3fb"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060error\u0060 at line 390 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.error(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.error(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":390}}}],"partialFingerprints":{"codehealthFindingId/v1":"59c7204f36df51b3b5ed9adca60f6486a913a4e81439e87d95f47977de9f92c7"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060warning\u0060 at line 506 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.warning(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.warning(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":506}}}],"partialFingerprints":{"codehealthFindingId/v1":"a85a9fab1321ed908543fe6153f8448c16b84285bf1b6c49418a74120cd2fee3"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060error\u0060 at line 534 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.error(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.error(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":534}}}],"partialFingerprints":{"codehealthFindingId/v1":"32fbe2b1815852a8ea4c9eebdf96f9fe55d4c4a2525d6a7b7d51d71970cd4880"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060error\u0060 at line 611 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.error(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.error(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":611}}}],"partialFingerprints":{"codehealthFindingId/v1":"4349cabff724621abebfdc6f394631632602c485d0cf95c4a65c6a4faaa3018f"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060error\u0060 at line 691 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.error(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.error(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":691}}}],"partialFingerprints":{"codehealthFindingId/v1":"26793994fba90730483e02090e4551bddaaf6ca2e3f7fd599223fe5a6a9b1e48"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060error\u0060 at line 724 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.error(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.error(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":724}}}],"partialFingerprints":{"codehealthFindingId/v1":"51ce00505195ade0a773b50df6977f3b12594f9d248aa556eda4b15bb5603be1"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060error\u0060 at line 758 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.error(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.error(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":758}}}],"partialFingerprints":{"codehealthFindingId/v1":"acbe11d29e103b7ef4cbab5c08659a32a2b9dc4d0c0a3ba6653c9ef367d0477c"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060error\u0060 at line 790 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.error(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.error(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":790}}}],"partialFingerprints":{"codehealthFindingId/v1":"06e045bb1a3a6311a6278476b5d18a26e8019df164bec3bc685a07212050751a"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060error\u0060 at line 870 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.error(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.error(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":870}}}],"partialFingerprints":{"codehealthFindingId/v1":"65cbebbf460587861edb5a0d962403a13bb1a937224003720ccd5c814a5f4ff0"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060error\u0060 at line 925 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.error(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.error(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":925}}}],"partialFingerprints":{"codehealthFindingId/v1":"210fea90e91e51ee73c220f1dd879d1384e28ea0c029805b4753d8b412a0a9c5"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060error\u0060 at line 979 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.error(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.error(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":979}}}],"partialFingerprints":{"codehealthFindingId/v1":"abc6e2c04fb90e8f6871ee0deb8009badb996619c117e0aa0c273ac4234cf7a3"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060error\u0060 at line 1055 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.error(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.error(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":1055}}}],"partialFingerprints":{"codehealthFindingId/v1":"1a7b94e94b7f161757a99a6d11bd2e32ed3052b4632deee84030c8734b93ef51"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060error\u0060 at line 1097 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.error(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.error(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":1097}}}],"partialFingerprints":{"codehealthFindingId/v1":"556a674a6ed1546b305442f7bc6fd41331eee472243d9200fb113e1a5168c281"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060error\u0060 at line 1141 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.error(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.error(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":1141}}}],"partialFingerprints":{"codehealthFindingId/v1":"80fa99fce29e74dcc90e4ab72ffce8536c11618cf1e162701b0270938bb72829"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060error\u0060 at line 1171 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.error(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.error(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":1171}}}],"partialFingerprints":{"codehealthFindingId/v1":"d5907ea24e619444e45533658f4336e75aeace68ce843cfad92b4dc64bc3357d"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060info\u0060 at line 1278 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.info(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.info(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":1278}}}],"partialFingerprints":{"codehealthFindingId/v1":"17831fed4982807c0b462c6a8227ca52901e8d16f5c2de0b7ab101d7396d9949"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060info\u0060 at line 1279 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.info(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.info(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":1279}}}],"partialFingerprints":{"codehealthFindingId/v1":"7f41f0843820ece007ee5bc7d43626b85b538f815b510855d57b051d6a23e3c2"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060info\u0060 at line 1280 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.info(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.info(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":1280}}}],"partialFingerprints":{"codehealthFindingId/v1":"4071e253edb9a3ba8ea29435daf2d96802cc49f901b5bb05ba56e34debe11959"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060info\u0060 at line 1281 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.info(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.info(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":1281}}}],"partialFingerprints":{"codehealthFindingId/v1":"ab429666071fbfea150adf32c2d0b5e624653f49ed414829fbd5533926cb894f"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060info\u0060 at line 1282 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.info(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.info(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":1282}}}],"partialFingerprints":{"codehealthFindingId/v1":"d00fb2fa31a8a9d73dc00054e035c1ea82030ffb6a8072b954cf26fd78ac3f55"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060info\u0060 at line 1289 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.info(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.info(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":1289}}}],"partialFingerprints":{"codehealthFindingId/v1":"a6c22b2b73da0ba17b21a6a40e46dfe8025ecdc86515b4433e9f89f4dfb2e3e5"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060info\u0060 at line 1295 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.info(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.info(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":1295}}}],"partialFingerprints":{"codehealthFindingId/v1":"9afb36651374ec49dcef9cd49261b3a0771b98c80fd96a02207fe87711ee6394"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060info\u0060 at line 1330 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.info(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.info(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":1330}}}],"partialFingerprints":{"codehealthFindingId/v1":"13381d88def8283f7186cb8f8ece8338d083f796b8fe604dd7e49fb5affc3014"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060info\u0060 at line 1334 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.info(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.info(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":1334}}}],"partialFingerprints":{"codehealthFindingId/v1":"7912b82e7bdd7d6e4770d85e3a778314cdb57f0b83b31d4e10a29045a4a94091"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060info\u0060 at line 1335 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.info(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.info(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":1335}}}],"partialFingerprints":{"codehealthFindingId/v1":"ebdd98f2a749bcfe619bdbb185d4e3e8ccc68d8525ad7c0f65af718aee047029"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060info\u0060 at line 1340 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.info(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.info(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":1340}}}],"partialFingerprints":{"codehealthFindingId/v1":"36f98f4a3648ccaa7aec7aa2feaf6584f405197cec3ee3e5585a2d0f4cbf3106"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060info\u0060 at line 1345 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.info(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.info(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":1345}}}],"partialFingerprints":{"codehealthFindingId/v1":"e8d304ee872cb1e83fc68ce77b587d04d1412651296e9e5ce133a3a0ddd209f8"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060info\u0060 at line 1346 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.info(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.info(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":1346}}}],"partialFingerprints":{"codehealthFindingId/v1":"c2387eab8be607227b1429158e208a015628eee286d54b6233b116e3cc6cbd5b"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060info\u0060 at line 1351 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.info(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.info(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":1351}}}],"partialFingerprints":{"codehealthFindingId/v1":"7a2efac9154f31fc309898a61f74349549e6425acd345cceae33a49160027908"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060error\u0060 at line 1374 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.error(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.error(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/graphiti_mcp_server.py"},"region":{"startLine":1374}}}],"partialFingerprints":{"codehealthFindingId/v1":"1058c358def9e78862f2f89b76234d936fd9acce37d5d211159f993d464c9bb5"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060info\u0060 at line 55 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.info(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.info(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/services/queue_service.py"},"region":{"startLine":55}}}],"partialFingerprints":{"codehealthFindingId/v1":"32b432b64586830e84bb60c331c3187f4e0ccbef2045e19d9e9d12766a8d7a31"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060error\u0060 at line 68 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.error(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.error(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/services/queue_service.py"},"region":{"startLine":68}}}],"partialFingerprints":{"codehealthFindingId/v1":"60699ef7f6d0acc1fe42113ef0de9dd9ca385fce1d36f3e48115ed432b329a07"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060info\u0060 at line 75 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.info(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.info(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/services/queue_service.py"},"region":{"startLine":75}}}],"partialFingerprints":{"codehealthFindingId/v1":"7dda0e956b423ef6ded001136d7c9025decf088f246e549bd1a64d7294d77789"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060error\u0060 at line 77 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.error(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.error(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/services/queue_service.py"},"region":{"startLine":77}}}],"partialFingerprints":{"codehealthFindingId/v1":"6d22756c90e31f3a904c707ec39d47ccea77590103a37709db32ae099916a089"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060info\u0060 at line 80 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.info(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.info(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/services/queue_service.py"},"region":{"startLine":80}}}],"partialFingerprints":{"codehealthFindingId/v1":"3de010e1baf58f304a622192fb48cbb93ed14d8564bfd4ecd7628602c1179464"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060info\u0060 at line 155 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.info(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.info(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/services/queue_service.py"},"region":{"startLine":155}}}],"partialFingerprints":{"codehealthFindingId/v1":"f29a2c736c5fd52c90cd34f5db3967e88e63af0bb00863c687b1b91cd3bd017d"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060info\u0060 at line 177 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.info(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.info(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/services/queue_service.py"},"region":{"startLine":177}}}],"partialFingerprints":{"codehealthFindingId/v1":"90945fb0fdd6b8212c2cc19e8f44a1695567b39896c3b465310639506f16ab39"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060error\u0060 at line 180 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.error(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.error(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/services/queue_service.py"},"region":{"startLine":180}}}],"partialFingerprints":{"codehealthFindingId/v1":"ac92c5d33ef939c775bcac633c6990f3d4660c9403b9a1858c63ea9c96c5e807"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060info\u0060 at line 91 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.info(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.info(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/services/factories.py"},"region":{"startLine":91}}}],"partialFingerprints":{"codehealthFindingId/v1":"c899e3542a0b59b78a9c4b51af8f1f1a0c25ad31f98d4c8e40d876e7364fe16f"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060info\u0060 at line 475 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.info(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.info(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/services/factories.py"},"region":{"startLine":475}}}],"partialFingerprints":{"codehealthFindingId/v1":"3ba301188ee74faab011fde2c93796da503d06a00b8e26cf6863c28eddc5ca7e"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060info\u0060 at line 498 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.info(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.info(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/services/factories.py"},"region":{"startLine":498}}}],"partialFingerprints":{"codehealthFindingId/v1":"ca59231455680035efa3a39e440a61db1173f3ffd57278b0dcd57d957f00c1b7"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060info\u0060 at line 510 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.info(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.info(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"mcp_server/src/services/factories.py"},"region":{"startLine":510}}}],"partialFingerprints":{"codehealthFindingId/v1":"03d5b279001d803c18dff7561181644865c00f5a45a30eaf29d421c36fcd9774"}},{"ruleId":"X4","level":"note","message":{"text":"Interpolated log message defeats structured logging: \u0060warning\u0060 at line 50 is handed an f-string as its message, so the logger receives one finished string \u2014 the values in it cannot be queried, filtered or aggregated as fields, every occurrence reads as a different message, and the string is built even when the record is filtered out. Pass the values beside a constant message instead: \u0060logger.warning(\u0022order %s moved to %s\u0022, order_id, status)\u0060 with the standard library\u0027s \u0060logging\u0060, which keeps the message and its arguments apart on the record, or \u0060log.warning(\u0022order moved\u0022, order_id=order_id, status=status)\u0060 with structlog."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"server/graph_service/zep_graphiti.py"},"region":{"startLine":50}}}],"partialFingerprints":{"codehealthFindingId/v1":"cbf5e6eef10400efd8bbc419420eccc75ec03f7aaf5b4305e410be0466ee40ed"}},{"ruleId":"X7","level":"note","message":{"text":"Silent fallback default in catch: \u0060get_anonymous_id\u0060 swallows every exception into \u0060return \u0027UNKNOWN\u0027\u0060 \u2014 a data error becomes a silent behavior change with no trail. Log the failure or let it raise. If that constant is the correct answer rather than a stand-in for a value that could not be read, say so in a comment on the handler or in the docstring, and the row stops firing."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/telemetry/telemetry.py"},"region":{"startLine":61}}}],"partialFingerprints":{"codehealthFindingId/v1":"4d81eaac18bc73c8bf38705d4a551185b817ea55db9eda6e500260a114865d87"}},{"ruleId":"X7","level":"note","message":{"text":"Silent fallback default in catch: \u0060get_graphiti_version\u0060 swallows every exception into \u0060return \u0027unknown\u0027\u0060 \u2014 a data error becomes a silent behavior change with no trail. Log the failure or let it raise. If that constant is the correct answer rather than a stand-in for a value that could not be read, say so in a comment on the handler or in the docstring, and the row stops firing."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"graphiti_core/telemetry/telemetry.py"},"region":{"startLine":72}}}],"partialFingerprints":{"codehealthFindingId/v1":"cdfc3a337e07aabd41fdc2f9b5f01d87f5dcee2a7f110a36541850474c696ec3"}}],"taxonomies":[{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d","organization":"MITRE","informationUri":"https://cwe.mitre.org/","isComprehensive":false,"shortDescription":{"text":"The MITRE Common Weakness Enumeration (CWE)."},"taxa":[{"id":"CWE-1032","guid":"5f21e517-68aa-a650-9a25-5771ef024637","name":"OWASP Top Ten \u2014 Security Misconfiguration category","shortDescription":{"text":"OWASP Top Ten \u2014 Security Misconfiguration category"},"helpUri":"https://cwe.mitre.org/data/definitions/1032.html"},{"id":"CWE-1059","guid":"a2381a08-60f6-9554-a8b8-f3018cfaaca5","name":"Insufficient Technical Documentation","shortDescription":{"text":"Insufficient Technical Documentation"},"helpUri":"https://cwe.mitre.org/data/definitions/1059.html"},{"id":"CWE-1357","guid":"e4d2e772-757e-0a5c-bd7d-77052949d866","name":"Reliance on Insufficiently Trustworthy Component","shortDescription":{"text":"Reliance on Insufficiently Trustworthy Component"},"helpUri":"https://cwe.mitre.org/data/definitions/1357.html"},{"id":"CWE-1395","guid":"800e09e7-c11a-8654-9fa6-86f398995fed","name":"Dependency on Vulnerable Third-Party Component","shortDescription":{"text":"Dependency on Vulnerable Third-Party Component"},"helpUri":"https://cwe.mitre.org/data/definitions/1395.html"},{"id":"CWE-16","guid":"659db3ea-affc-8453-8add-c1218fbfcb92","name":"Configuration","shortDescription":{"text":"Configuration"},"helpUri":"https://cwe.mitre.org/data/definitions/16.html"},{"id":"CWE-259","guid":"ae9ad959-fbb6-9d5e-892d-3dca66da0b69","name":"Use of Hard-coded Password","shortDescription":{"text":"Use of Hard-coded Password"},"helpUri":"https://cwe.mitre.org/data/definitions/259.html"},{"id":"CWE-353","guid":"09d7e902-d4ee-f05d-ae6c-0a1554d0c18f","name":"Missing Support for Integrity Check","shortDescription":{"text":"Missing Support for Integrity Check"},"helpUri":"https://cwe.mitre.org/data/definitions/353.html"},{"id":"CWE-494","guid":"b8a65e0d-e459-4a55-a931-fc1136482375","name":"Download of Code Without Integrity Check","shortDescription":{"text":"Download of Code Without Integrity Check"},"helpUri":"https://cwe.mitre.org/data/definitions/494.html"},{"id":"CWE-506","guid":"401d6455-56e3-0552-9a39-f77461673e3f","name":"Embedded Malicious Code","shortDescription":{"text":"Embedded Malicious Code"},"helpUri":"https://cwe.mitre.org/data/definitions/506.html"},{"id":"CWE-522","guid":"71fb233e-ce6a-ae57-9419-ef8373540b09","name":"Insufficiently Protected Credentials","shortDescription":{"text":"Insufficiently Protected Credentials"},"helpUri":"https://cwe.mitre.org/data/definitions/522.html"},{"id":"CWE-552","guid":"3492436b-eca2-9c54-9ba3-5dade427c903","name":"Files or Directories Accessible to External Parties","shortDescription":{"text":"Files or Directories Accessible to External Parties"},"helpUri":"https://cwe.mitre.org/data/definitions/552.html"},{"id":"CWE-732","guid":"1da27e8f-b330-7650-ab63-bd61953eae5d","name":"Incorrect Permission Assignment for Critical Resource","shortDescription":{"text":"Incorrect Permission Assignment for Critical Resource"},"helpUri":"https://cwe.mitre.org/data/definitions/732.html"},{"id":"CWE-77","guid":"332c8ade-6612-9f56-a06b-d8d90b1a8750","name":"Improper Neutralization of Special Elements used in a Command (\u0027Command Injection\u0027)","shortDescription":{"text":"Improper Neutralization of Special Elements used in a Command (\u0027Command Injection\u0027)"},"helpUri":"https://cwe.mitre.org/data/definitions/77.html"},{"id":"CWE-78","guid":"2e31ceaf-c7ae-2e5e-9661-cfb1362789cf","name":"Improper Neutralization of Special Elements used in an OS Command (\u0027OS Command Injection\u0027)","shortDescription":{"text":"Improper Neutralization of Special Elements used in an OS Command (\u0027OS Command Injection\u0027)"},"helpUri":"https://cwe.mitre.org/data/definitions/78.html"},{"id":"CWE-79","guid":"fd45580b-e8c4-fc5e-8c2f-aa8fab0b4dbf","name":"Improper Neutralization of Input During Web Page Generation (\u0027Cross-site Scripting\u0027)","shortDescription":{"text":"Improper Neutralization of Input During Web Page Generation (\u0027Cross-site Scripting\u0027)"},"helpUri":"https://cwe.mitre.org/data/definitions/79.html"},{"id":"CWE-798","guid":"5e8f057d-fee3-995a-a0cb-9fc5b0d174d1","name":"Use of Hard-coded Credentials","shortDescription":{"text":"Use of Hard-coded Credentials"},"helpUri":"https://cwe.mitre.org/data/definitions/798.html"},{"id":"CWE-829","guid":"13c33925-97fb-5a5e-b40c-56d328b8a4d7","name":"Inclusion of Functionality from Untrusted Control Sphere","shortDescription":{"text":"Inclusion of Functionality from Untrusted Control Sphere"},"helpUri":"https://cwe.mitre.org/data/definitions/829.html"},{"id":"CWE-89","guid":"6d08fdad-37eb-c150-bbf0-d7d946863407","name":"Improper Neutralization of Special Elements used in an SQL Command (\u0027SQL Injection\u0027)","shortDescription":{"text":"Improper Neutralization of Special Elements used in an SQL Command (\u0027SQL Injection\u0027)"},"helpUri":"https://cwe.mitre.org/data/definitions/89.html"},{"id":"CWE-937","guid":"16f316ae-415c-b354-a59b-1f7905f756e9","name":"Using Components with Known Vulnerabilities","shortDescription":{"text":"Using Components with Known Vulnerabilities"},"helpUri":"https://cwe.mitre.org/data/definitions/937.html"},{"id":"CWE-939","guid":"12397742-f2c4-8254-8012-482c3356c551","name":"CWE-939","shortDescription":{"text":"CWE-939"},"helpUri":"https://cwe.mitre.org/data/definitions/939.html"},{"id":"CWE-94","guid":"75e7f50c-6c2f-dd52-bf40-bf6c52b861fd","name":"Improper Control of Generation of Code (\u0027Code Injection\u0027)","shortDescription":{"text":"Improper Control of Generation of Code (\u0027Code Injection\u0027)"},"helpUri":"https://cwe.mitre.org/data/definitions/94.html"}]},{"name":"OWASP Top 10","guid":"7d1f0c2e-4b6a-4e39-9a51-0b2e8c3d5f61","version":"2025","organization":"OWASP Foundation","informationUri":"https://top10.owasp.org/2025/","isComprehensive":false,"shortDescription":{"text":"The OWASP Top 10:2025 web application security risks."},"taxa":[{"id":"A02:2025","guid":"668dee9e-41bc-6354-97d0-fdf8f40c50c1","name":"Security Misconfiguration","shortDescription":{"text":"Security Misconfiguration"},"helpUri":"https://top10.owasp.org/2025/A02_2025-Security_Misconfiguration/"},{"id":"A03:2025","guid":"168e227c-7735-2e57-bc6e-997affe644c5","name":"Software Supply Chain Failures","shortDescription":{"text":"Software Supply Chain Failures"},"helpUri":"https://top10.owasp.org/2025/A03_2025-Software_Supply_Chain_Failures/"},{"id":"A05:2025","guid":"87e86033-3231-8458-8e4a-5cb82743553a","name":"Injection","shortDescription":{"text":"Injection"},"helpUri":"https://top10.owasp.org/2025/A05_2025-Injection/"},{"id":"A06:2025","guid":"491554d7-b8e4-9652-91a9-53700dc92ece","name":"Insecure Design","shortDescription":{"text":"Insecure Design"},"helpUri":"https://top10.owasp.org/2025/A06_2025-Insecure_Design/"},{"id":"A07:2025","guid":"334ddd78-539d-d35c-83dd-253647eb7eaf","name":"Authentication Failures","shortDescription":{"text":"Authentication Failures"},"helpUri":"https://top10.owasp.org/2025/A07_2025-Authentication_Failures/"},{"id":"A08:2025","guid":"84b0c297-710e-9a5f-9335-936ecb260c36","name":"Software or Data Integrity Failures","shortDescription":{"text":"Software or Data Integrity Failures"},"helpUri":"https://top10.owasp.org/2025/A08_2025-Software_or_Data_Integrity_Failures/"}]},{"name":"OWASP Top 10 CI/CD Security Risks","guid":"3c9b2a6e-5d14-4f7b-8e02-6a1d9c4b7f35","organization":"OWASP Foundation","informationUri":"https://owasp.org/www-project-top-10-ci-cd-security-risks/","isComprehensive":false,"shortDescription":{"text":"The OWASP Top 10 CI/CD Security Risks."},"taxa":[{"id":"CICD-SEC-3","guid":"458c83a6-ebf7-cf55-8cc3-b4d949bbf271","name":"Dependency Chain Abuse","shortDescription":{"text":"Dependency Chain Abuse"},"helpUri":"https://owasp.github.io/www-project-top-10-ci-cd-security-risks/CICD-SEC-03-Dependency-Chain-Abuse"},{"id":"CICD-SEC-9","guid":"b3d36727-0395-5b51-94c4-5e173d62eab3","name":"Improper Artifact Integrity Validation","shortDescription":{"text":"Improper Artifact Integrity Validation"},"helpUri":"https://owasp.github.io/www-project-top-10-ci-cd-security-risks/CICD-SEC-09-Improper-Artifact-Integrity-Validation"}]}],"properties":{"codehealthPublication":{"public":true,"notice":"This is the PUBLIC form of this artifact. Findings are listed in full, but the details of SECURITY findings \u2014 which rule fired, in which file, on which line, and how to fix it \u2014 are deliberately withheld, and any secret-scanner results are excluded entirely. Where detail is absent here it was REMOVED FOR PUBLICATION; it is not missing from the analysis. The complete artifact is available from the repository owner.","securityFindingsRedacted":35,"secretScannerRunsExcluded":0}},"redactionTokens":["A security finding was recorded here. Its details are withheld on the public artifact \u2014 ask the repository owner for the full report."]}]}