codebeacon 0.6.2__tar.gz → 0.6.3__tar.gz

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 (153) hide show
  1. {codebeacon-0.6.2 → codebeacon-0.6.3}/.cursorrules +9 -9
  2. {codebeacon-0.6.2 → codebeacon-0.6.3}/AGENTS.md +9 -9
  3. {codebeacon-0.6.2 → codebeacon-0.6.3}/CLAUDE.md +9 -9
  4. {codebeacon-0.6.2 → codebeacon-0.6.3}/PKG-INFO +1 -1
  5. codebeacon-0.6.3/codebeacon/__init__.py +1 -0
  6. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/cache.py +35 -27
  7. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/common/safety.py +14 -0
  8. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/common/symbols.py +7 -1
  9. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/config.py +10 -4
  10. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/contextmap/generator.py +20 -8
  11. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/discover/detector.py +13 -4
  12. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/export/hooks.py +60 -10
  13. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/export/obsidian.py +5 -1
  14. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/extract/dependencies.py +75 -0
  15. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/extract/queries/django.scm +12 -0
  16. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/extract/queries/fastapi.scm +12 -0
  17. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/extract/queries/flask.scm +12 -0
  18. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/extract/routes.py +13 -6
  19. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/extract/services.py +6 -1
  20. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/graph/analyze.py +13 -5
  21. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/graph/build.py +5 -0
  22. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/graph/enrich.py +40 -29
  23. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/wiki/generator.py +4 -3
  24. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/wiki/templates.py +22 -25
  25. {codebeacon-0.6.2 → codebeacon-0.6.3}/pyproject.toml +1 -1
  26. codebeacon-0.6.3/tests/test_graphify_parity_0_6_3.py +235 -0
  27. codebeacon-0.6.3/tests/test_independent_audit_fixes.py +291 -0
  28. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/test_known_bugs.py +2 -2
  29. codebeacon-0.6.2/codebeacon/__init__.py +0 -1
  30. {codebeacon-0.6.2 → codebeacon-0.6.3}/.github/CODEOWNERS +0 -0
  31. {codebeacon-0.6.2 → codebeacon-0.6.3}/.github/dependabot.yml +0 -0
  32. {codebeacon-0.6.2 → codebeacon-0.6.3}/.github/workflows/ci.yml +0 -0
  33. {codebeacon-0.6.2 → codebeacon-0.6.3}/.github/workflows/release.yml +0 -0
  34. {codebeacon-0.6.2 → codebeacon-0.6.3}/.gitignore +0 -0
  35. {codebeacon-0.6.2 → codebeacon-0.6.3}/LICENSE +0 -0
  36. {codebeacon-0.6.2 → codebeacon-0.6.3}/README.de.md +0 -0
  37. {codebeacon-0.6.2 → codebeacon-0.6.3}/README.es.md +0 -0
  38. {codebeacon-0.6.2 → codebeacon-0.6.3}/README.fr.md +0 -0
  39. {codebeacon-0.6.2 → codebeacon-0.6.3}/README.ja.md +0 -0
  40. {codebeacon-0.6.2 → codebeacon-0.6.3}/README.ko.md +0 -0
  41. {codebeacon-0.6.2 → codebeacon-0.6.3}/README.md +0 -0
  42. {codebeacon-0.6.2 → codebeacon-0.6.3}/README.pt-BR.md +0 -0
  43. {codebeacon-0.6.2 → codebeacon-0.6.3}/README.zh-CN.md +0 -0
  44. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/__main__.py +0 -0
  45. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/affected.py +0 -0
  46. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/cli.py +0 -0
  47. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/common/__init__.py +0 -0
  48. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/common/filters.py +0 -0
  49. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/common/types.py +0 -0
  50. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/contextmap/__init__.py +0 -0
  51. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/diagnostics.py +0 -0
  52. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/discover/__init__.py +0 -0
  53. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/discover/ignore.py +0 -0
  54. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/discover/scanner.py +0 -0
  55. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/export/__init__.py +0 -0
  56. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/export/callflow_html.py +0 -0
  57. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/export/mcp.py +0 -0
  58. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/export/merge.py +0 -0
  59. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/export/tree_html.py +0 -0
  60. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/extract/__init__.py +0 -0
  61. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/extract/base.py +0 -0
  62. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/extract/components.py +0 -0
  63. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/extract/dotnet.py +0 -0
  64. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/extract/entities.py +0 -0
  65. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/extract/queries/README.md +0 -0
  66. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/extract/queries/actix.scm +0 -0
  67. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/extract/queries/angular.scm +0 -0
  68. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/extract/queries/aspnet.scm +0 -0
  69. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/extract/queries/express.scm +0 -0
  70. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/extract/queries/gin.scm +0 -0
  71. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/extract/queries/ktor.scm +0 -0
  72. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/extract/queries/laravel.scm +0 -0
  73. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/extract/queries/nestjs.scm +0 -0
  74. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/extract/queries/rails.scm +0 -0
  75. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/extract/queries/react.scm +0 -0
  76. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/extract/queries/spring_boot.scm +0 -0
  77. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/extract/queries/svelte.scm +0 -0
  78. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/extract/queries/tauri.scm +0 -0
  79. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/extract/queries/vapor.scm +0 -0
  80. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/extract/queries/vue.scm +0 -0
  81. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/extract/semantic.py +0 -0
  82. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/graph/__init__.py +0 -0
  83. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/graph/cluster.py +0 -0
  84. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/graph/write.py +0 -0
  85. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/knowledge/__init__.py +0 -0
  86. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/knowledge/generator.py +0 -0
  87. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/pipeline.py +0 -0
  88. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/plugins/__init__.py +0 -0
  89. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/plugins/githooks.py +0 -0
  90. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/plugins/skills.py +0 -0
  91. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/semantic_pipeline.py +0 -0
  92. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/skill/SKILL.md +0 -0
  93. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/wave.py +0 -0
  94. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/wiki/__init__.py +0 -0
  95. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon/wiki/index.py +0 -0
  96. {codebeacon-0.6.2 → codebeacon-0.6.3}/codebeacon.yaml.example +0 -0
  97. {codebeacon-0.6.2 → codebeacon-0.6.3}/docs/TRANSLATION_STATUS.md +0 -0
  98. {codebeacon-0.6.2 → codebeacon-0.6.3}/public-plan.md +0 -0
  99. {codebeacon-0.6.2 → codebeacon-0.6.3}/skill/install.py +0 -0
  100. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/__init__.py +0 -0
  101. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/conftest.py +0 -0
  102. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/fixtures/actix/main.rs +0 -0
  103. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/fixtures/angular/app.component.ts +0 -0
  104. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/fixtures/aspnet/UserController.cs +0 -0
  105. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/fixtures/django/views.py +0 -0
  106. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/fixtures/express/userRouter.js +0 -0
  107. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/fixtures/fastapi/main.py +0 -0
  108. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/fixtures/flask/app.py +0 -0
  109. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/fixtures/gin/main.go +0 -0
  110. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/fixtures/integration_workspace/api-python/pyproject.toml +0 -0
  111. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/fixtures/integration_workspace/api-python/src/__init__.py +0 -0
  112. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/fixtures/integration_workspace/api-python/src/main.py +0 -0
  113. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/fixtures/integration_workspace/api-python/src/services.py +0 -0
  114. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/fixtures/integration_workspace/web/package.json +0 -0
  115. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/fixtures/integration_workspace/web/src/UserPage.tsx +0 -0
  116. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/fixtures/ktor/UserRoutes.kt +0 -0
  117. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/fixtures/laravel/UserController.php +0 -0
  118. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/fixtures/nestjs/user.controller.ts +0 -0
  119. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/fixtures/rails/users_controller.rb +0 -0
  120. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/fixtures/react/UserPage.tsx +0 -0
  121. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/fixtures/spring_boot/UserController.java +0 -0
  122. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/fixtures/sveltekit/+page.svelte +0 -0
  123. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/fixtures/vapor/routes.swift +0 -0
  124. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/fixtures/vue/UserList.vue +0 -0
  125. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/integration/__init__.py +0 -0
  126. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/integration/test_full_pipeline.py +0 -0
  127. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/test_affected.py +0 -0
  128. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/test_affected_wiki.py +0 -0
  129. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/test_audit_bugfixes.py +0 -0
  130. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/test_cli_dispatch.py +0 -0
  131. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/test_contextmap_paths.py +0 -0
  132. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/test_dependencies.py +0 -0
  133. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/test_diagnostics.py +0 -0
  134. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/test_discover.py +0 -0
  135. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/test_dotnet.py +0 -0
  136. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/test_entities.py +0 -0
  137. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/test_filters.py +0 -0
  138. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/test_graph.py +0 -0
  139. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/test_graphify_parity_fixes.py +0 -0
  140. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/test_knowledge.py +0 -0
  141. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/test_mcp_and_semantic.py +0 -0
  142. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/test_optional_grammars.py +0 -0
  143. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/test_pipeline_module.py +0 -0
  144. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/test_plugins.py +0 -0
  145. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/test_resolve.py +0 -0
  146. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/test_routes.py +0 -0
  147. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/test_safety_and_writes.py +0 -0
  148. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/test_scanner_sensitive.py +0 -0
  149. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/test_semantic.py +0 -0
  150. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/test_semantic_hardening.py +0 -0
  151. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/test_semantic_stats.py +0 -0
  152. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/test_services.py +0 -0
  153. {codebeacon-0.6.2 → codebeacon-0.6.3}/tests/test_wiki.py +0 -0
