vigil-codeintel 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. vigil_codeintel-0.1.0.dist-info/METADATA +780 -0
  2. vigil_codeintel-0.1.0.dist-info/RECORD +131 -0
  3. vigil_codeintel-0.1.0.dist-info/WHEEL +5 -0
  4. vigil_codeintel-0.1.0.dist-info/entry_points.txt +3 -0
  5. vigil_codeintel-0.1.0.dist-info/licenses/LICENSE +21 -0
  6. vigil_codeintel-0.1.0.dist-info/top_level.txt +3 -0
  7. vigil_forensic/__init__.py +224 -0
  8. vigil_forensic/_git_utils.py +178 -0
  9. vigil_forensic/_shared.py +510 -0
  10. vigil_forensic/_stubs.py +156 -0
  11. vigil_forensic/gate_checks/__init__.py +1 -0
  12. vigil_forensic/gate_checks/_ast_helpers.py +629 -0
  13. vigil_forensic/gate_checks/_deployment_detector.py +573 -0
  14. vigil_forensic/gate_checks/atomic_write_checks.py +1143 -0
  15. vigil_forensic/gate_checks/authority_checks.py +95 -0
  16. vigil_forensic/gate_checks/boundary_breach_checks.py +202 -0
  17. vigil_forensic/gate_checks/broad_except_checks.py +301 -0
  18. vigil_forensic/gate_checks/broad_except_hidden_sentinel_checks.py +365 -0
  19. vigil_forensic/gate_checks/common.py +253 -0
  20. vigil_forensic/gate_checks/config_safety_checks.py +704 -0
  21. vigil_forensic/gate_checks/config_ssot_checks.py +78 -0
  22. vigil_forensic/gate_checks/conflict_checks.py +193 -0
  23. vigil_forensic/gate_checks/context_fallback_checks.py +697 -0
  24. vigil_forensic/gate_checks/context_health_checks.py +289 -0
  25. vigil_forensic/gate_checks/contract_shape_drift_checks.py +459 -0
  26. vigil_forensic/gate_checks/dirty_baseline_check.py +274 -0
  27. vigil_forensic/gate_checks/duplication_checks.py +387 -0
  28. vigil_forensic/gate_checks/embedded_string_checks.py +123 -0
  29. vigil_forensic/gate_checks/empty_output_checks.py +87 -0
  30. vigil_forensic/gate_checks/encoding_checks.py +847 -0
  31. vigil_forensic/gate_checks/export_completeness_checks.py +156 -0
  32. vigil_forensic/gate_checks/fallback_checks.py +41 -0
  33. vigil_forensic/gate_checks/file_proliferation_checks.py +171 -0
  34. vigil_forensic/gate_checks/fix_without_test_checks.py +69 -0
  35. vigil_forensic/gate_checks/forensic_cluster_runners/__init__.py +9 -0
  36. vigil_forensic/gate_checks/forensic_cluster_runners/_helpers.py +71 -0
  37. vigil_forensic/gate_checks/forensic_cluster_runners/advanced_checks.py +322 -0
  38. vigil_forensic/gate_checks/forensic_cluster_runners/core.py +273 -0
  39. vigil_forensic/gate_checks/forensic_cluster_runners/integrity_checks.py +203 -0
  40. vigil_forensic/gate_checks/forensic_cluster_runners/quality_checks.py +666 -0
  41. vigil_forensic/gate_checks/forensic_clusters/__init__.py +193 -0
  42. vigil_forensic/gate_checks/forensic_clusters/allowlist.py +426 -0
  43. vigil_forensic/gate_checks/forensic_clusters/allowlist_writer.py +302 -0
  44. vigil_forensic/gate_checks/forensic_clusters/api_protocol.py +231 -0
  45. vigil_forensic/gate_checks/forensic_clusters/async_quality.py +1156 -0
  46. vigil_forensic/gate_checks/forensic_clusters/code_style.py +808 -0
  47. vigil_forensic/gate_checks/forensic_clusters/core.py +319 -0
  48. vigil_forensic/gate_checks/forensic_clusters/data_quality.py +763 -0
  49. vigil_forensic/gate_checks/forensic_clusters/dead_code.py +480 -0
  50. vigil_forensic/gate_checks/forensic_clusters/edit_mutation.py +842 -0
  51. vigil_forensic/gate_checks/forensic_clusters/exception_boundary.py +240 -0
  52. vigil_forensic/gate_checks/forensic_clusters/legacy_debt.py +556 -0
  53. vigil_forensic/gate_checks/forensic_clusters/static_analysis.py +834 -0
  54. vigil_forensic/gate_checks/forensic_clusters/structural_quality.py +298 -0
  55. vigil_forensic/gate_checks/god_object_zones_checks.py +173 -0
  56. vigil_forensic/gate_checks/hallucination_checks.py +566 -0
  57. vigil_forensic/gate_checks/hunter_artifact_completeness_check.py +139 -0
  58. vigil_forensic/gate_checks/implementation_overfit_checks.py +380 -0
  59. vigil_forensic/gate_checks/import_integrity_checks.py +233 -0
  60. vigil_forensic/gate_checks/imports_in_function_checks.py +283 -0
  61. vigil_forensic/gate_checks/ml_checks.py +318 -0
  62. vigil_forensic/gate_checks/performance_checks.py +106 -0
  63. vigil_forensic/gate_checks/project_specific_runner.py +691 -0
  64. vigil_forensic/gate_checks/provider_capability_checks.py +73 -0
  65. vigil_forensic/gate_checks/refactor_completeness_checks.py +274 -0
  66. vigil_forensic/gate_checks/reliability_checks.py +389 -0
  67. vigil_forensic/gate_checks/reporting_checks.py +55 -0
  68. vigil_forensic/gate_checks/runtime_behavior_checks.py +220 -0
  69. vigil_forensic/gate_checks/security_injection_checks.py +332 -0
  70. vigil_forensic/gate_checks/semantic_intent_checks.py +139 -0
  71. vigil_forensic/gate_checks/size_complexity_checks.py +336 -0
  72. vigil_forensic/gate_checks/stuck_feature_flag_checks.py +354 -0
  73. vigil_forensic/gate_checks/syntax_validity_checks.py +217 -0
  74. vigil_forensic/gate_checks/temporal_freshness_checks.py +79 -0
  75. vigil_forensic/gate_checks/test_quality_checks.py +946 -0
  76. vigil_forensic/gate_checks/testing_checks.py +149 -0
  77. vigil_forensic/gate_checks/toctou_checks.py +367 -0
  78. vigil_forensic/gate_checks/type_checking_checks.py +316 -0
  79. vigil_forensic/gate_models.py +392 -0
  80. vigil_forensic/gate_packs/__init__.py +1 -0
  81. vigil_forensic/gate_packs/universal.py +179 -0
  82. vigil_forensic/gate_profile.json +31 -0
  83. vigil_forensic/gate_registry.py +21 -0
  84. vigil_forensic/language_profiles.py +219 -0
  85. vigil_forensic/meta_findings.py +207 -0
  86. vigil_forensic/self_audit.py +725 -0
  87. vigil_forensic/source_analysis.py +175 -0
  88. vigil_mapper/__init__.py +103 -0
  89. vigil_mapper/_ast_helpers_minimal.py +229 -0
  90. vigil_mapper/_extract_imports_impl.py +123 -0
  91. vigil_mapper/_file_count_guard.py +129 -0
  92. vigil_mapper/_git_utils.py +178 -0
  93. vigil_mapper/_runtime_ast.py +438 -0
  94. vigil_mapper/_runtime_dispatch.py +137 -0
  95. vigil_mapper/_seed_helpers.py +82 -0
  96. vigil_mapper/authority_builder.py +1102 -0
  97. vigil_mapper/cli_entry.py +731 -0
  98. vigil_mapper/conflict_builder.py +818 -0
  99. vigil_mapper/data_contract_builder.py +446 -0
  100. vigil_mapper/findings_builder.py +716 -0
  101. vigil_mapper/fingerprint.py +53 -0
  102. vigil_mapper/hotspot_builder.py +539 -0
  103. vigil_mapper/map_common.py +449 -0
  104. vigil_mapper/map_errors.py +55 -0
  105. vigil_mapper/map_models.py +431 -0
  106. vigil_mapper/map_models_ext.py +206 -0
  107. vigil_mapper/map_models_findings.py +130 -0
  108. vigil_mapper/map_storage.py +455 -0
  109. vigil_mapper/parse_cache.py +795 -0
  110. vigil_mapper/refactor_boundary_builder.py +266 -0
  111. vigil_mapper/runtime_builder.py +527 -0
  112. vigil_mapper/runtime_tracer.py +243 -0
  113. vigil_mapper/runtime_tracer_entry.py +199 -0
  114. vigil_mapper/semantic_diff.py +71 -0
  115. vigil_mapper/source_adapters/__init__.py +109 -0
  116. vigil_mapper/source_adapters/_base.py +264 -0
  117. vigil_mapper/source_adapters/_ir.py +156 -0
  118. vigil_mapper/source_adapters/_lexer.py +309 -0
  119. vigil_mapper/source_adapters/_patterns.py +212 -0
  120. vigil_mapper/source_adapters/_treesitter.py +182 -0
  121. vigil_mapper/source_adapters/go.py +553 -0
  122. vigil_mapper/source_adapters/java.py +541 -0
  123. vigil_mapper/source_adapters/javascript.py +626 -0
  124. vigil_mapper/source_adapters/python.py +325 -0
  125. vigil_mapper/source_adapters/typescript.py +749 -0
  126. vigil_mapper/structural_builder.py +586 -0
  127. vigil_mcp/__init__.py +1 -0
  128. vigil_mcp/_jobs.py +587 -0
  129. vigil_mcp/_paths.py +93 -0
  130. vigil_mcp/forensic_server.py +419 -0
  131. vigil_mcp/map_server.py +452 -0
