{"$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":"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":"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"]}],"properties":{"cwe":["CWE-798","CWE-259"]}},{"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":"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":"D20","name":"ADR Quality","shortDescription":{"text":"ADR Quality"},"helpUri":"https://codehealth.canine.dev/dimensions/D20"},{"id":"D21","name":"Naming Consistency","shortDescription":{"text":"Naming Consistency"},"helpUri":"https://codehealth.canine.dev/dimensions/D21"},{"id":"D22","name":"Internal API Consistency","shortDescription":{"text":"Internal API Consistency"},"helpUri":"https://codehealth.canine.dev/dimensions/D22"},{"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"]}],"properties":{"cwe":["CWE-798","CWE-259"]}},{"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"]}],"properties":{"cwe":["CWE-79","CWE-89","CWE-78","CWE-94","CWE-77"]}},{"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"]}],"properties":{"cwe":["CWE-1357","CWE-494"]}},{"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"]}],"properties":{"cwe":["CWE-1059"]}},{"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":"AXB2","name":"Runtime readiness","shortDescription":{"text":"Runtime readiness"},"helpUri":"https://codehealth.canine.dev/dimensions/AXB2"},{"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":"P12","name":"CI test-gate honesty","shortDescription":{"text":"CI test-gate honesty"},"helpUri":"https://codehealth.canine.dev/dimensions/P12"},{"id":"P3","name":"Security \u0026 performance tooling","shortDescription":{"text":"Security \u0026 performance tooling"},"helpUri":"https://codehealth.canine.dev/dimensions/P3"},{"id":"P6","name":"Release Hygiene","shortDescription":{"text":"Release Hygiene"},"helpUri":"https://codehealth.canine.dev/dimensions/P6"},{"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":"cli._cmd_install (cyclomatic 47): cli._cmd_install has cyclomatic complexity 47 (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. This file is where this pass\u0027s cyclomatic complexity CONCENTRATES: agent_reach/cli.py holds 7 of the 12 methods over the threshold \u2014 including the worst \u2014 and 121 of the 162 points over it (75%), 6.1\u00D7 the next-largest file (agent_reach/channels/xiaohongshu.py at 20). No single row can show this, because each is measured only against the threshold: reducing this one file moves this dimension further than any other file in the repository."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"agent_reach/cli.py"},"region":{"startLine":263}}}],"partialFingerprints":{"codehealthFindingId/v1":"441012191a9c5bf60c90e6b044943aabb151251389d48cfd730a8748af41c599"}},{"ruleId":"D1","level":"warning","message":{"text":"cli._install_system_deps (cyclomatic 44): cli._install_system_deps has cyclomatic complexity 44 (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. This file is where this pass\u0027s cyclomatic complexity CONCENTRATES: agent_reach/cli.py holds 7 of the 12 methods over the threshold \u2014 including the worst \u2014 and 121 of the 162 points over it (75%), 6.1\u00D7 the next-largest file (agent_reach/channels/xiaohongshu.py at 20). No single row can show this, because each is measured only against the threshold: reducing this one file moves this dimension further than any other file in the repository."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"agent_reach/cli.py"},"region":{"startLine":666}}}],"partialFingerprints":{"codehealthFindingId/v1":"ad424d5788e314286e011a14e71b2994b23709d5a1f5110c9c9bf0d76d481d3d"}},{"ruleId":"D1","level":"warning","message":{"text":"cli._configure_xhs_cookies (cyclomatic 39): cli._configure_xhs_cookies has cyclomatic complexity 39 (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. This file is where this pass\u0027s cyclomatic complexity CONCENTRATES: agent_reach/cli.py holds 7 of the 12 methods over the threshold \u2014 including the worst \u2014 and 121 of the 162 points over it (75%), 6.1\u00D7 the next-largest file (agent_reach/channels/xiaohongshu.py at 20). No single row can show this, because each is measured only against the threshold: reducing this one file moves this dimension further than any other file in the repository."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"agent_reach/cli.py"},"region":{"startLine":1637}}}],"partialFingerprints":{"codehealthFindingId/v1":"02cf47b349e6aca54e385a90372df653de2809d7943d5e42f0b2fa96e5fcfd55"}},{"ruleId":"D1","level":"warning","message":{"text":"xiaohongshu._clean_note (cyclomatic 35): xiaohongshu._clean_note 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":"agent_reach/channels/xiaohongshu.py"},"region":{"startLine":73}}}],"partialFingerprints":{"codehealthFindingId/v1":"26a84d3c63249e08773e3a5c0f423adaf672f34f3754bb3ce7cfb337c75cc7c5"}},{"ruleId":"D1","level":"warning","message":{"text":"cli.main (cyclomatic 32): cli.main has cyclomatic complexity 32 (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. This file is where this pass\u0027s cyclomatic complexity CONCENTRATES: agent_reach/cli.py holds 7 of the 12 methods over the threshold \u2014 including the worst \u2014 and 121 of the 162 points over it (75%), 6.1\u00D7 the next-largest file (agent_reach/channels/xiaohongshu.py at 20). No single row can show this, because each is measured only against the threshold: reducing this one file moves this dimension further than any other file in the repository."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"agent_reach/cli.py"},"region":{"startLine":68}}}],"partialFingerprints":{"codehealthFindingId/v1":"8e2082c182972787f429b86490bbb5e0b24efbacbc57e8620549c74004d8c3a0"}},{"ruleId":"D1","level":"warning","message":{"text":"cli._cmd_configure (cyclomatic 26): cli._cmd_configure 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. This file is where this pass\u0027s cyclomatic complexity CONCENTRATES: agent_reach/cli.py holds 7 of the 12 methods over the threshold \u2014 including the worst \u2014 and 121 of the 162 points over it (75%), 6.1\u00D7 the next-largest file (agent_reach/channels/xiaohongshu.py at 20). No single row can show this, because each is measured only against the threshold: reducing this one file moves this dimension further than any other file in the repository."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"agent_reach/cli.py"},"region":{"startLine":1425}}}],"partialFingerprints":{"codehealthFindingId/v1":"14eec1428b8490f729283d018f849500e9f297f4cd9e256c68611010339c8f0d"}},{"ruleId":"D1","level":"warning","message":{"text":"doctor.format_report (cyclomatic 25): doctor.format_report has cyclomatic complexity 25 (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":"agent_reach/doctor.py"},"region":{"startLine":57}}}],"partialFingerprints":{"codehealthFindingId/v1":"7ad841009f783502b88a5da556678f911c278a45cbe8947d34baa4c0aa512302"}},{"ruleId":"D1","level":"warning","message":{"text":"cli._cmd_uninstall (cyclomatic 22): cli._cmd_uninstall 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. This file is where this pass\u0027s cyclomatic complexity CONCENTRATES: agent_reach/cli.py holds 7 of the 12 methods over the threshold \u2014 including the worst \u2014 and 121 of the 162 points over it (75%), 6.1\u00D7 the next-largest file (agent_reach/channels/xiaohongshu.py at 20). No single row can show this, because each is measured only against the threshold: reducing this one file moves this dimension further than any other file in the repository."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"agent_reach/cli.py"},"region":{"startLine":1875}}}],"partialFingerprints":{"codehealthFindingId/v1":"acce3714cfb501c0af01e5db40c28aa39084111ca1224f84f76c2a8e17d2108f"}},{"ruleId":"D1","level":"warning","message":{"text":"cookie_extract.extract_all (cyclomatic 20): cookie_extract.extract_all 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":"agent_reach/cookie_extract.py"},"region":{"startLine":209}}}],"partialFingerprints":{"codehealthFindingId/v1":"69451ddcb596cc102e66149dc203a1aca973440e43cec8e02016455c9283298f"}},{"ruleId":"D1","level":"warning","message":{"text":"boss._cdp_zhipin_login_cookie (cyclomatic 19): boss._cdp_zhipin_login_cookie 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":"agent_reach/channels/boss.py"},"region":{"startLine":184}}}],"partialFingerprints":{"codehealthFindingId/v1":"15b94fe4230f016f707fc60ac60d50a482f62257b3427fe862b60378f02ffd2e"}},{"ruleId":"D1","level":"warning","message":{"text":"YouTubeChannel.check (cyclomatic 17): YouTubeChannel.check 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":"agent_reach/channels/youtube.py"},"region":{"startLine":50}}}],"partialFingerprints":{"codehealthFindingId/v1":"df0d3378b086e722754ab65925a275a8e16a3e316cde3edc17ffd9cc726987b1"}},{"ruleId":"D1","level":"warning","message":{"text":"cli._cmd_watch (cyclomatic 16): cli._cmd_watch has cyclomatic complexity 16 (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. This file is where this pass\u0027s cyclomatic complexity CONCENTRATES: agent_reach/cli.py holds 7 of the 12 methods over the threshold \u2014 including the worst \u2014 and 121 of the 162 points over it (75%), 6.1\u00D7 the next-largest file (agent_reach/channels/xiaohongshu.py at 20). No single row can show this, because each is measured only against the threshold: reducing this one file moves this dimension further than any other file in the repository."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"agent_reach/cli.py"},"region":{"startLine":2333}}}],"partialFingerprints":{"codehealthFindingId/v1":"102ac5459316b9b8faf87bafb45d93ab31ecd661b500190413c647810746fedf"}},{"ruleId":"D2","level":"warning","message":{"text":"cli._install_system_deps (cognitive 100): cli._install_system_deps has cognitive complexity 100 (threshold 15). Drivers by points: if/else 33 (63 pts), error handling 6 (19 pts), boolean chains 8, ternaries 2 (7 pts), loops 1 (3 pts) (nesting depth added 50). 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 file is where this pass\u0027s cognitive complexity CONCENTRATES: agent_reach/cli.py holds 13 of the 26 methods over the threshold \u2014 including the worst \u2014 and 304 of the 432 points over it (70%), 8.4\u00D7 the next-largest file (agent_reach/channels/xiaohongshu.py at 36). No single row can show this, because each is measured only against the threshold: reducing this one file moves this dimension further than any other file in the repository."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"agent_reach/cli.py"},"region":{"startLine":666}}}],"partialFingerprints":{"codehealthFindingId/v1":"f00ebd4056259f26193062c46f823086a574dbec644f541933fc1f389681609c"}},{"ruleId":"D2","level":"warning","message":{"text":"cli._cmd_install (cognitive 69): cli._cmd_install has cognitive complexity 69 (threshold 15). Drivers by points: if/else 36 (49 pts), boolean chains 13, loops 2 (4 pts), ternaries 1 (3 pts) (nesting depth added 17). 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 file is where this pass\u0027s cognitive complexity CONCENTRATES: agent_reach/cli.py holds 13 of the 26 methods over the threshold \u2014 including the worst \u2014 and 304 of the 432 points over it (70%), 8.4\u00D7 the next-largest file (agent_reach/channels/xiaohongshu.py at 36). No single row can show this, because each is measured only against the threshold: reducing this one file moves this dimension further than any other file in the repository."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"agent_reach/cli.py"},"region":{"startLine":263}}}],"partialFingerprints":{"codehealthFindingId/v1":"6af8e8535f20bad5cd947e2d6ca7095f91fbbfca975c10952fb582cd21df726c"}},{"ruleId":"D2","level":"warning","message":{"text":"cli._configure_xhs_cookies (cognitive 69): cli._configure_xhs_cookies has cognitive complexity 69 (threshold 15). Drivers by points: if/else 25 (44 pts), error handling 9 (14 pts), boolean chains 6, loops 2 (5 pts) (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. This file is where this pass\u0027s cognitive complexity CONCENTRATES: agent_reach/cli.py holds 13 of the 26 methods over the threshold \u2014 including the worst \u2014 and 304 of the 432 points over it (70%), 8.4\u00D7 the next-largest file (agent_reach/channels/xiaohongshu.py at 36). No single row can show this, because each is measured only against the threshold: reducing this one file moves this dimension further than any other file in the repository."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"agent_reach/cli.py"},"region":{"startLine":1637}}}],"partialFingerprints":{"codehealthFindingId/v1":"cd9887fe56413ca2fb9e4b345cfb974e15f6d949beba8c8b30c4724846d9e087"}},{"ruleId":"D2","level":"warning","message":{"text":"cli._cmd_configure (cognitive 51): cli._cmd_configure has cognitive complexity 51 (threshold 15). Drivers by points: if/else 18 (35 pts), ternaries 2 (7 pts), loops 2 (6 pts), error handling 1 (2 pts), boolean chains 1 (nesting depth added 27). To reduce it, flatten the nesting: this score is depth rather than breadth \u2014 most of its points come from checks stacked inside one another, so the work sits several levels in. Invert each enclosing check into an early exit (a return, or the language\u0027s equivalent) so the happy path stays at one level, and where a level cannot be exited early, lift the block it encloses into its own named function. This file is where this pass\u0027s cognitive complexity CONCENTRATES: agent_reach/cli.py holds 13 of the 26 methods over the threshold \u2014 including the worst \u2014 and 304 of the 432 points over it (70%), 8.4\u00D7 the next-largest file (agent_reach/channels/xiaohongshu.py at 36). No single row can show this, because each is measured only against the threshold: reducing this one file moves this dimension further than any other file in the repository."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"agent_reach/cli.py"},"region":{"startLine":1425}}}],"partialFingerprints":{"codehealthFindingId/v1":"32939c21efadc1604fa4b8a956092e0c13ce2a1a9774282864bff22892b44195"}},{"ruleId":"D2","level":"warning","message":{"text":"xiaohongshu._clean_note (cognitive 49): xiaohongshu._clean_note has cognitive complexity 49 (threshold 15). Drivers by points: if/else 16 (30 pts), boolean chains 11, loops 5 (8 pts) (nesting depth added 17). 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":"agent_reach/channels/xiaohongshu.py"},"region":{"startLine":73}}}],"partialFingerprints":{"codehealthFindingId/v1":"2807a194527ebb7e69a9a217d2363e327f9c59fb21f6afa6d3bc0e2b1ebe317c"}},{"ruleId":"D2","level":"warning","message":{"text":"cli._cmd_uninstall (cognitive 46): cli._cmd_uninstall has cognitive complexity 46 (threshold 15). Drivers by points: if/else 19 (31 pts), error handling 3 (10 pts), loops 3 (5 pts) (nesting depth added 21). 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 file is where this pass\u0027s cognitive complexity CONCENTRATES: agent_reach/cli.py holds 13 of the 26 methods over the threshold \u2014 including the worst \u2014 and 304 of the 432 points over it (70%), 8.4\u00D7 the next-largest file (agent_reach/channels/xiaohongshu.py at 36). No single row can show this, because each is measured only against the threshold: reducing this one file moves this dimension further than any other file in the repository."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"agent_reach/cli.py"},"region":{"startLine":1875}}}],"partialFingerprints":{"codehealthFindingId/v1":"4c034b3aa5d135ff5973c7812e6a4ea4dcd8cc6bdebb2689de8013521222bfb8"}},{"ruleId":"D2","level":"warning","message":{"text":"cookie_extract.extract_all (cognitive 36): cookie_extract.extract_all has cognitive complexity 36 (threshold 15). Drivers by points: if/else 11 (18 pts), error handling 4 (8 pts), loops 3 (6 pts), ternaries 2 (4 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":"agent_reach/cookie_extract.py"},"region":{"startLine":209}}}],"partialFingerprints":{"codehealthFindingId/v1":"dba41acf67d86006a5ac43a62e918376e9a5e8041b7da14534cf1a260b173efb"}},{"ruleId":"D2","level":"warning","message":{"text":"boss._read_ws_text_frame (cognitive 31): boss._read_ws_text_frame has cognitive complexity 31 (threshold 15). Drivers by points: if/else 7 (20 pts), loops 5 (11 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":"agent_reach/channels/boss.py"},"region":{"startLine":122}}}],"partialFingerprints":{"codehealthFindingId/v1":"b46e187b47d6223bde9db51a76ff2033693cd367a67d34073041dc51ae4f856a"}},{"ruleId":"D2","level":"warning","message":{"text":"cli.main (cognitive 30): cli.main has cognitive complexity 30 (threshold 15). Drivers by points: if/else 14 (23 pts), boolean chains 7 (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. This file is where this pass\u0027s cognitive complexity CONCENTRATES: agent_reach/cli.py holds 13 of the 26 methods over the threshold \u2014 including the worst \u2014 and 304 of the 432 points over it (70%), 8.4\u00D7 the next-largest file (agent_reach/channels/xiaohongshu.py at 36). No single row can show this, because each is measured only against the threshold: reducing this one file moves this dimension further than any other file in the repository."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"agent_reach/cli.py"},"region":{"startLine":68}}}],"partialFingerprints":{"codehealthFindingId/v1":"efcd05821301b68e3f755ed60d4574e0315419cbf5c8d626b824cdd4ced319ff"}},{"ruleId":"D2","level":"warning","message":{"text":"doctor.format_report (cognitive 30): doctor.format_report has cognitive complexity 30 (threshold 15). Drivers by points: if/else 15 (20 pts), loops 3 (5 pts), error handling 1 (2 pts), ternaries 2, boolean chains 1 (nesting depth added 8). 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."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"agent_reach/doctor.py"},"region":{"startLine":57}}}],"partialFingerprints":{"codehealthFindingId/v1":"8acd6c7ce733be25a6febca3a78b003f19950371ee1046424281bb9a36a174a5"}},{"ruleId":"D2","level":"warning","message":{"text":"YouTubeChannel.check (cognitive 29): YouTubeChannel.check has cognitive complexity 29 (threshold 15). Drivers by points: if/else 15 (27 pts), boolean chains 2 (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":"agent_reach/channels/youtube.py"},"region":{"startLine":50}}}],"partialFingerprints":{"codehealthFindingId/v1":"8a3dbe4b83fee2c7559d7ede71ae07b732f89311657a183d729a718dbc7ccc97"}},{"ruleId":"D2","level":"warning","message":{"text":"cli._cmd_setup (cognitive 27): cli._cmd_setup has cognitive complexity 27 (threshold 15). Drivers by points: if/else 16 (25 pts), error handling 1 (2 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. This file is where this pass\u0027s cognitive complexity CONCENTRATES: agent_reach/cli.py holds 13 of the 26 methods over the threshold \u2014 including the worst \u2014 and 304 of the 432 points over it (70%), 8.4\u00D7 the next-largest file (agent_reach/channels/xiaohongshu.py at 36). No single row can show this, because each is measured only against the threshold: reducing this one file moves this dimension further than any other file in the repository."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"agent_reach/cli.py"},"region":{"startLine":2040}}}],"partialFingerprints":{"codehealthFindingId/v1":"8c916a57e3da58f609e3f75b2519d3503fe7b819e4b7bbb669276e03404a185a"}},{"ruleId":"D2","level":"warning","message":{"text":"boss._cdp_zhipin_login_cookie (cognitive 25): boss._cdp_zhipin_login_cookie has cognitive complexity 25 (threshold 15). Drivers by points: if/else 8 (14 pts), boolean chains 5, loops 3 (4 pts), error handling 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":"agent_reach/channels/boss.py"},"region":{"startLine":184}}}],"partialFingerprints":{"codehealthFindingId/v1":"97ccf83a3ad5748b501f9d2cc9ed5bb4661d6c625b3803585cb6338aba748129"}},{"ruleId":"D2","level":"warning","message":{"text":"BilibiliChannel.check (cognitive 22): BilibiliChannel.check has cognitive complexity 22 (threshold 15). Drivers by points: if/else 7 (14 pts), loops 3 (4 pts), ternaries 1 (4 pts) (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":"agent_reach/channels/bilibili.py"},"region":{"startLine":46}}}],"partialFingerprints":{"codehealthFindingId/v1":"ed0e0296cd2ae29055503a36357370ee348e56d692600f8ab4cb74e8c22dfbdf"}},{"ruleId":"D2","level":"warning","message":{"text":"cli._cmd_watch (cognitive 20): cli._cmd_watch has cognitive complexity 20 (threshold 15). Drivers by points: if/else 8 (11 pts), loops 3 (6 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. This file is where this pass\u0027s cognitive complexity CONCENTRATES: agent_reach/cli.py holds 13 of the 26 methods over the threshold \u2014 including the worst \u2014 and 304 of the 432 points over it (70%), 8.4\u00D7 the next-largest file (agent_reach/channels/xiaohongshu.py at 36). No single row can show this, because each is measured only against the threshold: reducing this one file moves this dimension further than any other file in the repository."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"agent_reach/cli.py"},"region":{"startLine":2333}}}],"partialFingerprints":{"codehealthFindingId/v1":"89618b630a252e03f00d0f0834b7cfd5b4c8e8f8efe5c648fe2e1312049f4c45"}},{"ruleId":"D2","level":"warning","message":{"text":"cli._detect_environment (cognitive 19): cli._detect_environment has cognitive complexity 19 (threshold 15). Drivers by points: if/else 6 (9 pts), boolean chains 4, error handling 2 (4 pts), 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. This file is where this pass\u0027s cognitive complexity CONCENTRATES: agent_reach/cli.py holds 13 of the 26 methods over the threshold \u2014 including the worst \u2014 and 304 of the 432 points over it (70%), 8.4\u00D7 the next-largest file (agent_reach/channels/xiaohongshu.py at 36). No single row can show this, because each is measured only against the threshold: reducing this one file moves this dimension further than any other file in the repository."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"agent_reach/cli.py"},"region":{"startLine":1343}}}],"partialFingerprints":{"codehealthFindingId/v1":"ae6a1edbd641df5bcccc880756e89e3a9100acd6276119302b52c69207aa5b89"}},{"ruleId":"D2","level":"warning","message":{"text":"cli._github_get_with_retry (cognitive 19): cli._github_get_with_retry has cognitive complexity 19 (threshold 15). Drivers by points: if/else 4 (11 pts), error handling 2 (6 pts), boolean chains 1, loops 1 (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. This file is where this pass\u0027s cognitive complexity CONCENTRATES: agent_reach/cli.py holds 13 of the 26 methods over the threshold \u2014 including the worst \u2014 and 304 of the 432 points over it (70%), 8.4\u00D7 the next-largest file (agent_reach/channels/xiaohongshu.py at 36). No single row can show this, because each is measured only against the threshold: reducing this one file moves this dimension further than any other file in the repository."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"agent_reach/cli.py"},"region":{"startLine":2210}}}],"partialFingerprints":{"codehealthFindingId/v1":"6cae10fea17a58c393fbbfcc341b8701e56c7e737d5474da978c31b1a4812270"}},{"ruleId":"D2","level":"warning","message":{"text":"transcribe._assert_safe_public_url (cognitive 18): transcribe._assert_safe_public_url has cognitive complexity 18 (threshold 15). Drivers by points: if/else 9 (13 pts), boolean chains 4, error handling 1 (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":"agent_reach/transcribe.py"},"region":{"startLine":214}}}],"partialFingerprints":{"codehealthFindingId/v1":"f877db91245d39f3123890e20fe86b082684a8051d07578ade08ace9ca46579c"}},{"ruleId":"D2","level":"warning","message":{"text":"RedditChannel.check (cognitive 17): RedditChannel.check has cognitive complexity 17 (threshold 15). Drivers by points: if/else 5 (9 pts), loops 3 (4 pts), ternaries 1 (4 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":"agent_reach/channels/reddit.py"},"region":{"startLine":41}}}],"partialFingerprints":{"codehealthFindingId/v1":"62350ae6bc05b696a51cbb641e3d2cb70d2c73ee7ade3a7c2a7a036f65fef3c7"}},{"ruleId":"D2","level":"warning","message":{"text":"TwitterChannel.check (cognitive 17): TwitterChannel.check has cognitive complexity 17 (threshold 15). Drivers by points: if/else 5 (9 pts), loops 3 (4 pts), ternaries 1 (4 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":"agent_reach/channels/twitter.py"},"region":{"startLine":43}}}],"partialFingerprints":{"codehealthFindingId/v1":"30bcce688bfc283553b8f5e4fed8963dc985581f77e8043696d04fc67c6d08bc"}},{"ruleId":"D2","level":"warning","message":{"text":"XiaoHongShuChannel.check (cognitive 17): XiaoHongShuChannel.check has cognitive complexity 17 (threshold 15). Drivers by points: if/else 5 (9 pts), loops 3 (4 pts), ternaries 1 (4 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":"agent_reach/channels/xiaohongshu.py"},"region":{"startLine":171}}}],"partialFingerprints":{"codehealthFindingId/v1":"aef93298bc98a79b7fe66885d3c7dc22856f72dad9301d827a9a877c3ce1788a"}},{"ruleId":"D2","level":"warning","message":{"text":"cli._install_mcporter (cognitive 17): cli._install_mcporter has cognitive complexity 17 (threshold 15). Drivers by points: if/else 10 (13 pts), error handling 2 (3 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. This file is where this pass\u0027s cognitive complexity CONCENTRATES: agent_reach/cli.py holds 13 of the 26 methods over the threshold \u2014 including the worst \u2014 and 304 of the 432 points over it (70%), 8.4\u00D7 the next-largest file (agent_reach/channels/xiaohongshu.py at 36). No single row can show this, because each is measured only against the threshold: reducing this one file moves this dimension further than any other file in the repository."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"agent_reach/cli.py"},"region":{"startLine":1248}}}],"partialFingerprints":{"codehealthFindingId/v1":"566f0b17caa023eef856e6735bf620188a23fc94f1faf87804bf3d86222b640f"}},{"ruleId":"D2","level":"warning","message":{"text":"mcporter.inspect_mcporter_config (cognitive 16): mcporter.inspect_mcporter_config has cognitive complexity 16 (threshold 15). Drivers by points: if/else 5 (11 pts), loops 2 (3 pts), boolean chains 2 (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":"agent_reach/channels/mcporter.py"},"region":{"startLine":32}}}],"partialFingerprints":{"codehealthFindingId/v1":"7f6247dfbcd61be6ec64a2c85b2816d720082c10648036b2a7b48f6090d1194f"}},{"ruleId":"D2","level":"warning","message":{"text":"cli._install_skill (cognitive 16): cli._install_skill has cognitive complexity 16 (threshold 15). Drivers by points: if/else 8 (15 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. This file is where this pass\u0027s cognitive complexity CONCENTRATES: agent_reach/cli.py holds 13 of the 26 methods over the threshold \u2014 including the worst \u2014 and 304 of the 432 points over it (70%), 8.4\u00D7 the next-largest file (agent_reach/channels/xiaohongshu.py at 36). No single row can show this, because each is measured only against the threshold: reducing this one file moves this dimension further than any other file in the repository."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"agent_reach/cli.py"},"region":{"startLine":479}}}],"partialFingerprints":{"codehealthFindingId/v1":"ac139517f2d3686c1f95f345e4b82c95763eab9ddee9f51a046b46356d669ec4"}},{"ruleId":"D2","level":"warning","message":{"text":"cli._cmd_check_update (cognitive 16): cli._cmd_check_update has cognitive complexity 16 (threshold 15). Drivers by points: if/else 8 (11 pts), loops 1 (4 pts), boolean chains 1 (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. This file is where this pass\u0027s cognitive complexity CONCENTRATES: agent_reach/cli.py holds 13 of the 26 methods over the threshold \u2014 including the worst \u2014 and 304 of the 432 points over it (70%), 8.4\u00D7 the next-largest file (agent_reach/channels/xiaohongshu.py at 36). No single row can show this, because each is measured only against the threshold: reducing this one file moves this dimension further than any other file in the repository."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"agent_reach/cli.py"},"region":{"startLine":2271}}}],"partialFingerprints":{"codehealthFindingId/v1":"530d83c073729a20173c17d3d42e4107d0bc8437979a698db7b89c5ceebf42d5"}},{"ruleId":"D2","level":"warning","message":{"text":"cookie_extract.configure_from_browser (cognitive 16): cookie_extract.configure_from_browser has cognitive complexity 16 (threshold 15). Drivers by points: if/else 7 (11 pts), ternaries 1 (3 pts), error handling 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":"agent_reach/cookie_extract.py"},"region":{"startLine":414}}}],"partialFingerprints":{"codehealthFindingId/v1":"86c72b11a05991acf6a538f4c34a62bbe1c8c1807940bb7b31034099e601d534"}},{"ruleId":"D3","level":"warning","message":{"text":"FileTooLong: agent_reach/cli.py: FileTooLong \u2014 1802 significant lines (blank, comment-only and punctuation-only lines excluded). The bar is 500 significant lines; this is 1302 over it, 3.60\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":"agent_reach/cli.py"},"region":{"startLine":1}}}],"partialFingerprints":{"codehealthFindingId/v1":"584fc026b024dfc0ab626740b0666b302a2208f138e5d9acd574fbfa2a83bc37"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (24 lines \u00D7 3): agent_reach/cli.py:966-989 | agent_reach/cli.py:1138-1161 | agent_reach/cli.py:1167-1190 \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":"agent_reach/cli.py"},"region":{"startLine":966}}}],"partialFingerprints":{"codehealthFindingId/v1":"f41cf993c1ab94ac197f0946eafabcf051afb455b60013afb4c3ed2896e72a93"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (17\u201318 lines \u00D7 3): agent_reach/channels/reddit.py:51-68 | agent_reach/channels/twitter.py:63-80 | agent_reach/channels/xiaohongshu.py:188-204 \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 all 3 call sites, so a change lands once. Read the line range as the matched WINDOW rather than a finished unit: at \u0060agent_reach/channels/reddit.py:51\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":"agent_reach/channels/reddit.py"},"region":{"startLine":51}}}],"partialFingerprints":{"codehealthFindingId/v1":"3ed2c0f7d68124b850d533ec28628d552e62adcaa97fd38b854dd6b4a461ea4a"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (13 lines \u00D7 2): agent_reach/cli.py:2109-2121 | agent_reach/cli.py:2130-2142 \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":"agent_reach/cli.py"},"region":{"startLine":2109}}}],"partialFingerprints":{"codehealthFindingId/v1":"cf7bcb553782e8ab76769b52ac3d38ae5b7fbd0d4e81d085c3f0a9f2981adfee"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (9 lines \u00D7 2): agent_reach/cli.py:621-629 | agent_reach/cli.py:1945-1953 \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":"agent_reach/cli.py"},"region":{"startLine":621}}}],"partialFingerprints":{"codehealthFindingId/v1":"d150c0953e746ff6987267df933642f5abaf19e3eabccd148666ee762df4b524"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (14 lines \u00D7 4): agent_reach/channels/bilibili.py:97-110 | agent_reach/channels/reddit.py:75-88 | agent_reach/channels/twitter.py:112-125 | agent_reach/channels/xiaohongshu.py:211-224 \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 all 4 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":"agent_reach/channels/bilibili.py"},"region":{"startLine":97}}}],"partialFingerprints":{"codehealthFindingId/v1":"415238791dc0e628a8ed1b4bab62876e268f30dc5e5c994beb98b382ed74a8c6"}},{"ruleId":"D4","level":"warning","message":{"text":"Duplicated block (11 lines \u00D7 2): agent_reach/transcribe.py:178-195 | agent_reach/utils/url.py:34-44 \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":"agent_reach/transcribe.py"},"region":{"startLine":178}}}],"partialFingerprints":{"codehealthFindingId/v1":"c2af06d4125d88456efa92bcf641d1590db41fb6185b7312fe6657ee8d216a30"}},{"ruleId":"D15","level":"warning","message":{"text":"Hotspot: agent_reach/cli.py: agent_reach/cli.py changed 12 times in last 90 days, max cyclomatic complexity 47 in cli._cmd_install at line 263. 10 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-06-18..2026-09-16, the 90 days ending at the analysed commit. Reproduce with \u0060git log --since=\u00272026-06-18 00:16:24 \u002B08:00\u0027 --until=\u00272026-09-16 00:16:24 \u002B08:00\u0027 --full-history --no-merges -- agent_reach/cli.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":"agent_reach/cli.py"},"region":{"startLine":263}}}],"partialFingerprints":{"codehealthFindingId/v1":"52062f2429f3b87d0bcd2c32d6506d3ef213018851c0687983eeeeffff0db843"}},{"ruleId":"D15","level":"warning","message":{"text":"Hotspot: agent_reach/channels/xiaohongshu.py: agent_reach/channels/xiaohongshu.py changed 4 times in last 90 days, max cyclomatic complexity 35 in xiaohongshu._clean_note at line 73. 4 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-06-18..2026-09-16, the 90 days ending at the analysed commit. Reproduce with \u0060git log --since=\u00272026-06-18 00:16:24 \u002B08:00\u0027 --until=\u00272026-09-16 00:16:24 \u002B08:00\u0027 --full-history --no-merges -- agent_reach/channels/xiaohongshu.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":"agent_reach/channels/xiaohongshu.py"},"region":{"startLine":73}}}],"partialFingerprints":{"codehealthFindingId/v1":"fae1c7683d8a856da9e2c57873de3b94e40e3e4b93c5fab6ad37ec710834a566"}},{"ruleId":"D15","level":"warning","message":{"text":"Hotspot: agent_reach/channels/youtube.py: agent_reach/channels/youtube.py changed 6 times in last 90 days, max cyclomatic complexity 17 in YouTubeChannel.check at line 50. 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-06-18..2026-09-16, the 90 days ending at the analysed commit. Reproduce with \u0060git log --since=\u00272026-06-18 00:16:24 \u002B08:00\u0027 --until=\u00272026-09-16 00:16:24 \u002B08:00\u0027 --full-history --no-merges -- agent_reach/channels/youtube.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":"agent_reach/channels/youtube.py"},"region":{"startLine":50}}}],"partialFingerprints":{"codehealthFindingId/v1":"9170077a098dd0516f5eb53d5e3ab78bfd60560e87749b0494d6bece3a77288f"}},{"ruleId":"D15","level":"warning","message":{"text":"Hotspot: agent_reach/cookie_extract.py: agent_reach/cookie_extract.py changed 4 times in last 90 days, max cyclomatic complexity 20 in cookie_extract.extract_all at line 209. 4 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-06-18..2026-09-16, the 90 days ending at the analysed commit. Reproduce with \u0060git log --since=\u00272026-06-18 00:16:24 \u002B08:00\u0027 --until=\u00272026-09-16 00:16:24 \u002B08:00\u0027 --full-history --no-merges -- agent_reach/cookie_extract.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":"agent_reach/cookie_extract.py"},"region":{"startLine":209}}}],"partialFingerprints":{"codehealthFindingId/v1":"416b38ef5eaa2a60fc53e67875f3f9aab21eb20f43dced2ea97c969f585d7add"}},{"ruleId":"D15","level":"warning","message":{"text":"Repeated repair: agent_reach/transcribe.py: agent_reach/transcribe.py changed 6 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 14 (its worst body is transcribe._assert_safe_public_url at line 214), 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(transcribe): decode subprocess output as UTF-8\u201D; \u201Cfix(security): close remaining trust-boundary gaps\u201D; \u201Cfix(transcribe): block shorthand IPv4 spellings of internal hosts\u201D; \u201Cfix(transcribe): reject oversized media before processing\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-06-18..2026-09-16, the 90 days ending at the analysed commit. Reproduce with \u0060git log --since=\u00272026-06-18 00:16:24 \u002B08:00\u0027 --until=\u00272026-09-16 00:16:24 \u002B08:00\u0027 --full-history --no-merges -- agent_reach/transcribe.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":"agent_reach/transcribe.py"},"region":{"startLine":214}}}],"partialFingerprints":{"codehealthFindingId/v1":"93cfaf177be810d90184c2b80b72e9b82f76f8edd2963d14c7f8397ae94e9ef8"}},{"ruleId":"D15","level":"warning","message":{"text":"Repeated repair: agent_reach/channels/xueqiu.py: agent_reach/channels/xueqiu.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 11 (its worst body is XueqiuChannel.get_hot_posts at line 225), 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(xueqiu): honor and clamp hot-post limits\u201D; \u201Cfix(security): make diagnostics provably read-only\u201D; \u201Cfix(security): enforce least-privilege credential boundaries\u201D; \u201Cfix(xueqiu): return detail quote fields safely\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-06-18..2026-09-16, the 90 days ending at the analysed commit. Reproduce with \u0060git log --since=\u00272026-06-18 00:16:24 \u002B08:00\u0027 --until=\u00272026-09-16 00:16:24 \u002B08:00\u0027 --full-history --no-merges -- agent_reach/channels/xueqiu.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":"agent_reach/channels/xueqiu.py"},"region":{"startLine":225}}}],"partialFingerprints":{"codehealthFindingId/v1":"73b0bab400f9801f5374acedfabcd7135a85fa568e03dee3ebe80e31f8605b85"}},{"ruleId":"D15","level":"warning","message":{"text":"Repeated repair: agent_reach/config.py: agent_reach/config.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 9 (its worst body is config._atomic_write_yaml at line 46), 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(security): close remaining trust-boundary gaps\u201D; \u201Cfix(security): honor HOME for credential paths\u201D; \u201Cfix(security): make diagnostics provably read-only\u201D; \u201Cfix(doctor): make health checks truthful and read-only\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-06-18..2026-09-16, the 90 days ending at the analysed commit. Reproduce with \u0060git log --since=\u00272026-06-18 00:16:24 \u002B08:00\u0027 --until=\u00272026-09-16 00:16:24 \u002B08:00\u0027 --full-history --no-merges -- agent_reach/config.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":"agent_reach/config.py"},"region":{"startLine":46}}}],"partialFingerprints":{"codehealthFindingId/v1":"6e9ec58fa34a213d4137f89a50249d618f67a4941222bde149aa26341c1a5346"}},{"ruleId":"D15","level":"warning","message":{"text":"Repeated repair: agent_reach/channels/v2ex.py: agent_reach/channels/v2ex.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 v2ex._is_unexpected_tls_eof at line 57), 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(v2ex): encode caller values in URLs\u201D; \u201Cfix(v2ex): constrain TLS fallback boundary\u201D; \u201Cfix(v2ex): recover from Python TLS EOF\u201D; \u201Cfix(security): enforce least-privilege credential boundaries\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-06-18..2026-09-16, the 90 days ending at the analysed commit. Reproduce with \u0060git log --since=\u00272026-06-18 00:16:24 \u002B08:00\u0027 --until=\u00272026-09-16 00:16:24 \u002B08:00\u0027 --full-history --no-merges -- agent_reach/channels/v2ex.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":"agent_reach/channels/v2ex.py"},"region":{"startLine":57}}}],"partialFingerprints":{"codehealthFindingId/v1":"15435d6f3a3d4a43045bda60608873f3ab263529ead1f005cb206964a0c510ca"}},{"ruleId":"D15","level":"warning","message":{"text":"Repeated repair: agent_reach/channels/twitter.py: agent_reach/channels/twitter.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 11 (its worst body is TwitterChannel.check at line 43), 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(security): close remaining trust-boundary gaps\u201D; \u201Cfix(security): make diagnostics provably read-only\u201D; \u201Cfix(security): enforce least-privilege credential boundaries\u201D; \u201Cfix(doctor): make health checks truthful and read-only\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-06-18..2026-09-16, the 90 days ending at the analysed commit. Reproduce with \u0060git log --since=\u00272026-06-18 00:16:24 \u002B08:00\u0027 --until=\u00272026-09-16 00:16:24 \u002B08:00\u0027 --full-history --no-merges -- agent_reach/channels/twitter.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":"agent_reach/channels/twitter.py"},"region":{"startLine":43}}}],"partialFingerprints":{"codehealthFindingId/v1":"824b322fa30c88e305d4e944c22d6dfa35030c9b17d3b89f7f9efc888c5c16f2"}},{"ruleId":"D15","level":"warning","message":{"text":"Repeated repair: agent_reach/channels/linkedin.py: agent_reach/channels/linkedin.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 6 (its worst body is LinkedInChannel.check at line 34), 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(linkedin): refresh uvx stdio contract\u201D; \u201Cfix(security): make diagnostics provably read-only\u201D; \u201Cfix(security): enforce least-privilege credential boundaries\u201D; \u201Cfix(doctor): make health checks truthful and read-only\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-06-18..2026-09-16, the 90 days ending at the analysed commit. Reproduce with \u0060git log --since=\u00272026-06-18 00:16:24 \u002B08:00\u0027 --until=\u00272026-09-16 00:16:24 \u002B08:00\u0027 --full-history --no-merges -- agent_reach/channels/linkedin.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":"agent_reach/channels/linkedin.py"},"region":{"startLine":34}}}],"partialFingerprints":{"codehealthFindingId/v1":"8484d70ad38e699b1b104eb862878c82e5023a6c858bbf78c022ff15490b18f0"}},{"ruleId":"D15","level":"warning","message":{"text":"Repeated repair: agent_reach/utils/paths.py: agent_reach/utils/paths.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 10 (its worst body is paths.atomic_write_private_text at line 71), 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(security): honor HOME for credential paths\u201D; \u201Cfix(security): make diagnostics provably read-only\u201D; \u201Cfix(youtube): align yt-dlp runtime support\u201D; \u201Cfix(security): harden local credential handling\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-06-18..2026-09-16, the 90 days ending at the analysed commit. Reproduce with \u0060git log --since=\u00272026-06-18 00:16:24 \u002B08:00\u0027 --until=\u00272026-09-16 00:16:24 \u002B08:00\u0027 --full-history --no-merges -- agent_reach/utils/paths.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":"agent_reach/utils/paths.py"},"region":{"startLine":71}}}],"partialFingerprints":{"codehealthFindingId/v1":"cedc34bde5517289eade9dc5c9332f0a77f4341a1a277461f5aa9a35e461dcdc"}},{"ruleId":"D15","level":"warning","message":{"text":"Repeated repair: agent_reach/channels/_opencli_site.py: agent_reach/channels/_opencli_site.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 4 (its worst body is OpenCLISiteChannel.check at line 27), 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(security): close remaining trust-boundary gaps\u201D; \u201Cfix(security): make diagnostics provably read-only\u201D; \u201Cfix(security): enforce least-privilege credential boundaries\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-06-18..2026-09-16, the 90 days ending at the analysed commit. Reproduce with \u0060git log --since=\u00272026-06-18 00:16:24 \u002B08:00\u0027 --until=\u00272026-09-16 00:16:24 \u002B08:00\u0027 --full-history --no-merges -- agent_reach/channels/_opencli_site.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":"agent_reach/channels/_opencli_site.py"},"region":{"startLine":27}}}],"partialFingerprints":{"codehealthFindingId/v1":"656b06852cb577ee0a93e4e72a47ae207fcbe12344748fa79c107240c54b5e5f"}},{"ruleId":"D15","level":"warning","message":{"text":"Repeated repair: agent_reach/channels/bilibili.py: agent_reach/channels/bilibili.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 12 (its worst body is BilibiliChannel.check at line 46), 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(security): close remaining trust-boundary gaps\u201D; \u201Cfix(security): make diagnostics provably read-only\u201D; \u201Cfix(security): enforce least-privilege credential boundaries\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-06-18..2026-09-16, the 90 days ending at the analysed commit. Reproduce with \u0060git log --since=\u00272026-06-18 00:16:24 \u002B08:00\u0027 --until=\u00272026-09-16 00:16:24 \u002B08:00\u0027 --full-history --no-merges -- agent_reach/channels/bilibili.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":"agent_reach/channels/bilibili.py"},"region":{"startLine":46}}}],"partialFingerprints":{"codehealthFindingId/v1":"49e56b93e0b23a0b36393f30c945ffdf909361c980a21c100b4d13aabb8fc6e6"}},{"ruleId":"D15","level":"warning","message":{"text":"Repeated repair: agent_reach/channels/reddit.py: agent_reach/channels/reddit.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 11 (its worst body is RedditChannel._check_rdt at line 90), 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(security): close remaining trust-boundary gaps\u201D; \u201Cfix(security): make diagnostics provably read-only\u201D; \u201Cfix(security): enforce least-privilege credential boundaries\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-06-18..2026-09-16, the 90 days ending at the analysed commit. Reproduce with \u0060git log --since=\u00272026-06-18 00:16:24 \u002B08:00\u0027 --until=\u00272026-09-16 00:16:24 \u002B08:00\u0027 --full-history --no-merges -- agent_reach/channels/reddit.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":"agent_reach/channels/reddit.py"},"region":{"startLine":90}}}],"partialFingerprints":{"codehealthFindingId/v1":"78fc67832cda33c9a2ffc03666c325de7d40fd6ab7f833b337ea3b9a0ee408ac"}},{"ruleId":"D19","level":"note","message":{"text":"Documentation: no project overview: The root README is a single-file README that says nothing about what Agent Reach does and gives only a one-line slogan (\u0027\u7ED9\u4F60\u7684 AI Agent \u4E00\u952E\u88C5\u4E0A\u4E92\u8054\u7F51\u80FD\u529B\u0027) without explaining the project\u0027s purpose. Replace the README with an overview describing each platform (Twitter/X, Bilibili, XiaoHongShu) it connects to, why it matters, and what makes it reliable over alternatives."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"README.md"},"region":{"startLine":1}}}],"partialFingerprints":{"codehealthFindingId/v1":"66dbf18d06aa983c06246108610908ba99aae6784280b3aacd1a6655733d71be"}},{"ruleId":"D20","level":"note","message":{"text":"No ADRs found: No ADRs found. No recognised ADR directory (\u0060docs/adr/\u0060, \u0060docs/decisions/\u0060, \u0060adr/\u0060, \u0060docs/rfcs/\u0060, an \u0060ADR0001/\u0060 folder, or their siblings) exists anywhere in this tree. What was searched, so you can tell an empty log from a search that missed one: every directory under the tree (build output, dependencies and VCS metadata excepted), for a document that is either any non-index page inside a recognised ADR directory, whatever its name and however deeply nested (\u0060docs/adr/use-postgres.md\u0060, \u0060docs/adr/2024/0001-x.md\u0060); or a file anywhere whose name is ADR-shaped (\u00600001-use-postgres.md\u0060, \u0060adr-012-caching.md\u0060); or, when neither turned anything up, a document carrying the decision-record signature (an \u0022Architecture Decision Record\u0022 heading, or Status / Context / Decision / Consequences as section headings). A decision log that clears none of these \u2014 unnumbered files outside any recognised directory, without those headings \u2014 is not seen by this check and this row is then wrong. If that is your case, say so rather than renaming anything; otherwise, consider recording architectural decisions in \u0060docs/adr/\u0060."},"locations":[],"partialFingerprints":{"codehealthFindingId/v1":"d2bea044ff79d7d275f5a91a6e2f548586178eaf480274c33960ad020c854631"}},{"ruleId":"D22","level":"warning","message":{"text":"Inconsistent return type for validation method. The base class \u0060Channel.check\u0060 returns a \u0060Tuple[str, str]\u0060 (likely status/message or error/info), while the specific \u0060OpenCLISiteChannel.check\u0060 has no return type annotation, implying it returns \u0060None\u0060 or void. This breaks the contract for callers expecting a tuple.: Ensure \u0060OpenCLISiteChannel.check\u0060 returns \u0060Tuple[str, str]\u0060 to match the base class interface, or update the base class to \u0060Optional[Tuple[str, str]]\u0060 if some channels are expected to not return a tuple. (signatures: agent_reach.channels.base.Channel.check(self, config): Tuple[str, str] | agent_reach.channels._opencli_site.OpenCLISiteChannel.check(self, config))"},"locations":[],"partialFingerprints":{"codehealthFindingId/v1":"8c29502fc48dab3d6ac4ed01f218b99527d30fd3f3ebca3c6df7d013fc2d1c61"}},{"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":"1a2685cee65d80505578f546ed20b5128640e70c960c0363c9421a1548f4452f"},"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"}}]},{"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":"6e30be5cda739afb4b06cd2a3b6092774d3a66a07b306d57a3455a43e0e238ad"},"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"}}]},{"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":"e734cbbc3a0f8e4341eceb4bb51125ecc768656f1284ea4b465845ab5c1184c4"},"taxa":[{"id":"CWE-1357","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},{"id":"CWE-829","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}}]},{"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":"7caca3583a30887d24adb08aa940ab5244afd959570ca6ad7be8c4be5f05a8f6"},"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"}}]},{"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":"f68359bf3558546e3354591bea598f8fac311428b5fdcdc6b7c2d4e7c735d424"},"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"}}]},{"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":"6c95d0243c24c32767eca3235a9bd3e3db49af35d3ffbe613ef21ccbdc4c3353"},"taxa":[{"id":"CWE-1357","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},{"id":"CWE-829","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}}]},{"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":"56edf112c47babf7542f34fc9d2a212c6584d4ea459f3d751ee99be270550692"},"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"}}]},{"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":"f9b27a0cef67393956c98d5758b79fb022c2813a5cc0fa15326a912f117ae86f"},"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"}}]},{"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":"f0637c01745bb1f8e0f667b00869231bb2db5bf37d4357ae43d6425abe170b07"},"taxa":[{"id":"CWE-1357","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}},{"id":"CWE-829","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}}]},{"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":"00d2932c200f70c8b663f94e5bea5e34ed0d9a8aa0dc74f6702010d07227046b"},"taxa":[{"id":"CWE-327","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":"a57f198f8ac5652d828cbf6e3142c5523074b8ae0f15f73370d0baa9064faf40"},"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":"2ba30bd4393d9292bdabb044f45d02f7faf12f9b3d4e08fb99c686e48ba53a83"},"taxa":[{"id":"CWE-939","toolComponent":{"name":"CWE","guid":"c3a2b1d0-7f3e-4b2a-9c1d-5e6f7a8b9c0d"}}]},{"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":"9658270ba49f37ed04e99ab1263b6393cd42aed8bdfb7aafd9fa1070f5491895"}},{"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":"ace515990e7ee0f17b5c17e44dd168a5bdecf90804a3a3dd845cf05540978013"}},{"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":"P3","level":"note","message":{"text":"No SAST: No static application security testing detected. For this repository\u0027s stack, add bandit, \u0060semgrep --config=p/python\u0060, or CodeQL\u0027s python pack as a CI step. What was searched, so you can tell an absence from a miss: the 2928 CI workflow file(s) in this repository, and the scanner and linter configuration checked in beside them. A scan that runs outside CI, one configured in your forge\u0027s web UI rather than in a committed file, or a tool whose name is none of those this check carries, is not seen \u2014 if that is your case the row is wrong, and saying so is more useful than adding a second scanner."},"locations":[],"partialFingerprints":{"codehealthFindingId/v1":"6e54424179c892f03ef2fd003130ac4bd43f39bbf3b1ca0a0143e25acb80ec87"}}],"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-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-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-327","guid":"e51910ff-67b5-3c51-aa3d-92837d9eab90","name":"CWE-327","shortDescription":{"text":"CWE-327"},"helpUri":"https://cwe.mitre.org/data/definitions/327.html"},{"id":"CWE-353","guid":"09d7e902-d4ee-f05d-ae6c-0a1554d0c18f","name":"CWE-353","shortDescription":{"text":"CWE-353"},"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-77","guid":"332c8ade-6612-9f56-a06b-d8d90b1a8750","name":"Command Injection","shortDescription":{"text":"Command Injection"},"helpUri":"https://cwe.mitre.org/data/definitions/77.html"},{"id":"CWE-78","guid":"2e31ceaf-c7ae-2e5e-9661-cfb1362789cf","name":"OS Command Injection","shortDescription":{"text":"OS Command Injection"},"helpUri":"https://cwe.mitre.org/data/definitions/78.html"},{"id":"CWE-79","guid":"fd45580b-e8c4-fc5e-8c2f-aa8fab0b4dbf","name":"Cross-site Scripting (XSS)","shortDescription":{"text":"Cross-site Scripting (XSS)"},"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":"CWE-829","shortDescription":{"text":"CWE-829"},"helpUri":"https://cwe.mitre.org/data/definitions/829.html"},{"id":"CWE-89","guid":"6d08fdad-37eb-c150-bbf0-d7d946863407","name":"SQL Injection","shortDescription":{"text":"SQL Injection"},"helpUri":"https://cwe.mitre.org/data/definitions/89.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":"Code Injection","shortDescription":{"text":"Code Injection"},"helpUri":"https://cwe.mitre.org/data/definitions/94.html"}]}],"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":14,"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."]}]}