@@ -27,7 +27,7 @@ Look up by class name — replace `{project}` with the relevant folder:
27
27
 
28
28
  | Project | Notes | Example |
29
29
  | --- | --- | --- |
30
- | codebeacon | 398 services, 3 entities | `affected_from_paths.py.md` |
30
+ | codebeacon | 409 services, 3 entities | `affected_from_paths.py.md` |
31
31
 
32
32
  ### Step 3 — source file
33
33
  Open the paths surfaced by Steps 1–2.
@@ -38,7 +38,7 @@ Open the paths surfaced by Steps 1–2.
38
38
 
39
39
  | Project | Framework | Routes | Services | Entities | Components |
40
40
  | --- | --- | --- | --- | --- | --- |
41
- | codebeacon | python | 5 | 398 | 3 | 0 |
41
+ | codebeacon | python | 5 | 409 | 3 | 0 |
42
42
 
43
43
  ---
44
44
 
@@ -56,19 +56,19 @@ Open the paths surfaced by Steps 1–2.
56
56
  ## Architecture
57
57
 
58
58
  **codebeacon**: **python** · python
59
- Routes: 5 | Services: 398 | Entities: 3 | Components: 0
59
+ Routes: 5 | Services: 409 | Entities: 3 | Components: 0
60
60
 