@@ -0,0 +1,131 @@
1
+ vigil_codeintel-0.1.0.dist-info/licenses/LICENSE,sha256=G2pHFIBvoEe4OpTuMyExwovPm3MztKOENi3Kk-M67gY,1062
2
+ vigil_forensic/__init__.py,sha256=ztEStgYGEcqRg7tEuc6jfZ8zRpxBdUALpqtebQEGyLE,8495
3
+ vigil_forensic/_git_utils.py,sha256=yS7dr3fQj5sLdGs8hvJza8WuEqUhPdT9x5DTRlJQXV8,5572
4
+ vigil_forensic/_shared.py,sha256=IBMC7WQI9XLLApvQZrtsJNZA1_44nYN-ux3QOeLb-xE,17930
5
+ vigil_forensic/_stubs.py,sha256=ssnke9spSeSjy_YI38-BhC1rFHSEMPdOwB9KRrr_K1o,6955
6
+ vigil_forensic/gate_models.py,sha256=pmXN6geOFnNnTD884unZSfkNKs2G8ZWpFcrBrVO-inc,16229
7
+ vigil_forensic/gate_profile.json,sha256=2JUfR3VhXwrbCl2wb1q5PzRXcjXen-DFtevdwFlH3Aw,957
8
+ vigil_forensic/gate_registry.py,sha256=P6whCRbytvFKd0Bb19_p2GHxdOv7wivE_jZALCT7u88,546
9
+ vigil_forensic/language_profiles.py,sha256=Sziyu6ejbEJbQp6Ns3gPwnELLyUYXfa_gjuFnK3gQas,7692
10
+ vigil_forensic/meta_findings.py,sha256=vLkZZC5s4yY9ACG4g04c8dqqucAgXOpJXqp-BdtYsM4,6955
11
+ vigil_forensic/self_audit.py,sha256=7hRXFHTua9iheVIisqMoRMxmiJsAQ7W7fCrnk_gScCU,32738
12
+ vigil_forensic/source_analysis.py,sha256=eHF5XMf8zcf44nd-maLQqX68uvsTEgkg8CAC6qjuBsE,5694
13
+ vigil_forensic/gate_checks/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
14
+ vigil_forensic/gate_checks/_ast_helpers.py,sha256=mJOYmh6KVfgLDrwtosiWZvkzuHA8dJXytJBuBWY4wyg,23612
15
+ vigil_forensic/gate_checks/_deployment_detector.py,sha256=UxTiOuC_eJ_cIj030LobQ_OaJmm4q6pjZaHl-4f9Bus,21372
16
+ vigil_forensic/gate_checks/atomic_write_checks.py,sha256=LjLpagkaGRMV8YsehK2W1Eu0Fl2D4ubqJIONra9KRWA,44090
17
+ vigil_forensic/gate_checks/authority_checks.py,sha256=gXQJlQ5-N0_qNgpJgzCWPNaZght96BRJMyFy-F3DyEs,3929
18
+ vigil_forensic/gate_checks/boundary_breach_checks.py,sha256=0i01WU0zoAo8hOkgMN0asYgJWW2XqQ8QliZK1G1nOWI,7638
19
+ vigil_forensic/gate_checks/broad_except_checks.py,sha256=t7ap7sIpOIP52RTsttSaTK1NT00_TzPoQxnfC7Lw3e0,13090
20
+ vigil_forensic/gate_checks/broad_except_hidden_sentinel_checks.py,sha256=vQtGg7nsMoX-sZRLiMGZyv--GZruq13XwnUbJs7Wtg4,14614
21
+ vigil_forensic/gate_checks/common.py,sha256=Iw1MKeDSyuhlgfWLSXEw0omnKJcwBPLWs2-IXrvRMkc,9158
22
+ vigil_forensic/gate_checks/config_safety_checks.py,sha256=MtMmvC4-tOcCAZ5eAFevhp67fcDElPJOTjSS5n4H-iw,31394
23
+ vigil_forensic/gate_checks/config_ssot_checks.py,sha256=73leRSbo0WxQkPRGHLX9TuHDBAMAyYqhhl99l67gPaM,4054
24
+ vigil_forensic/gate_checks/conflict_checks.py,sha256=13aPgHjBLukzpEa4m94z7ePx8sCYEztXqk6SdxgsYug,7566
25
+ vigil_forensic/gate_checks/context_fallback_checks.py,sha256=WYYCwEAYbzQwSCIzt0O9hqX7IJH4DAihd-qAMJYuXiU,29831
26
+ vigil_forensic/gate_checks/context_health_checks.py,sha256=vt6PChyS7EBn3cTktmNlNzdm7P91oSmX3KKXT-jcRaM,12561
27
+ vigil_forensic/gate_checks/contract_shape_drift_checks.py,sha256=Wy4R_J1H8z7SlxSPtiTuQXMshkHJZUMzFyS7tVlQII4,20221
28
+ vigil_forensic/gate_checks/dirty_baseline_check.py,sha256=oGVdrwsLxOFr3gtT0HSo0j-Sxaf90J4cs04pj-4si7s,9848
29
+ vigil_forensic/gate_checks/duplication_checks.py,sha256=3FaOtVAwfbz4yExiA6Cx1yqEel55kSw7612BltkdwQY,19397
30
+ vigil_forensic/gate_checks/embedded_string_checks.py,sha256=docNGY8uLZfrz7FxvqtTj-4N7DgyHlU9Ow3Y8I0J-K4,5762
31
+ vigil_forensic/gate_checks/empty_output_checks.py,sha256=WuALy0UbgLoBnKK588Ussepi-oFcegY5kZyzrfDMSb8,3792
32
+ vigil_forensic/gate_checks/encoding_checks.py,sha256=f-i8sMJmiT3svqDLGPdeJy8eEe4tjc-VRpED_T2zy7E,36926
33
+ vigil_forensic/gate_checks/export_completeness_checks.py,sha256=XSAvko-vx1-6In927kmvKNouYTf27Th4ybV7_ijtUNs,6351
34
+ vigil_forensic/gate_checks/fallback_checks.py,sha256=vHRFnqWr4fIxRSHTU8nO3CGeuoZBzMLg8KFrcfwIjfM,2193
35
+ vigil_forensic/gate_checks/file_proliferation_checks.py,sha256=aP9VHBeAABJjh0BrwQb0XoAEx7IPB_66p2urADamRJY,8405
36
+ vigil_forensic/gate_checks/fix_without_test_checks.py,sha256=RoC3LWCTf9jRsWHrDtj9G1gCGh8a-DoVf933UIYkVec,3154
37
+ vigil_forensic/gate_checks/god_object_zones_checks.py,sha256=t-yjWogWLlljrZlZNUvE-fybOtRHRHhETLi56RqJa7o,5684
38
+ vigil_forensic/gate_checks/hallucination_checks.py,sha256=aey86cBSJEpoYGdX5bhLVLlzEBSR85LkWfB5_72Dd9w,25206
39
+ vigil_forensic/gate_checks/hunter_artifact_completeness_check.py,sha256=MHPGjZHabMVJXGtbHC1pxEbHvZugcasRODRbiiDbjBw,6535
40
+ vigil_forensic/gate_checks/implementation_overfit_checks.py,sha256=R9t0qh7zXiR7k5uvucy1PjNhD7C546JddyzXYHqtIDA,14297
41
+ vigil_forensic/gate_checks/import_integrity_checks.py,sha256=ppnzzA7j89ScLPS_49P3IQNBLW4oZQ1JM3vkKvJQpx4,9894
42
+ vigil_forensic/gate_checks/imports_in_function_checks.py,sha256=BpK96Bg7Supo3xziKPq7VqWXHVqktns3DRLxTs18eM8,11209
43
+ vigil_forensic/gate_checks/ml_checks.py,sha256=vnHzUWZmwSntMN6MfFt6xQoNNdy1DqYiqnCHdZrxqHQ,13025
44
+ vigil_forensic/gate_checks/performance_checks.py,sha256=Ftksl3zrv9h81VoflSMMVjtoC2MqzKl4ZxBSX8OYHUY,4580
45
+ vigil_forensic/gate_checks/project_specific_runner.py,sha256=htTX0RFgvfxz6fbg_qgpByqTjrUQSdUc6HEM4HD2Bc0,27218
46
+ vigil_forensic/gate_checks/provider_capability_checks.py,sha256=GmFkTs6MKgZJP5rYjo6n1OOdx8LfkXfF655blNVxX-4,2876
47
+ vigil_forensic/gate_checks/refactor_completeness_checks.py,sha256=Ec7HiDFp5fOAp2L_bgHKIYU6e_oLv5o-SkjOHBKJtyQ,10832
48
+ vigil_forensic/gate_checks/reliability_checks.py,sha256=TPY9BoUv-jPYTAOBdHezubm8sGahzfZT6L2BqQu8tDk,15981
49
+ vigil_forensic/gate_checks/reporting_checks.py,sha256=c5sBYT0aJ8afzlV47J4P5TBqPhrqb56yIx-ID3iKmlo,2580
50
+ vigil_forensic/gate_checks/runtime_behavior_checks.py,sha256=HNQufbDMMpZOiKBujlmC51B2BKwuwWP5gTDkUL7gzxk,9185
51
+ vigil_forensic/gate_checks/security_injection_checks.py,sha256=-Li5D2Nyq4gN96VotgAgx0eCBmLVN4CCtItq3uZxHm0,13921
52
+ vigil_forensic/gate_checks/semantic_intent_checks.py,sha256=ggZF4Hp5jlOfLtSvuzq5hpSr6_fHjIj6c8jxEgmZHVo,5758
53
+ vigil_forensic/gate_checks/size_complexity_checks.py,sha256=vXKOhQVKCgKD5cphShE2NxfnOZAMHdYNRUmYd8ZyOQk,17085
54
+ vigil_forensic/gate_checks/stuck_feature_flag_checks.py,sha256=XkKbmwizrcrVw1-b8tXB2By5Xc7sAzKo1V9fC2bF15Y,13523
55
+ vigil_forensic/gate_checks/syntax_validity_checks.py,sha256=CkSo4PZ5zk3NBRS82t_hSZASs4fnTIEuFgcnWr527lA,8363
56
+ vigil_forensic/gate_checks/temporal_freshness_checks.py,sha256=luNFPGcuW1SJL4KeGwdZzo4zJecc8DCMfBFoxtJLhGE,3357
57
+ vigil_forensic/gate_checks/test_quality_checks.py,sha256=iOXa1lonFsfp-9REBkcu8iUOwIxsmnu1rdfH6A4zcLM,39904
58
+ vigil_forensic/gate_checks/testing_checks.py,sha256=oOyGE-Eh0RDd949yH0_JAXb6jfNJO0aAHflGHgm4Qgc,7713
59
+ vigil_forensic/gate_checks/toctou_checks.py,sha256=9oqBXkOPuXroUGc-rncW5iZfUCB5ltUYSYXh4j9aVpM,14754
60
+ vigil_forensic/gate_checks/type_checking_checks.py,sha256=yEj20q1mu-sgmIpIhlyh99uErqTizcUX2sp7PGsIR9g,14015
61
+ vigil_forensic/gate_checks/forensic_cluster_runners/__init__.py,sha256=G9V7s3i81VFDtyQ8NclIZe0tZCWVZL4WSQH7UfpD4eE,346
62
+ vigil_forensic/gate_checks/forensic_cluster_runners/_helpers.py,sha256=V8XGLqh8BhWGGsyfMA2sXPiffgof81-XT0852Q3P4UE,3230
63
+ vigil_forensic/gate_checks/forensic_cluster_runners/advanced_checks.py,sha256=w7brafGnfsRqWAKlPLbfP1c3MeVKr62PbQZWJ0VsgZc,11555
64
+ vigil_forensic/gate_checks/forensic_cluster_runners/core.py,sha256=IdVCMKFM8HX-Az--NwlrNtz3Idge0PmqOHp53Emekso,13530
65
+ vigil_forensic/gate_checks/forensic_cluster_runners/integrity_checks.py,sha256=vrd_pt5TJ_T1QFdcASqwl0tV8TStGjtvaqMYI_Z9I_c,7902
66
+ vigil_forensic/gate_checks/forensic_cluster_runners/quality_checks.py,sha256=V3vpIUiNsW_74N7VN2dcyJG8083NiOTZmHnQ98r7EZI,24505
67
+ vigil_forensic/gate_checks/forensic_clusters/__init__.py,sha256=E7nSAEpwa-m4OTBIZZMNpiIWhb9YVYCA-CYfa8_7xro,5311
68
+ vigil_forensic/gate_checks/forensic_clusters/allowlist.py,sha256=8-1i5ZoCwkT2hTx6-01EvXGjQajMglByTXihmh7kDfM,15904
69
+ vigil_forensic/gate_checks/forensic_clusters/allowlist_writer.py,sha256=ZV7dUcGIb-Sj4881KLMUICNxVOPlBTUYqanfIJbeNyQ,11170
70
+ vigil_forensic/gate_checks/forensic_clusters/api_protocol.py,sha256=F0z6RVHQl1YNVXoB0TGPXy9nmU9dlY9R4BjZCKIGEqU,10380
71
+ vigil_forensic/gate_checks/forensic_clusters/async_quality.py,sha256=kFDbxpVe5UzgXzqZrx6RO9ZCokNqcGMiGikG1NFUcOo,49447
72
+ vigil_forensic/gate_checks/forensic_clusters/code_style.py,sha256=2WIT33zmsYU29IeRiiw80BrkwUSjt7KHfhjqZzX949g,34230
73
+ vigil_forensic/gate_checks/forensic_clusters/core.py,sha256=jkNE8xfHoXI-GfLvrJ5t9M05FeGfqPnMNlF1Gtme0Jk,12803
74
+ vigil_forensic/gate_checks/forensic_clusters/data_quality.py,sha256=TqgmY_p1ecW2TE3BwIVI53GrVAgNUJl6QpGVrb8j0es,34952
75
+ vigil_forensic/gate_checks/forensic_clusters/dead_code.py,sha256=2nxQfuDEzvSN1601EM5gNy7LW7XCUPtyWUjw5cueTzg,19895
76
+ vigil_forensic/gate_checks/forensic_clusters/edit_mutation.py,sha256=rDrEr_GsB9BtmANCS6GM-fxQP2zDDqte6uQHtquyEsk,35761
77
+ vigil_forensic/gate_checks/forensic_clusters/exception_boundary.py,sha256=zF_uCIfXdhVg0HuwBS0qD7aroO2qiHh9jzvXQxD2Gqg,11403
78
+ vigil_forensic/gate_checks/forensic_clusters/legacy_debt.py,sha256=UFrErFRRhkT4RmWI47oKXL9InnWBjFspXRcKPxBrKNA,21283
79
+ vigil_forensic/gate_checks/forensic_clusters/static_analysis.py,sha256=qVUSCDDeiEO5eisR1PhQ0DpJbjxV43cYhQQ1yxqiPbY,36931
80
+ vigil_forensic/gate_checks/forensic_clusters/structural_quality.py,sha256=K12qQ6TUcgEeJ4vvieWavuUXmH9Uehd3UbN4dvFIBrI,12972
81
+ vigil_forensic/gate_packs/__init__.py,sha256=19PwrW5hqaOdzEh_b2s-Dx2_c6WHizwT8ya1HavqjAE,36
82
+ vigil_forensic/gate_packs/universal.py,sha256=DyqAffKJTsYucs10ErmRMCvsTldSby-0Z3v4XiG8E-w,11990
83
+ vigil_mapper/__init__.py,sha256=SeinM7MVoZWqse33Ek5eJVhHhaooLZAwL3HmoRNaHMY,3497
84
+ vigil_mapper/_ast_helpers_minimal.py,sha256=wT2eYRia1E0ZRbY832ysWcBB3f-jGzBAqtyINXF-CEg,7854
85
+ vigil_mapper/_extract_imports_impl.py,sha256=-MhkutstDl3BZZWL947KfuSXrBMT7Uv2b0qQAeCmJSk,4868
86
+ vigil_mapper/_file_count_guard.py,sha256=Hdm11ukWBJKwkH_akEMlwvk7Gz4-rQJs54Dnjf6ynho,4727
87
+ vigil_mapper/_git_utils.py,sha256=yS7dr3fQj5sLdGs8hvJza8WuEqUhPdT9x5DTRlJQXV8,5572
88
+ vigil_mapper/_runtime_ast.py,sha256=NbeLPotsmUGOHiiVtsa3vMrIA-ZYuMl5bOGfgQFyBTg,16809
89
+ vigil_mapper/_runtime_dispatch.py,sha256=wtq6ynMLsexxxhL3BOev4EVW1fZi7GEBFFHJ-PGOsGo,6089
90
+ vigil_mapper/_seed_helpers.py,sha256=9qnPLPbVA-_ocDGP0nT1sTUptGAtSvmh5Rh3rINJn-s,2968
91
+ vigil_mapper/authority_builder.py,sha256=KOTcW8ZQtTOICIu804lUp-jDntfsBcbhd8jq39J82RQ,47207
92
+ vigil_mapper/cli_entry.py,sha256=XUhn2gCMddQX6qfg9D447qgqQbk4GjFX7ReO1AjHFo8,29330
93
+ vigil_mapper/conflict_builder.py,sha256=WumSNeh16HDVgwMfr1vByYM_aVrPApQ2leYojS20ALk,29131
94
+ vigil_mapper/data_contract_builder.py,sha256=ZLnKNDIa3N7vatEUiGgOfTOZr5W1e_uShn-zMqp3vsE,17238
95
+ vigil_mapper/findings_builder.py,sha256=pbcca_Qnqb5w2luj8VltELANfddjB5P8y6ymuuF-_JM,27226
96
+ vigil_mapper/fingerprint.py,sha256=K5X8GPcecpZs9KAr7F-ys-S9gna-vanfN-AUZzvBYR8,1732
97
+ vigil_mapper/hotspot_builder.py,sha256=xnPyEZrXx34NhkjKYtbRJrFz-aTqm4kc6jQ542IEShs,19977
98
+ vigil_mapper/map_common.py,sha256=NG4liaJsvSGQ3NqjhUncw7N65qJl8N4jz0JvcArJJsg,16097
99
+ vigil_mapper/map_errors.py,sha256=y0gC45SLfwvDJBvsbHQbfBAsln8RJGXQtn2TJ9WxsBE,1395
100
+ vigil_mapper/map_models.py,sha256=kEdfY3EQ2lyUxUVUAitDjWRceue1pD-mInAktN45yig,17269
101
+ vigil_mapper/map_models_ext.py,sha256=-m7w2OY39S6OuQQNUQlnH769Cui68sYgbdAW7aHrLmU,7262
102
+ vigil_mapper/map_models_findings.py,sha256=RqrbFIVlcNQlWOwe7YUftRLJw9azDhJToNh5a6HcYrg,4780
103
+ vigil_mapper/map_storage.py,sha256=Sz3CinNcHlywUtnh7M42psHxDJUB3YLP7HEduD7o9nQ,17565
104
+ vigil_mapper/parse_cache.py,sha256=JfC5TWhFZYenETO9J0A61ZSLoUZIPhjIJGD54dGJU8g,29983
105
+ vigil_mapper/refactor_boundary_builder.py,sha256=n_mBmfxgSsoAtRvsyz2BNIHdm0STQKh7ck0pfWMnIy8,9750
106
+ vigil_mapper/runtime_builder.py,sha256=iXGEt6Quk_pyUb2-cvKT9HviAEbFna8zzPNNLi374tM,20058
107
+ vigil_mapper/runtime_tracer.py,sha256=HvmmfiL-BsNI037NzjN93HD-0hpYOuTzKfJzipjl6cc,8088
108
+ vigil_mapper/runtime_tracer_entry.py,sha256=y8nUf7rXJFOXMs08hsAhJnrhvDhVO3OQOBMGo3Xdgzw,7284
109
+ vigil_mapper/semantic_diff.py,sha256=ImGQH8cmPUvNHVeIYQG0I8xAx334C76rTUpSeoozYns,2799
110
+ vigil_mapper/structural_builder.py,sha256=FWxOYhO3IfNGkwkFyy5PBuToLslGMAa7UayWQT_eiV8,22260
111
+ vigil_mapper/source_adapters/__init__.py,sha256=8cfHxkm5-BBXG0jfX2FAKhtFb5WWEfH4PhsNJQHuATI,3695
112
+ vigil_mapper/source_adapters/_base.py,sha256=Dh41x0Uh2nDjlq5Cu2e9qWKxiapCSJjUtBzZW7MODjM,10550
113
+ vigil_mapper/source_adapters/_ir.py,sha256=MjnqMn4CXAZpUX88HdL3ZffNqdWUKy6o0FpTa4rmZHg,5277
114
+ vigil_mapper/source_adapters/_lexer.py,sha256=osWo-5I5piqWN163XPDXgTsGa64Eh7eOspL5TA8o7KU,11057
115
+ vigil_mapper/source_adapters/_patterns.py,sha256=yZC7QyXhQp7lD32Q8He4Ev9Yn__vnqNR3pRID5fpWOs,6692
116
+ vigil_mapper/source_adapters/_treesitter.py,sha256=dsOFj7LKWzUMjgiMe9kqs2ZRQ760dx6YdB71IF-6hLE,5695
117
+ vigil_mapper/source_adapters/go.py,sha256=cJ3-G4hRsZGgBym21pyIoTorViK-LQdtXITo50HPV0E,23504
118
+ vigil_mapper/source_adapters/java.py,sha256=BNLAXeEBv4ad81X4Uab-UxB2Gv1XuzMbuGU24pzX_08,21817
119
+ vigil_mapper/source_adapters/javascript.py,sha256=TpS0J_yAoB2ylZV7LW2Y0TJ1mwTh4cBeLe8PUeG0trU,26497
120
+ vigil_mapper/source_adapters/python.py,sha256=FfU04OtRmqGFr8qssgIh_KXcNQ_9cNsiyg-ijt_BHeg,13050
121
+ vigil_mapper/source_adapters/typescript.py,sha256=SMbLwpkojd2ARF3i1mNG4Kycl-oxrZjfb92VCg-BCUI,31079
122
+ vigil_mcp/__init__.py,sha256=LIk3_JrCxyTC4zv_pVCdahqIJ0lGFzNV7Jl1K80-dSQ,84
123
+ vigil_mcp/_jobs.py,sha256=r2doRMvffcvNKJW4Vpz2f3LP-gjTsNFhLBZ-50vkTPA,23716
124
+ vigil_mcp/_paths.py,sha256=lP-qRiTXr_SdE-f6Jym_wsBJUJoDEGLZ3ZceBRdntIg,3257
125
+ vigil_mcp/forensic_server.py,sha256=UVHbRDL_R0qi7WyMmqWG7craTghy52qoKvZbm-ZzFdU,16152
126
+ vigil_mcp/map_server.py,sha256=IHrM3rO5MGD0hayVJ-tQKyZ-_IebSaRaEjm0u41LOPc,17852
127
+ vigil_codeintel-0.1.0.dist-info/METADATA,sha256=OCGd43cZ2z5Syacs3gzmCUcgtgF1-LiRcPg7UU3WYVc,45607
128
+ vigil_codeintel-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
129
+ vigil_codeintel-0.1.0.dist-info/entry_points.txt,sha256=UZ9zWBfDOUC-Girrfq4ANLucwkIXgtgM74pYlpmpYLY,115
130
+ vigil_codeintel-0.1.0.dist-info/top_level.txt,sha256=t29Bu-mTMgwyWNFiPPnW7bOH_y_tLm7mkcHj39EVLPY,38
131
+ vigil_codeintel-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ vigil-forensic-mcp = vigil_mcp.forensic_server:main
3
+ vigil-mapper-mcp = vigil_mcp.map_server:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Julio
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,3 @@
1
+ vigil_forensic
2
+ vigil_mapper
3
+ vigil_mcp
@@ -0,0 +1,224 @@
1
+ """vigil_forensic — standalone static forensic gate package.
2
+
3
+ Public API
4
+ ----------
5
+ run_forensic_audit(project_dir, *, gates=None, severity="LOW", all_languages=True) -> dict
6
+ Run static forensic gates on a project directory and return findings as data.
7
+
8
+ Returned dict shape::
9
+
10
+ {
11
+ "exit_code": int, # 0 = clean, 1 = high/critical findings, 2 = error
12
+ "findings": [ # list of finding dicts (filtered by severity)
13
+ {
14
+ "check_id": str,
15
+ "category": str,
16
+ "title": str,
17
+ "severity": str, # "low" | "medium" | "high" | "critical"
18
+ "impact": str,
19
+ "summary": str,
20
+ "recommendation": str,
21
+ "evidence": [{"kind": str, "path": str, "detail": str}],
22
+ "fingerprint": str,
23
+ "confidence": float,
24
+ "applicability": str,
25
+ "analysis_mode": str,
26
+ "applicability_reason": str,
27
+ },
28
+ ...
29
+ ],
30
+ "meta": {
31
+ "project_dir": str,
32
+ "source_files_scanned": int,
33
+ "gates_attempted": int,
34
+ "gates_succeeded": int,
35
+ "gates_errored": int,
36
+ "total_findings": int,
37
+ "severity_counts": {"low": int, "medium": int, ...},
38
+ "category_counts": {str: int},
39
+ "schema_version": "1.1",
40
+ "gates_skipped": [{"gate_id": str, "reason": str}],
41
+ ...
42
+ },
43
+ "errors": [{"check_id": str, "error": str}],
44
+ }
45
+
46
+ Zero imports from BRAIN, SYSTEM, or INTERFACE. May import vigil_mapper
47
+ (sibling standalone package).
48
+ """
49
+ from __future__ import annotations
50
+
51
+ from pathlib import Path
52
+ from typing import Any, Optional
53
+
54
+
55
+ def run_forensic_audit(
56
+ project_dir: str | Path,
57
+ *,
58
+ gates: Optional[list[str]] = None,
59
+ severity: str = "LOW",
60
+ all_languages: bool = True,
61
+ max_files: int = 800,
62
+ cancel_event: Optional[Any] = None,
63
+ ) -> dict[str, Any]:
64
+ """Run static forensic gates on *project_dir* and return structured findings.
65
+
66
+ Parameters
67
+ ----------
68
+ project_dir:
69
+ Path to the project root to audit.
70
+ gates:
71
+ Optional list of gate check_ids to run. None means run all applicable
72
+ file-based gates (skipping runtime-only gates as per skip_in_static policy).
73
+ Gates listed in ``<project_dir>/.cortex/disabled_gates.json`` are always
74
+ skipped (reported in ``meta["gates_skipped"]`` with reason
75
+ ``"disabled_by_project"``) regardless of this argument.
76
+ severity:
77
+ Minimum severity floor for the returned ``findings``. One of
78
+ "LOW", "MEDIUM", "HIGH", "CRITICAL" (case-insensitive); ordering is
79
+ LOW < MEDIUM < HIGH < CRITICAL. Defaults to "LOW" (all findings).
80
+ Findings below the floor are removed from ``findings``; the ``meta.*``
81
+ counts are computed BEFORE this filter (so they always reflect the full
82
+ finding set), and ``meta["findings_after_severity_filter"]`` records the
83
+ post-filter count when a non-LOW floor is used.
84
+ all_languages:
85
+ Reserved for future use. Currently all source extensions recognized by
86
+ vigil_mapper.source_adapters are included automatically.
87
+ max_files:
88
+ Anti-hang ceiling on the COLLECTED source-file count. Forensic does a
89
+ per-gate AST walk over every file (~0.4 s/file), so on a repo with
90
+ thousands of files a full scan takes hours and effectively hangs. When
91
+ the collected count exceeds ``max_files`` (default 800 ≈ a ~5 min
92
+ ceiling) NO gates run; instead a FAST structured result is returned with
93
+ ``meta["skipped_reason"] == "too_many_files"`` plus ``top_subdirs`` and a
94
+ ``suggestion`` to narrow scope. Raise ``max_files`` to force a full scan.
95
+
96
+ Returns
97
+ -------
98
+ dict with keys: "exit_code", "findings", "meta", "errors".
99
+ Never raises — errors are captured in the returned dict.
100
+ """
101
+ import traceback
102
+ from vigil_forensic.self_audit import (
103
+ discover_source_files,
104
+ build_synthetic_context,
105
+ run_gates,
106
+ build_json_report,
107
+ filter_findings_by_severity,
108
+ _load_project_disabled_gates,
109
+ _probe_meta_integrity,
110
+ GateOutcome,
111
+ )
112
+ from vigil_forensic.meta_findings import drain_meta_findings
113
+
114
+ project_dir = Path(project_dir).resolve()
115
+ if not project_dir.is_dir():
116
+ return {
117
+ "exit_code": 2,
118
+ "findings": [],
119
+ "meta": {"error": f"project_dir is not a directory: {project_dir}"},
120
+ "errors": [{"check_id": "init", "error": f"Not a directory: {project_dir}"}],
121
+ }
122
+
123
+ gates_filter: Optional[set[str]] = set(gates) if gates else None
124
+
125
+ try:
126
+ source_files = discover_source_files(project_dir)
127
+ except Exception as exc:
128
+ return {
129
+ "exit_code": 2,
130
+ "findings": [],
131
+ "meta": {"error": f"file discovery failed: {exc}"},
132
+ "errors": [{"check_id": "discover", "error": traceback.format_exc()}],
133
+ }
134
+
135
+ if not source_files:
136
+ return {
137
+ "exit_code": 0,
138
+ "findings": [],
139
+ "meta": {
140
+ "project_dir": str(project_dir),
141
+ "source_files_scanned": 0,
142
+ "gates_attempted": 0,
143
+ "gates_succeeded": 0,
144
+ "gates_errored": 0,
145
+ "total_findings": 0,
146
+ "severity_counts": {},
147
+ "category_counts": {},
148
+ "schema_version": "1.1",
149
+ "gates_skipped": [],
150
+ "gates_skipped_in_static": [],
151
+ "note": "no source files found",
152
+ },
153
+ "errors": [],
154
+ }
155
+
156
+ # Anti-hang file-COUNT guard. Forensic walks every file per gate (~0.4 s/file)
157
+ # so thousands of files = hours. When the collected count exceeds max_files we
158
+ # do NOT build context or run gates; we return a FAST structured skip result
159
+ # (just count + group-by-top-subdir) telling the caller to narrow scope.
160
+ if len(source_files) > max_files:
161
+ from vigil_mapper._file_count_guard import build_too_many_files_meta
162
+
163
+ meta = build_too_many_files_meta(
164
+ source_files, max_files, entry_call="start_forensic_audit"
165
+ )
166
+ meta["project_dir"] = str(project_dir)
167
+ return {
168
+ "exit_code": 0,
169
+ "findings": [],
170
+ "meta": meta,
171
+ "errors": [],
172
+ }
173
+
174
+ try:
175
+ ctx = build_synthetic_context(project_dir, source_files)
176
+ except Exception as exc:
177
+ return {
178
+ "exit_code": 2,
179
+ "findings": [],
180
+ "meta": {"error": f"context build failed: {exc}"},
181
+ "errors": [{"check_id": "build_context", "error": traceback.format_exc()}],
182
+ }
183
+
184
+ # Per-project gate opt-out: <project_dir>/.cortex/disabled_gates.json.
185
+ # Malformed file → meta finding + empty set (never raises, never silently
186
+ # disables). Loaded here so the meta finding is drained with the rest below.
187
+ disabled_gates = _load_project_disabled_gates(project_dir)
188
+
189
+ outcomes, gates_skipped = run_gates(
190
+ ctx, gates_filter, workers=1, cancel_event=cancel_event,
191
+ disabled_gates=disabled_gates,
192
+ )
193
+
194
+ # Probe audit infrastructure for corrupted artifacts
195
+ _probe_meta_integrity(project_dir)
196
+ meta_findings = drain_meta_findings()
197
+ if meta_findings:
198
+ outcomes.append(GateOutcome(check_id="meta_integrity_probe", ok=True, findings=list(meta_findings)))
199
+
200
+ report = build_json_report(outcomes, project_dir, len(source_files), gates_skipped=gates_skipped)
201
+
202
+ # Apply severity filter to findings list (meta counts are pre-filter)
203
+ min_sev = severity.lower()
204
+ if min_sev != "low":
205
+ filtered = filter_findings_by_severity(report["findings"], min_sev)
206
+ report = dict(report)
207
+ report["findings"] = filtered
208
+ report["meta"] = dict(report["meta"])
209
+ report["meta"]["findings_after_severity_filter"] = len(filtered)
210
+
211
+ # Compute exit code
212
+ sev_counts = report["meta"].get("severity_counts", {})
213
+ critical_or_high = sev_counts.get("critical", 0) + sev_counts.get("high", 0)
214
+ exit_code = 1 if critical_or_high > 0 else 0
215
+
216
+ return {
217
+ "exit_code": exit_code,
218
+ "findings": report["findings"],
219
+ "meta": report["meta"],
220
+ "errors": report.get("errors", []),
221
+ }
222
+
223
+
224
+ __all__ = ["run_forensic_audit"]
@@ -0,0 +1,178 @@
1
+ """Neutral shared git helpers. Depends only on stdlib.
2
+
3
+ Used by map_builder (churn) and gate_checks (diff-based checks).
4
+ Never imports from gate_checks or map_builder (correct dependency direction).
5
+
6
+ Public API:
7
+ git_show(path, ref, project_dir) -- file content at git ref
8
+ git_log_numstat(project_dir, since) -- churn line counts per file
9
+ git_has_repo(project_dir) -- is inside a git work tree?
10
+ git_head_sha(project_dir) -- current HEAD SHA
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import logging
15
+ import subprocess
16
+ from pathlib import Path
17
+
18
+ _log = logging.getLogger(__name__)
19
+
20
+ __all__ = [
21
+ "git_show",
22
+ "git_log_numstat",
23
+ "git_has_repo",
24
+ "git_head_sha",
25
+ ]
26
+
27
+
28
+ def git_show(
29
+ path: str,
30
+ ref: str = "HEAD~1",
31
+ project_dir: Path | None = None,
32
+ ) -> str | None:
33
+ """Return file content at git ref or None on failure.
34
+
35
+ Args:
36
+ path: Relative file path (as stored in git, e.g. "BRAIN/foo.py").
37
+ ref: Git ref to read from. Defaults to "HEAD~1".
38
+ project_dir: If given, passes ``-C project_dir`` to git so the command
39
+ runs in the correct working directory regardless of the caller's cwd.
40
+
41
+ Returns:
42
+ File content as a string, or None if the file didn't exist at that ref,
43
+ git is unavailable, or any other error occurs (fail-open).
44
+ """
45
+ args = ["git"]
46
+ if project_dir is not None:
47
+ args += ["-C", str(project_dir)]
48
+ args += ["show", "%s:%s" % (ref, path)]
49
+
50
+ try:
51
+ r = subprocess.run(
52
+ args,
53
+ capture_output=True,
54
+ text=True,
55
+ encoding="utf-8",
56
+ errors="replace",
57
+ timeout=10,
58
+ shell=False,
59
+ )
60
+ if r.returncode != 0:
61
+ return None
62
+ return r.stdout
63
+ except (subprocess.SubprocessError, FileNotFoundError, OSError) as exc:
64
+ _log.debug("git_show failed for %s@%s: %s", path, ref, type(exc).__name__)
65
+ return None
66
+
67
+
68
+ def git_log_numstat(
69
+ project_dir: Path,
70
+ since: str = "90.days",
71
+ ) -> dict[str, int]:
72
+ """Return ``{relative_path: churn_line_count}`` for commits since *since*.
73
+
74
+ Churn is defined as added + deleted lines across all commits in the window.
75
+ Binary files (where git outputs ``-`` for line counts) are skipped.
76
+
77
+ Args:
78
+ project_dir: Absolute path to the project root (must be inside a git repo).
79
+ since: ``--since`` value passed to ``git log``, e.g. ``"90.days"`` or
80
+ ``"2025-01-01"``.
81
+
82
+ Returns:
83
+ Dict mapping each file path to total churn line count. Returns an empty
84
+ dict if the directory is not a git repo, git is unavailable, or any
85
+ subprocess error occurs (fail-open).
86
+ """
87
+ try:
88
+ r = subprocess.run(
89
+ [
90
+ "git",
91
+ "-C", str(project_dir),
92
+ "log",
93
+ "--numstat",
94
+ "--since=%s" % since,
95
+ "--pretty=format:",
96
+ ],
97
+ capture_output=True,
98
+ text=True,
99
+ encoding="utf-8",
100
+ errors="replace",
101
+ timeout=30,
102
+ shell=False,
103
+ )
104
+ if r.returncode != 0:
105
+ return {}
106
+ result: dict[str, int] = {}
107
+ for line in r.stdout.splitlines():
108
+ parts = line.split("\t")
109
+ if len(parts) != 3:
110
+ continue
111
+ added, deleted, path = parts
112
+ # Binary files have "-" for line counts — skip them
113
+ if added == "-" or deleted == "-":
114
+ continue
115
+ try:
116
+ churn = int(added) + int(deleted)
117
+ except ValueError:
118
+ continue
119
+ result[path] = result.get(path, 0) + churn
120
+ return result
121
+ except (subprocess.SubprocessError, FileNotFoundError, OSError) as exc:
122
+ _log.debug("git_log_numstat failed in %s: %s", project_dir, type(exc).__name__)
123
+ return {}
124
+
125
+
126
+ def git_has_repo(project_dir: Path) -> bool:
127
+ """Return True if *project_dir* is inside a git work tree.
128
+
129
+ Uses ``git rev-parse --is-inside-work-tree``. Returns False on any error,
130
+ including git not installed or directory not being a repo (fail-open).
131
+ """
132
+ try:
133
+ r = subprocess.run(
134
+ [
135
+ "git",
136
+ "-C", str(project_dir),
137
+ "rev-parse",
138
+ "--is-inside-work-tree",
139
+ ],
140
+ capture_output=True,
141
+ text=True,
142
+ encoding="utf-8",
143
+ errors="replace",
144
+ timeout=5,
145
+ shell=False,
146
+ )
147
+ return r.returncode == 0 and r.stdout.strip() == "true"
148
+ except (subprocess.SubprocessError, FileNotFoundError, OSError):
149
+ return False
150
+
151
+
152
+ def git_head_sha(project_dir: Path) -> str | None:
153
+ """Return current HEAD SHA or None on non-git / error.
154
+
155
+ Returns:
156
+ 40-character hex SHA string, or None if git is unavailable, the
157
+ directory is not a repo, or any other error occurs (fail-open).
158
+ """
159
+ try:
160
+ r = subprocess.run(
161
+ [
162
+ "git",
163
+ "-C", str(project_dir),
164
+ "rev-parse",
165
+ "HEAD",
166
+ ],
167
+ capture_output=True,
168
+ text=True,
169
+ encoding="utf-8",
170
+ errors="replace",
171
+ timeout=5,
172
+ shell=False,
173
+ )
174
+ if r.returncode != 0:
175
+ return None
176
+ return r.stdout.strip() or None
177
+ except (subprocess.SubprocessError, FileNotFoundError, OSError):
178
+ return None