61
61
  ## High-Impact Files
62
62
 
63
63
  Changes here affect many other files:
64
64
 
65
- - `codebeacon/semantic_pipeline.py` (imported by 56 files)
66
- - `codebeacon/pipeline.py` (imported by 12 files)
67
- - `tests/test_graph.py` (imported by 6 files)
68
- - `tests/integration/test_full_pipeline.py` (imported by 3 files)
69
- - `tests/test_known_bugs.py` (imported by 2 files)
65
+ - `codebeacon/common/safety.py` (imported by 9 files)
66
+ - `codebeacon/discover/detector.py` (imported by 6 files)
67
+ - `codebeacon/wiki/generator.py` (imported by 6 files)
68
+ - `codebeacon/affected.py` (imported by 5 files)
69
+ - `codebeacon/common/symbols.py` (imported by 5 files)
70
70
 
71
71
  ---
72
72
 
73
- _Generated by [codebeacon](https://github.com/codebeacon/codebeacon) · 2026-05-31_
73
+ _Generated by [codebeacon](https://github.com/codebeacon/codebeacon) · 2026-06-10_
74
74
  <!-- codebeacon:end -->
@@ -27,7 +27,7 @@ Look up by class name — replace `{project}` with the relevant folder:
27
27
 
28
28
  | Project | Notes | Example |
29
29
  | --- | --- | --- |
30
- | codebeacon | 398 services, 3 entities | `affected_from_paths.py.md` |
30
+ | codebeacon | 409 services, 3 entities | `affected_from_paths.py.md` |
31
31
 
32
32
  ### Step 3 — source file
33
33
  Open the paths surfaced by Steps 1–2.
@@ -38,7 +38,7 @@ Open the paths surfaced by Steps 1–2.
38
38
 
39
39
  | Project | Framework | Routes | Services | Entities | Components |
40
40
  | --- | --- | --- | --- | --- | --- |
41
- | codebeacon | python | 5 | 398 | 3 | 0 |
41
+ | codebeacon | python | 5 | 409 | 3 | 0 |
42
42
 
43
43
  ---
44
44
 
@@ -56,19 +56,19 @@ Open the paths surfaced by Steps 1–2.
56
56
  ## Architecture
57
57
 
58
58
  **codebeacon**: **python** · python
59
- Routes: 5 | Services: 398 | Entities: 3 | Components: 0
59
+ Routes: 5 | Services: 409 | Entities: 3 | Components: 0
60
60
 
61
61
  ## High-Impact Files
62
62
 
63
63
  Changes here affect many other files:
64
64
 
65
- - `codebeacon/semantic_pipeline.py` (imported by 56 files)
66
- - `codebeacon/pipeline.py` (imported by 12 files)
67
- - `tests/test_graph.py` (imported by 6 files)
68
- - `tests/integration/test_full_pipeline.py` (imported by 3 files)
69
- - `tests/test_known_bugs.py` (imported by 2 files)
65
+ - `codebeacon/common/safety.py` (imported by 9 files)
66
+ - `codebeacon/discover/detector.py` (imported by 6 files)
67
+ - `codebeacon/wiki/generator.py` (imported by 6 files)
68
+ - `codebeacon/affected.py` (imported by 5 files)
69
+ - `codebeacon/common/symbols.py` (imported by 5 files)
70
70
 
71
71
  ---
72
72
 
73
- _Generated by [codebeacon](https://github.com/codebeacon/codebeacon) · 2026-05-31_
73
+ _Generated by [codebeacon](https://github.com/codebeacon/codebeacon) · 2026-06-10_
74
74
  <!-- codebeacon:end -->
@@ -27,7 +27,7 @@ Look up by class name — replace `{project}` with the relevant folder:
27
27
 
28
28
  | Project | Notes | Example |
29
29
  | --- | --- | --- |
30
- | codebeacon | 398 services, 3 entities | `affected_from_paths.py.md` |
30
+ | codebeacon | 409 services, 3 entities | `affected_from_paths.py.md` |
31
31
 
32
32
  ### Step 3 — source file
33
33
  Open the paths surfaced by Steps 1–2.
@@ -43,7 +43,7 @@ searches that span projects.
43
43
 
44
44
  | Project | Framework | Routes | Services | Entities | Components |
45
45
  | --- | --- | --- | --- | --- | --- |
46
- | codebeacon | python | 5 | 398 | 3 | 0 |
46
+ | codebeacon | python | 5 | 409 | 3 | 0 |
47
47
 
48
48
  ---
49
49
 
@@ -61,19 +61,19 @@ searches that span projects.
61
61
  ## Architecture
62
62
 
63
63
  **codebeacon**: **python** · python
64
- Routes: 5 | Services: 398 | Entities: 3 | Components: 0
64
+ Routes: 5 | Services: 409 | Entities: 3 | Components: 0
65
65
 
66
66
  ## High-Impact Files
67
67
 
68
68
  Changes here affect many other files:
69
69
 
70
- - `codebeacon/semantic_pipeline.py` (imported by 56 files)
71
- - `codebeacon/pipeline.py` (imported by 12 files)
72
- - `tests/test_graph.py` (imported by 6 files)
73
- - `tests/integration/test_full_pipeline.py` (imported by 3 files)
74
- - `tests/test_known_bugs.py` (imported by 2 files)
70
+ - `codebeacon/common/safety.py` (imported by 9 files)
71
+ - `codebeacon/discover/detector.py` (imported by 6 files)
72
+ - `codebeacon/wiki/generator.py` (imported by 6 files)
73
+ - `codebeacon/affected.py` (imported by 5 files)
74
+ - `codebeacon/common/symbols.py` (imported by 5 files)
75
75
 
76
76
  ---
77
77
 
78
- _Generated by [codebeacon](https://github.com/codebeacon/codebeacon) · 2026-05-31_
78
+ _Generated by [codebeacon](https://github.com/codebeacon/codebeacon) · 2026-06-10_
79
79
  <!-- codebeacon:end -->
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codebeacon
3
- Version: 0.6.2
3
+ Version: 0.6.3
4
4
  Summary: Source code AST analysis tool for AI context generation — unified multi-framework knowledge graph
5
5
  Project-URL: Homepage, https://github.com/codebeacon/codebeacon
6
6
  Project-URL: Repository, https://github.com/codebeacon/codebeacon
@@ -0,0 +1 @@
1
+ __version__ = "0.6.3"
@@ -33,6 +33,7 @@ from __future__ import annotations
33
33
  import hashlib
34
34
  import json
35
35
  import os
36
+ import threading
36
37
  import time
37
38
  from pathlib import Path
38
39
  from typing import Any, Optional
@@ -63,6 +64,9 @@ class Cache:
63
64
  self._hash_memo: dict[str, str] = {}
64
65
  # Memoize stat() results within a single run as well
65
66
  self._stat_memo: dict[str, tuple[int, int]] = {}
67
+ # One Cache instance is shared by the wave ThreadPoolExecutor workers;
68
+ # the get()/put() check-then-act sequences need a lock to stay atomic.
69
+ self._lock = threading.RLock()
66
70
 
67
71
  def _key(self, file_path: str) -> str:
68
72
  """Map a (possibly absolute) ``file_path`` to its on-disk cache key.
@@ -117,8 +121,9 @@ class Cache:
117
121
 
118
122
  def file_hash(self, file_path: str) -> str:
119
123
  """Compute (and memoize) the SHA-256 hex digest of a file's contents."""
120
- if file_path in self._hash_memo:
121
- return self._hash_memo[file_path]
124
+ with self._lock:
125
+ if file_path in self._hash_memo:
126
+ return self._hash_memo[file_path]
122
127
 
123
128
  h = hashlib.sha256()
124
129
  try:
@@ -129,7 +134,8 @@ class Cache:
129
134
  except OSError:
130
135
  digest = ""
131
136
 
132
- self._hash_memo[file_path] = digest
137
+ with self._lock:
138
+ self._hash_memo[file_path] = digest
133
139
  return digest
134
140
 
135
141
  def _file_stat(self, file_path: str) -> tuple[int, int]:
@@ -167,21 +173,22 @@ class Cache:
167
173
 
168
174
  def get(self, file_path: str) -> Optional[dict]:
169
175
  """Return the cached extraction result dict, or None if stale/missing."""
170
- key = self._key(file_path)
171
- entry = self._data.get(key)
172
- if not entry:
173
- return None
174
- mtime_ns, size = self._file_stat(file_path)
175
- if entry.get("mtime_ns") == mtime_ns and entry.get("size") == size:
176
+ with self._lock:
177
+ key = self._key(file_path)
178
+ entry = self._data.get(key)
179
+ if not entry:
180
+ return None
181
+ mtime_ns, size = self._file_stat(file_path)
182
+ if entry.get("mtime_ns") == mtime_ns and entry.get("size") == size:
183
+ return entry.get("result")
184
+ if entry.get("hash") != self.file_hash(file_path):
185
+ return None
186
+ # Content unchanged despite mtime bump — refresh the stat fields so
187
+ # the next run skips hashing.
188
+ entry["mtime_ns"] = mtime_ns
189
+ entry["size"] = size
190
+ self._dirty = True
176
191
  return entry.get("result")
177
- if entry.get("hash") != self.file_hash(file_path):
178
- return None
179
- # Content unchanged despite mtime bump — refresh the stat fields so the
180
- # next run skips hashing.
181
- entry["mtime_ns"] = mtime_ns
182
- entry["size"] = size
183
- self._dirty = True
184
- return entry.get("result")
185
192
 
186
193
  def put(self, file_path: str, result: Any, file_hash: Optional[str] = None) -> None:
187
194
  """Store an extraction result for a file.
@@ -199,16 +206,17 @@ class Cache:
199
206
  except (TypeError, ImportError):
200
207
  result = {"_raw": str(result)}
201
208
 
202
- mtime_ns, size = self._file_stat(file_path)
203
- self._data[self._key(file_path)] = {
204
- "hash": h,
205
- "result": result,
206
- "ts": time.time(),
207
- "mtime_ns": mtime_ns,
208
- "size": size,
209
- }
210
- self._hash_memo[file_path] = h
211
- self._dirty = True
209
+ with self._lock:
210
+ mtime_ns, size = self._file_stat(file_path)
211
+ self._data[self._key(file_path)] = {
212
+ "hash": h,
213
+ "result": result,
214
+ "ts": time.time(),
215
+ "mtime_ns": mtime_ns,
216
+ "size": size,
217
+ }
218
+ self._hash_memo[file_path] = h
219
+ self._dirty = True
212
220
 
213
221
  def invalidate(self, file_path: str) -> None:
214
222
  """Remove a specific file's cache entry."""
@@ -102,6 +102,20 @@ def cap_filename(name: str, limit: int = 200) -> str:
102
102
  return f"{truncated}_{digest}"
103
103
 
104
104
 
105
+ def safe_wiki_filename(label: str) -> str:
106
+ """Filename stem for a wiki article: filesystem-safe and byte-capped.
107
+
108
+ Lives here (not in wiki/generator.py) because BOTH sides of every wiki
109
+ link must agree on it: the generator names the file with it, and the
110
+ templates build `./<stem>.md` links with it. When the two used different
111
+ transforms, any label with a character outside [-_.\\w] (spaces, `#`,
112
+ parentheses, `<>` from generics) produced a file at one path and links
113
+ pointing at another — every such link was dead on arrival.
114
+ """
115
+ cleaned = "".join(c if c.isalnum() or c in "-_." else "_" for c in label)
116
+ return cap_filename(cleaned)
117
+
118
+
105
119
  def git_head(repo_path: str | Path) -> str:
106
120
  """Return the full HEAD commit SHA for ``repo_path``, or "" if not a repo.
107
121
 
@@ -27,6 +27,8 @@ class SymbolTable:
27
27
  self._implements_map: dict[str, list[str]] = {}
28
28
  # All known node IDs
29
29
  self._node_ids: set[str] = set()
30
+ # node_id → source_file, so resolved edges carry a real file path
31
+ self._node_files: dict[str, str] = {}
30
32
 
31
33
  def build(self, nodes: list[Node]) -> None:
32
34
  """Build symbol maps from a flat list of all extracted nodes.
@@ -35,6 +37,7 @@ class SymbolTable:
35
37
  """
36
38
  for node in nodes:
37
39
  self._node_ids.add(node.id)
40
+ self._node_files[node.id] = node.source_file or ""
38
41
 
39
42
  label = node.label
40
43
  if label not in self._class_map:
@@ -91,7 +94,10 @@ class SymbolTable:
91
94
  relation="injects",
92
95
  confidence="INFERRED" if is_interface_resolved else "EXTRACTED",
93
96
  confidence_score=0.8 if is_interface_resolved else 1.0,
94
- source_file=ref.source_node_id,
97
+ # The source node's actual file — Edge.source_file is a file path
98
+ # everywhere else; stamping the node ID here leaked "proj::Name"
99
+ # strings into beacon.json's source_file fields.
100
+ source_file=self._node_files.get(ref.source_node_id, ""),
95
101
  )
96
102
 
97
103
  def resolve_all(
@@ -70,6 +70,10 @@ def load_config(path: str | Path) -> CodebeaconConfig:
70
70
 
71
71
  projects = []
72
72
  for p in projects_raw:
73
+ # A bare `-` in the YAML list arrives as None; a scalar entry as str.
74
+ # Both must surface as a config error, not a TypeError traceback.
75
+ if not isinstance(p, dict):
76
+ raise ValueError(f"Invalid project entry (expected mapping): {p!r}")
73
77
  if "name" not in p or "path" not in p:
74
78
  raise ValueError(f"Project entry missing 'name' or 'path': {p}")
75
79
  # Resolve path relative to config file location
@@ -82,8 +86,10 @@ def load_config(path: str | Path) -> CodebeaconConfig:
82
86
  type=p.get("type", "auto"),
83
87
  ))
84
88
 
85
- output_raw = raw.get("output", {})
86
- context_map = output_raw.get("context_map", {})
89
+ # `.get(key, {})` is not enough: a section present-but-empty in the YAML
90
+ # (`output:` on its own line) loads as None, and None.get(...) crashes.
91
+ output_raw = raw.get("output") or {}
92
+ context_map = output_raw.get("context_map") or {}
87
93
  output = OutputConfig(
88
94
  dir=output_raw.get("dir", ".codebeacon"),
89
95
  wiki=output_raw.get("wiki", True),
@@ -91,14 +97,14 @@ def load_config(path: str | Path) -> CodebeaconConfig:
91
97
  context_map_targets=context_map.get("targets", ["CLAUDE.md", ".cursorrules", "AGENTS.md"]),
92
98
  )
93
99
 
94
- wave_raw = raw.get("wave", {})
100
+ wave_raw = raw.get("wave") or {}
95
101
  wave = WaveConfig(
96
102
  auto=wave_raw.get("auto", True),
97
103
  chunk_size=wave_raw.get("chunk_size", 300),
98
104
  max_parallel=wave_raw.get("max_parallel", 5),
99
105
  )
100
106
 
101
- semantic_raw = raw.get("semantic", {})
107
+ semantic_raw = raw.get("semantic") or {}
102
108
  semantic = SemanticConfig(
103
109
  enabled=semantic_raw.get("enabled", False),
104
110
  )
@@ -247,14 +247,26 @@ def _collect_stats(G: nx.DiGraph) -> dict[str, dict[str, int]]:
247
247
 
248
248
 
249
249
  def _hub_files(G: nx.DiGraph, top_n: int = 5) -> list[tuple[str, int]]:
250
- """Return (file_path, import_count) for the most-imported source files."""
251
- counter: dict[str, int] = defaultdict(int)
252
- for _, _, data in G.edges(data=True):
253
- if data.get("relation") in ("imports", "imports_from"):
254
- sf = data.get("source_file", "")
255
- if sf:
256
- counter[sf] += 1
257
- ranked = sorted(counter.items(), key=lambda x: x[1], reverse=True)
250
+ """Return (file_path, importer_count) for the most-imported source files.
251
+
252
+ Counts DISTINCT importing files per edge-target file, mirroring
253
+ analyze.hub_files. The old version counted `edge["source_file"]` — the
254
+ importer's own file — so CLAUDE.md's "High-Impact Files" actually listed
255
+ the files that *import the most* (entry points), with counts inflated by
256
+ per-node edge remapping (e.g. "imported by 392 files" in a 60-file repo).
257
+ """
258
+ importers: dict[str, set[str]] = defaultdict(set)
259
+ for src, tgt, data in G.edges(data=True):
260
+ if data.get("relation") not in ("imports", "imports_from"):
261
+ continue
262
+ tgt_file = G.nodes[tgt].get("source_file", "")
263
+ src_file = G.nodes[src].get("source_file", "")
264
+ if tgt_file and src_file and tgt_file != src_file:
265
+ importers[tgt_file].add(src_file)
266
+ ranked = sorted(
267
+ ((fp, len(srcs)) for fp, srcs in importers.items()),
268
+ key=lambda x: (-x[1], x[0]),
269
+ )
258
270
  return ranked[:top_n]
259
271
 
260
272
 
@@ -218,10 +218,19 @@ def _detect_language_from_files(directory: Path) -> str:
218
218
  ".swift": "swift",
219
219
  }
220
220
  try:
221
- for entry in directory.rglob("*"):
222
- if entry.is_file() and entry.suffix in ext_to_lang:
223
- lang = ext_to_lang[entry.suffix]
224
- counts[lang] = counts.get(lang, 0) + 1
221
+ # os.walk with pruning, not rglob: an unbounded rglob descends into
222
+ # node_modules / .git / dist, which on a Node repo means enumerating
223
+ # tens of thousands of vendored files (and skews the language vote
224
+ # toward vendored JS) just to answer "what language is this project".
225
+ for dirpath, dirnames, filenames in os.walk(directory):
226
+ dirnames[:] = [
227
+ d for d in dirnames if d not in _SKIP_DIRS and not d.startswith(".")
228
+ ]
229
+ for fname in filenames:
230
+ ext = os.path.splitext(fname)[1]
231
+ if ext in ext_to_lang:
232
+ lang = ext_to_lang[ext]
233
+ counts[lang] = counts.get(lang, 0) + 1
225
234
  except (PermissionError, OSError):
226
235
  pass
227
236
 
@@ -25,7 +25,7 @@ import sys
25
25
  from pathlib import Path
26
26
 
27
27
 
28
- _POST_COMMIT_HOOK = r"""#!/usr/bin/env bash
28
+ _POST_COMMIT_HOOK_TEMPLATE = r"""#!/usr/bin/env bash
29
29
  # codebeacon: incremental rebuild after each commit.
30
30
  # Detaches so `git commit` returns immediately; output goes to the log below.
31
31
  #
@@ -54,14 +54,51 @@ if [ -z "$CHANGED" ] || ! echo "$CHANGED" | grep -v '^\.codebeacon/' | grep -E "
54
54
  exit 0
55
55
  fi
56
56
 
57
- (
58
- nohup codebeacon scan . --update >>"$LOG" 2>&1 &
59
- ) >/dev/null 2>&1 &
60
- disown 2>/dev/null || true
57
+ # Interpreter pinned at install time so the hook still works where the
58
+ # `codebeacon` launcher is not on PATH — GUI git clients and CI runners
59
+ # don't inherit a login shell's PATH, which made the old `nohup codebeacon`
60
+ # form silently no-op. The heredoc detaches via subprocess so no `nohup`
61
+ # is needed (it doesn't exist on Windows). Mirrors graphify #1127/#1161.
62
+ CODEBEACON_PYTHON=__CODEBEACON_PYTHON__
63
+
64
+ if [ -x "$CODEBEACON_PYTHON" ]; then
65
+ "$CODEBEACON_PYTHON" - "$LOG" <<'CODEBEACON_PY' >>"$LOG" 2>&1
66
+ import subprocess, sys
67
+ kwargs = {"stdin": subprocess.DEVNULL}
68
+ if sys.platform == "win32":
69
+ kwargs["creationflags"] = 0x00000208 # DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP
70
+ else:
71
+ kwargs["start_new_session"] = True
72
+ log = open(sys.argv[1], "ab")
73
+ subprocess.Popen(
74
+ [sys.executable, "-m", "codebeacon", "scan", ".", "--update"],
75
+ stdout=log, stderr=log, **kwargs,
76
+ )
77
+ CODEBEACON_PY
78
+ else
79
+ # Pinned interpreter vanished (reinstall, deleted venv) — fall back to PATH.
80
+ (
81
+ nohup codebeacon scan . --update >>"$LOG" 2>&1 &
82
+ ) >/dev/null 2>&1 &
83
+ disown 2>/dev/null || true
84
+ fi
61
85
  exit 0
62
86
  """
63
87
 
64
88
 
89
+ def _render_post_commit_hook() -> str:
90
+ """Render the hook with the current interpreter pinned.
91
+
92
+ ``as_posix()`` keeps the path usable from git's bundled sh on Windows
93
+ (``C:/Python311/python.exe``); single quotes stop spaces or shell
94
+ metacharacters in the path from being interpreted.
95
+ """
96
+ python_path = "'" + Path(sys.executable).as_posix().replace("'", "'\\''") + "'"
97
+ # Plain token replacement — str.format would choke on the template's own
98
+ # shell braces (`${HOME}`, the heredoc's dict literal).
99
+ return _POST_COMMIT_HOOK_TEMPLATE.replace("__CODEBEACON_PYTHON__", python_path)
100
+
101
+
65
102
  def install_hooks(repo_path: str | Path) -> int:
66
103
  """Install the merge driver, ``.gitattributes`` entry, and post-commit hook.
67
104
 
@@ -105,8 +142,13 @@ def _is_git_repo(repo: Path) -> bool:
105
142
 
106
143
  def _register_merge_driver(repo: Path) -> None:
107
144
  """Register the ``codebeacon`` merge driver in ``.git/config``."""
108
- codebeacon = shutil.which("codebeacon") or "codebeacon"
109
- cmd = f'{codebeacon} merge-driver "%O" "%A" "%B"'
145
+ codebeacon = shutil.which("codebeacon")
146
+ if codebeacon:
147
+ cmd = f'"{Path(codebeacon).as_posix()}" merge-driver "%O" "%A" "%B"'
148
+ else:
149
+ # Launcher not on PATH (uv tool / pipx install, GUI client) — invoke
150
+ # the module through the pinned interpreter instead.
151
+ cmd = f'"{Path(sys.executable).as_posix()}" -m codebeacon merge-driver "%O" "%A" "%B"'
110
152
  subprocess.run(
111
153
  ["git", "config", "--local", "merge.codebeacon.name", "codebeacon union merge"],
112
154
  cwd=str(repo),
@@ -134,9 +176,17 @@ def _install_post_commit(repo: Path) -> None:
134
176
  hooks_dir = _hooks_dir(repo)
135
177
  hooks_dir.mkdir(parents=True, exist_ok=True)
136
178
  hook = hooks_dir / "post-commit"
137
- if hook.exists() and "codebeacon" in hook.read_text(encoding="utf-8"):
138
- return
139
- hook.write_text(_POST_COMMIT_HOOK, encoding="utf-8")
179
+ content = _render_post_commit_hook()
180
+ if hook.exists():
181
+ existing = hook.read_text(encoding="utf-8")
182
+ if existing == content:
183
+ return
184
+ # A codebeacon-authored hook (our marker header) gets refreshed so
185
+ # re-running `hook install` picks up template fixes; anything else
186
+ # that merely mentions codebeacon is the user's — leave it alone.
187
+ if "codebeacon: incremental rebuild" not in existing and "codebeacon" in existing:
188
+ return
189
+ hook.write_text(content, encoding="utf-8")
140
190
  hook.chmod(hook.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
141
191
 
142
192
 
@@ -641,7 +641,11 @@ def _step8_move_remaining(vault: Path) -> None:
641
641
  content = md.read_text(errors="ignore")
642
642
  m = _PROJECT_RE.search(content)
643
643
  if m and m.group(1):
644
- svc = m.group(1)
644
+ # Cap here so the folder, the Step-9 hub note (`<svc>/<svc>.md`),
645
+ # and every backlink derived from the folder name stay consistent
646
+ # and under the 255-byte filesystem limit — note filenames are
647
+ # capped in _safe_note_name but this path wasn't.
648
+ svc = cap_filename(m.group(1))
645
649
  dest_dir = vault / svc
646
650
  dest_dir.mkdir(exist_ok=True)
647
651
  dest = dest_dir / md.name
@@ -60,6 +60,52 @@ _JS_REQUIRE_RE = re.compile(
60
60
  _JS_FAMILY_EXTS = frozenset({".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs"})
61
61
 
62
62
 
63
+ def _strip_js_comments(source: str) -> str:
64
+ """Blank out ``//`` and ``/* */`` comments, preserving string literals.
65
+
66
+ The regex passes below run on raw text, so a commented-out
67
+ ``export * from './foo'`` or ``require('./x')`` — or a module path
68
+ mentioned in prose like ``// re-exported from './index'`` — would
69
+ otherwise produce a phantom edge and false import cycles. Mirrors
70
+ graphify #1193. String/template literals are scanned so a ``//``
71
+ inside ``'http://…'`` is not treated as a comment.
72
+ """
73
+ out: list[str] = []
74
+ i, n = 0, len(source)
75
+ while i < n:
76
+ c = source[i]
77
+ if c in "'\"`":
78
+ quote = c
79
+ out.append(c)
80
+ i += 1
81
+ while i < n:
82
+ ch = source[i]
83
+ out.append(ch)
84
+ if ch == "\\" and i + 1 < n:
85
+ out.append(source[i + 1])
86
+ i += 2
87
+ continue
88
+ i += 1
89
+ if ch == quote:
90
+ break
91
+ if ch == "\n" and quote != "`":
92
+ break # unterminated ' / " string ends at the line
93
+ elif c == "/" and i + 1 < n and source[i + 1] == "/":
94
+ while i < n and source[i] != "\n":
95
+ i += 1
96
+ elif c == "/" and i + 1 < n and source[i + 1] == "*":
97
+ i += 2
98
+ while i + 1 < n and not (source[i] == "*" and source[i + 1] == "/"):
99
+ if source[i] == "\n":
100
+ out.append("\n")
101
+ i += 1
102
+ i = min(i + 2, n)
103
+ else:
104
+ out.append(c)
105
+ i += 1
106
+ return "".join(out)
107
+
108
+
63
109
  # ── Framework → query file stem ───────────────────────────────────────────────
64
110
 
65
111
  _FW_TO_QUERY: dict[str, str] = {
@@ -175,6 +221,34 @@ def extract_dependencies(file_path: str, framework: str) -> list[Edge]:
175
221
  source_file=file_path,
176
222
  ))
177
223
 
224
+ # Python `from pkg import name` — the module path alone loses the
225
+ # actual import target (`from auth.services import UserService` only
226
+ # yielded "auth.services", so the edge to UserService was never even
227
+ # attempted, and `from pkg import submodule` left the importer
228
+ # disconnected from pkg/submodule.py). The Python queries capture the
229
+ # imported names as @import.item; emit a dotted `module.name` target
230
+ # for each so label resolution can bind the real symbol. Mirrors
231
+ # graphify #1146.
232
+ if "import.item" in caps and "import.path" in caps:
233
+ module_raw = node_text(caps["import.path"][0]).strip("'\"` ")
234
+ for item_node in caps["import.item"]:
235
+ item = node_text(item_node).strip()
236
+ if not item:
237
+ continue
238
+ dotted = f"{module_raw}.{item}" if module_raw else item
239
+ key = ("imports_from", dotted)
240
+ if key in seen:
241
+ continue
242
+ seen.add(key)
243
+ edges.append(Edge(
244
+ source=file_path,
245
+ target=dotted,
246
+ relation="imports_from",
247
+ confidence="EXTRACTED",
248
+ confidence_score=1.0,
249
+ source_file=file_path,
250
+ ))
251
+
178
252
  # Vapor uses @import.name instead of @import.path
179
253
  if "import.name" in caps:
180
254
  for import_node in caps["import.name"]:
@@ -201,6 +275,7 @@ def extract_dependencies(file_path: str, framework: str) -> list[Edge]:
201
275
  source = Path(file_path).read_text(encoding="utf-8", errors="replace")
202
276
  except OSError:
203
277
  source = ""
278
+ source = _strip_js_comments(source)
204
279
  for m in _JS_REEXPORT_RE.finditer(source):
205
280
  raw = m.group(1).strip()
206
281
  key = ("re_exports", raw)
@@ -117,6 +117,18 @@
117
117
  module_name: _ @import.path
118
118
  ) @import.from
119
119
 
120
+ ; `from pkg import name` — also capture each imported name so the edge can
121
+ ; bind the real symbol/submodule, not just the module path (graphify #1146).
122
+ (import_from_statement
123
+ module_name: _ @import.path
124
+ name: (dotted_name) @import.item
125
+ ) @import.from_item
126
+
127
+ (import_from_statement
128
+ module_name: _ @import.path
129
+ name: (aliased_import name: (dotted_name) @import.item)
130
+ ) @import.from_item
131
+
120
132
  (import_statement
121
133
  name: _ @import.path
122
134
  ) @import.plain
@@ -147,6 +147,18 @@
147
147
  module_name: _ @import.path
148
148
  ) @import.from
149
149
 
150
+ ; `from pkg import name` — also capture each imported name so the edge can
151
+ ; bind the real symbol/submodule, not just the module path (graphify #1146).
152
+ (import_from_statement
153
+ module_name: _ @import.path
154
+ name: (dotted_name) @import.item
155
+ ) @import.from_item
156
+
157
+ (import_from_statement
158
+ module_name: _ @import.path
159
+ name: (aliased_import name: (dotted_name) @import.item)
160
+ ) @import.from_item
161
+
150
162
  (import_statement
151
163
  name: _ @import.path
152
164
  ) @import.plain