source-kb 0.2.5__tar.gz → 0.2.26__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 (215) hide show
  1. {source_kb-0.2.5 → source_kb-0.2.26}/MANIFEST.in +15 -15
  2. {source_kb-0.2.5/source_kb.egg-info → source_kb-0.2.26}/PKG-INFO +15 -6
  3. {source_kb-0.2.5 → source_kb-0.2.26}/README.en.md +14 -5
  4. {source_kb-0.2.5 → source_kb-0.2.26}/README.md +14 -5
  5. {source_kb-0.2.5 → source_kb-0.2.26}/cli/__init__.py +21 -0
  6. {source_kb-0.2.5 → source_kb-0.2.26}/cli/__main__.py +1 -0
  7. source_kb-0.2.26/cli/commands/anchor_fix.py +126 -0
  8. source_kb-0.2.26/cli/commands/audit.py +53 -0
  9. source_kb-0.2.26/cli/commands/check_triggers.py +69 -0
  10. source_kb-0.2.26/cli/commands/detect.py +44 -0
  11. {source_kb-0.2.5 → source_kb-0.2.26}/cli/commands/dispatch.py +18 -5
  12. source_kb-0.2.26/cli/commands/history_cmd.py +94 -0
  13. {source_kb-0.2.5 → source_kb-0.2.26}/cli/commands/index.py +18 -6
  14. source_kb-0.2.26/cli/commands/install_skills.py +135 -0
  15. source_kb-0.2.26/cli/commands/merge.py +118 -0
  16. source_kb-0.2.26/cli/commands/merge_delta.py +49 -0
  17. source_kb-0.2.26/cli/commands/module_dag.py +39 -0
  18. {source_kb-0.2.5 → source_kb-0.2.26}/cli/commands/post_merge.py +10 -0
  19. source_kb-0.2.26/cli/commands/query_preset.py +50 -0
  20. {source_kb-0.2.5 → source_kb-0.2.26}/cli/commands/record_feedback.py +19 -2
  21. {source_kb-0.2.5 → source_kb-0.2.26}/cli/commands/render.py +24 -5
  22. source_kb-0.2.26/cli/commands/report.py +48 -0
  23. source_kb-0.2.26/cli/commands/scan_repos.py +179 -0
  24. source_kb-0.2.26/cli/commands/score.py +149 -0
  25. source_kb-0.2.26/cli/commands/status.py +82 -0
  26. source_kb-0.2.26/cli/commands/validate.py +332 -0
  27. source_kb-0.2.26/cli/global_assets.py +59 -0
  28. source_kb-0.2.26/core/bootstrap.py +414 -0
  29. {source_kb-0.2.5 → source_kb-0.2.26}/core/docs/shared.py +114 -0
  30. source_kb-0.2.26/core/docs/trigger_detect.py +106 -0
  31. source_kb-0.2.26/core/history.py +438 -0
  32. {source_kb-0.2.5 → source_kb-0.2.26}/core/paths.py +15 -0
  33. {source_kb-0.2.5 → source_kb-0.2.26}/core/preset.py +6 -1
  34. {source_kb-0.2.5 → source_kb-0.2.26}/core/prompt/__main__.py +19 -4
  35. {source_kb-0.2.5 → source_kb-0.2.26}/core/prompt/variables.py +0 -1
  36. {source_kb-0.2.5 → source_kb-0.2.26}/core/rag/__main__.py +3 -6
  37. {source_kb-0.2.5 → source_kb-0.2.26}/core/rag/bm25_index.py +39 -8
  38. {source_kb-0.2.5 → source_kb-0.2.26}/core/rag/chunker.py +36 -3
  39. source_kb-0.2.26/core/scan_repos.py +414 -0
  40. source_kb-0.2.26/core/skeleton/__main__.py +453 -0
  41. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/anchor_fix.py +98 -0
  42. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/cmd_anchor_fix.py +5 -1
  43. source_kb-0.2.26/core/skeleton/cmd_cross_graph.py +76 -0
  44. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/cmd_diff_doc.py +5 -1
  45. source_kb-0.2.26/core/skeleton/cmd_dispatch_preview.py +74 -0
  46. source_kb-0.2.26/core/skeleton/cmd_extract.py +84 -0
  47. source_kb-0.2.26/core/skeleton/cmd_file_list.py +66 -0
  48. source_kb-0.2.26/core/skeleton/cmd_graph.py +65 -0
  49. source_kb-0.2.26/core/skeleton/cmd_impact.py +68 -0
  50. source_kb-0.2.26/core/skeleton/cmd_inject_version.py +57 -0
  51. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/cmd_lock.py +5 -1
  52. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/cmd_merge_delta.py +5 -1
  53. source_kb-0.2.26/core/skeleton/cmd_report.py +117 -0
  54. source_kb-0.2.26/core/skeleton/cmd_split_apply.py +120 -0
  55. source_kb-0.2.26/core/skeleton/cmd_split_files.py +169 -0
  56. source_kb-0.2.26/core/skeleton/cmd_stale_files.py +108 -0
  57. source_kb-0.2.26/core/skeleton/cross_module_graph.py +245 -0
  58. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/dispatch.py +25 -1
  59. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/extract.py +24 -6
  60. source_kb-0.2.26/core/skeleton/graph.py +298 -0
  61. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/impact.py +92 -2
  62. source_kb-0.2.26/core/skeleton/impact_cross_module.py +86 -0
  63. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/parsers/jqassistant.py +19 -3
  64. source_kb-0.2.26/core/skeleton/section_ops.py +237 -0
  65. {source_kb-0.2.5 → source_kb-0.2.26}/core/validators/__init__.py +1 -0
  66. source_kb-0.2.26/core/validators/density.py +215 -0
  67. source_kb-0.2.26/core/validators/score.py +285 -0
  68. source_kb-0.2.26/examples/README.md +97 -0
  69. source_kb-0.2.26/examples/__init__.py +0 -0
  70. source_kb-0.2.26/examples/monorepo-backend/kb-project.yaml +70 -0
  71. source_kb-0.2.26/examples/multi-module-ecommerce/kb-project.yaml +88 -0
  72. source_kb-0.2.26/examples/spring-petclinic/README.md +39 -0
  73. source_kb-0.2.26/examples/spring-petclinic/kb-project.yaml +71 -0
  74. {source_kb-0.2.5 → source_kb-0.2.26}/presets/java-spring/doc_types.yaml +121 -1
  75. source_kb-0.2.26/presets/java-spring/templates/subagent-data-flow.md +105 -0
  76. source_kb-0.2.26/presets/java-spring/templates/subagent-deployment.md +143 -0
  77. source_kb-0.2.26/presets/java-spring/templates/subagent-extension-points.md +98 -0
  78. source_kb-0.2.26/presets/java-spring/templates/subagent-public-api.md +119 -0
  79. source_kb-0.2.26/presets/java-spring/templates/subagent-rate-limiting.md +103 -0
  80. source_kb-0.2.26/presets/java-spring/templates/subagent-routing.md +101 -0
  81. source_kb-0.2.26/presets/java-spring/templates/subagent-runbook.md +137 -0
  82. source_kb-0.2.26/presets/java-spring/templates/subagent-scheduling.md +106 -0
  83. source_kb-0.2.26/presets/java-spring/templates/subagent-starters.md +109 -0
  84. {source_kb-0.2.5 → source_kb-0.2.26}/pyproject.toml +5 -2
  85. {source_kb-0.2.5 → source_kb-0.2.26/source_kb.egg-info}/PKG-INFO +15 -6
  86. {source_kb-0.2.5 → source_kb-0.2.26}/source_kb.egg-info/SOURCES.txt +44 -0
  87. source_kb-0.2.26/source_kb.egg-info/top_level.txt +6 -0
  88. source_kb-0.2.5/cli/commands/anchor_fix.py +0 -47
  89. source_kb-0.2.5/cli/commands/audit.py +0 -18
  90. source_kb-0.2.5/cli/commands/merge.py +0 -60
  91. source_kb-0.2.5/cli/commands/merge_delta.py +0 -19
  92. source_kb-0.2.5/cli/commands/module_dag.py +0 -17
  93. source_kb-0.2.5/cli/commands/scan_repos.py +0 -44
  94. source_kb-0.2.5/cli/commands/validate.py +0 -191
  95. source_kb-0.2.5/core/scan_repos.py +0 -664
  96. source_kb-0.2.5/core/skeleton/__main__.py +0 -934
  97. source_kb-0.2.5/source_kb.egg-info/top_level.txt +0 -3
  98. {source_kb-0.2.5 → source_kb-0.2.26}/LICENSE +0 -0
  99. {source_kb-0.2.5 → source_kb-0.2.26}/cli/commands/__init__.py +0 -0
  100. {source_kb-0.2.5 → source_kb-0.2.26}/cli/commands/diff_doc.py +0 -0
  101. {source_kb-0.2.5 → source_kb-0.2.26}/cli/commands/extract.py +0 -0
  102. {source_kb-0.2.5 → source_kb-0.2.26}/cli/commands/file_list.py +0 -0
  103. {source_kb-0.2.5 → source_kb-0.2.26}/cli/commands/jar_resolve.py +0 -0
  104. {source_kb-0.2.5 → source_kb-0.2.26}/cli/commands/lock.py +0 -0
  105. {source_kb-0.2.5 → source_kb-0.2.26}/cli/commands/metadata.py +0 -0
  106. {source_kb-0.2.5 → source_kb-0.2.26}/cli/commands/query.py +0 -0
  107. {source_kb-0.2.5 → source_kb-0.2.26}/cli/commands/setup.py +0 -0
  108. {source_kb-0.2.5 → source_kb-0.2.26}/cli/commands/split.py +0 -0
  109. {source_kb-0.2.5 → source_kb-0.2.26}/cli/commands/stale_files.py +0 -0
  110. {source_kb-0.2.5 → source_kb-0.2.26}/core/__init__.py +0 -0
  111. {source_kb-0.2.5 → source_kb-0.2.26}/core/config.py +0 -0
  112. {source_kb-0.2.5 → source_kb-0.2.26}/core/docs/__init__.py +0 -0
  113. {source_kb-0.2.5 → source_kb-0.2.26}/core/docs/section_updater.py +0 -0
  114. {source_kb-0.2.5 → source_kb-0.2.26}/core/git.py +0 -0
  115. {source_kb-0.2.5 → source_kb-0.2.26}/core/interfaces.py +0 -0
  116. {source_kb-0.2.5 → source_kb-0.2.26}/core/monitor/__init__.py +0 -0
  117. {source_kb-0.2.5 → source_kb-0.2.26}/core/monitor/progress.py +0 -0
  118. {source_kb-0.2.5 → source_kb-0.2.26}/core/monitor/prompt_store.py +0 -0
  119. {source_kb-0.2.5 → source_kb-0.2.26}/core/preset_accessors.py +0 -0
  120. {source_kb-0.2.5 → source_kb-0.2.26}/core/preset_classify.py +0 -0
  121. {source_kb-0.2.5 → source_kb-0.2.26}/core/preset_hooks.py +0 -0
  122. {source_kb-0.2.5 → source_kb-0.2.26}/core/preset_profile.py +0 -0
  123. {source_kb-0.2.5 → source_kb-0.2.26}/core/prompt/__init__.py +0 -0
  124. {source_kb-0.2.5 → source_kb-0.2.26}/core/prompt/content.py +0 -0
  125. {source_kb-0.2.5 → source_kb-0.2.26}/core/prompt/context_manager.py +0 -0
  126. {source_kb-0.2.5 → source_kb-0.2.26}/core/prompt/renderer.py +0 -0
  127. {source_kb-0.2.5 → source_kb-0.2.26}/core/prompt/response_parser.py +0 -0
  128. {source_kb-0.2.5 → source_kb-0.2.26}/core/prompt/templates.py +0 -0
  129. {source_kb-0.2.5 → source_kb-0.2.26}/core/prompt/validate_parity.py +0 -0
  130. {source_kb-0.2.5 → source_kb-0.2.26}/core/rag/__init__.py +0 -0
  131. {source_kb-0.2.5 → source_kb-0.2.26}/core/rag/embedder.py +0 -0
  132. {source_kb-0.2.5 → source_kb-0.2.26}/core/rag/indexer.py +0 -0
  133. {source_kb-0.2.5 → source_kb-0.2.26}/core/rag/loader.py +0 -0
  134. {source_kb-0.2.5 → source_kb-0.2.26}/core/rag/retriever.py +0 -0
  135. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/__init__.py +0 -0
  136. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/classify.py +0 -0
  137. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/community.py +0 -0
  138. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/dependency_graph.py +0 -0
  139. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/diff_doc.py +0 -0
  140. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/dispatch_render.py +0 -0
  141. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/dispatch_source.py +0 -0
  142. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/extract_methods.py +0 -0
  143. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/file_list.py +0 -0
  144. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/jar_download.py +0 -0
  145. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/jar_resolver.py +0 -0
  146. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/loader.py +0 -0
  147. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/merge.py +0 -0
  148. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/merge_delta.py +0 -0
  149. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/metadata.py +0 -0
  150. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/metadata_builders.py +0 -0
  151. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/module_dag.py +0 -0
  152. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/parsers/__init__.py +0 -0
  153. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/parsers/jqassistant_cypher.py +0 -0
  154. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/parsers/regex.py +0 -0
  155. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/parsers/treesitter.py +0 -0
  156. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/parsers/treesitter_java.py +0 -0
  157. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/parsers/treesitter_multi.py +0 -0
  158. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/pom_parser.py +0 -0
  159. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/post_merge.py +0 -0
  160. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/post_merge_llm.py +0 -0
  161. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/query.py +0 -0
  162. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/shard_context.py +0 -0
  163. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/split.py +0 -0
  164. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/split_cache.py +0 -0
  165. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/split_feedback.py +0 -0
  166. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/split_plan.py +0 -0
  167. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/split_plan_helpers.py +0 -0
  168. {source_kb-0.2.5 → source_kb-0.2.26}/core/skeleton/split_plan_llm.py +0 -0
  169. {source_kb-0.2.5 → source_kb-0.2.26}/core/utils.py +0 -0
  170. {source_kb-0.2.5 → source_kb-0.2.26}/core/validators/__main__.py +0 -0
  171. {source_kb-0.2.5 → source_kb-0.2.26}/core/validators/consistency.py +0 -0
  172. {source_kb-0.2.5 → source_kb-0.2.26}/core/validators/coverage.py +0 -0
  173. {source_kb-0.2.5 → source_kb-0.2.26}/core/validators/duplicates.py +0 -0
  174. {source_kb-0.2.5 → source_kb-0.2.26}/core/validators/engine.py +0 -0
  175. {source_kb-0.2.5 → source_kb-0.2.26}/core/validators/links.py +0 -0
  176. {source_kb-0.2.5 → source_kb-0.2.26}/core/validators/sampling.py +0 -0
  177. {source_kb-0.2.5 → source_kb-0.2.26}/core/validators/structure.py +0 -0
  178. {source_kb-0.2.5 → source_kb-0.2.26}/presets/generic/audit_dimensions.md +0 -0
  179. {source_kb-0.2.5 → source_kb-0.2.26}/presets/generic/doc_types.yaml +0 -0
  180. {source_kb-0.2.5 → source_kb-0.2.26}/presets/generic/preset.yaml +0 -0
  181. {source_kb-0.2.5 → source_kb-0.2.26}/presets/java-spring/audit_dimensions.md +0 -0
  182. {source_kb-0.2.5 → source_kb-0.2.26}/presets/java-spring/audit_dimensions.yaml +0 -0
  183. {source_kb-0.2.5 → source_kb-0.2.26}/presets/java-spring/hooks.py +0 -0
  184. {source_kb-0.2.5 → source_kb-0.2.26}/presets/java-spring/preset.yaml +0 -0
  185. {source_kb-0.2.5 → source_kb-0.2.26}/presets/java-spring/templates/README.md +0 -0
  186. {source_kb-0.2.5 → source_kb-0.2.26}/presets/java-spring/templates/audit-system.md +0 -0
  187. {source_kb-0.2.5 → source_kb-0.2.26}/presets/java-spring/templates/subagent-aop.md +0 -0
  188. {source_kb-0.2.5 → source_kb-0.2.26}/presets/java-spring/templates/subagent-api.md +0 -0
  189. {source_kb-0.2.5 → source_kb-0.2.26}/presets/java-spring/templates/subagent-architecture.md +0 -0
  190. {source_kb-0.2.5 → source_kb-0.2.26}/presets/java-spring/templates/subagent-async-events.md +0 -0
  191. {source_kb-0.2.5 → source_kb-0.2.26}/presets/java-spring/templates/subagent-audit-api-contracts.md +0 -0
  192. {source_kb-0.2.5 → source_kb-0.2.26}/presets/java-spring/templates/subagent-audit-architecture.md +0 -0
  193. {source_kb-0.2.5 → source_kb-0.2.26}/presets/java-spring/templates/subagent-audit-business.md +0 -0
  194. {source_kb-0.2.5 → source_kb-0.2.26}/presets/java-spring/templates/subagent-audit-data-models.md +0 -0
  195. {source_kb-0.2.5 → source_kb-0.2.26}/presets/java-spring/templates/subagent-business.md +0 -0
  196. {source_kb-0.2.5 → source_kb-0.2.26}/presets/java-spring/templates/subagent-caching.md +0 -0
  197. {source_kb-0.2.5 → source_kb-0.2.26}/presets/java-spring/templates/subagent-database-access.md +0 -0
  198. {source_kb-0.2.5 → source_kb-0.2.26}/presets/java-spring/templates/subagent-enum.md +0 -0
  199. {source_kb-0.2.5 → source_kb-0.2.26}/presets/java-spring/templates/subagent-error-handling.md +0 -0
  200. {source_kb-0.2.5 → source_kb-0.2.26}/presets/java-spring/templates/subagent-external-integrations.md +0 -0
  201. {source_kb-0.2.5 → source_kb-0.2.26}/presets/java-spring/templates/subagent-index.md +0 -0
  202. {source_kb-0.2.5 → source_kb-0.2.26}/presets/java-spring/templates/subagent-messaging.md +0 -0
  203. {source_kb-0.2.5 → source_kb-0.2.26}/presets/java-spring/templates/subagent-model.md +0 -0
  204. {source_kb-0.2.5 → source_kb-0.2.26}/presets/java-spring/templates/subagent-observability.md +0 -0
  205. {source_kb-0.2.5 → source_kb-0.2.26}/presets/java-spring/templates/subagent-scheduled.md +0 -0
  206. {source_kb-0.2.5 → source_kb-0.2.26}/presets/java-spring/templates/subagent-security.md +0 -0
  207. {source_kb-0.2.5 → source_kb-0.2.26}/presets/java-spring/templates/subagent-structure.md +0 -0
  208. {source_kb-0.2.5 → source_kb-0.2.26}/presets/java-spring/templates/subagent-sync-section.md +0 -0
  209. {source_kb-0.2.5 → source_kb-0.2.26}/presets/java-spring/templates/subagent-utils.md +0 -0
  210. {source_kb-0.2.5 → source_kb-0.2.26}/presets/java-spring/templates/sync-system.md +0 -0
  211. {source_kb-0.2.5 → source_kb-0.2.26}/presets/java-spring/workflow-extensions.md +0 -0
  212. {source_kb-0.2.5 → source_kb-0.2.26}/setup.cfg +0 -0
  213. {source_kb-0.2.5 → source_kb-0.2.26}/source_kb.egg-info/dependency_links.txt +0 -0
  214. {source_kb-0.2.5 → source_kb-0.2.26}/source_kb.egg-info/entry_points.txt +0 -0
  215. {source_kb-0.2.5 → source_kb-0.2.26}/source_kb.egg-info/requires.txt +0 -0
@@ -1,15 +1,15 @@
1
- include LICENSE
2
- include README.en.md
3
- include pyproject.toml
4
- include setup.cfg
5
-
6
- recursive-include cli *.py
7
- recursive-include core *.py
8
- recursive-include presets *.yaml *.md *.py
9
-
10
- prune engine
11
- prune mcp_server
12
- prune skills
13
- prune tests
14
- prune docs
15
- prune .source-cache
1
+ include LICENSE
2
+ include README.en.md
3
+ include pyproject.toml
4
+ include setup.cfg
5
+
6
+ recursive-include cli *.py
7
+ recursive-include core *.py
8
+ recursive-include presets *.yaml *.md *.py
9
+
10
+ prune engine
11
+ prune mcp_server
12
+ prune skills
13
+ prune tests
14
+ prune docs
15
+ prune .source-cache
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: source-kb
3
- Version: 0.2.5
3
+ Version: 0.2.26
4
4
  Summary: Auto-generate structured knowledge base documents from source code. Supports AI agent mode (skill-based) and standalone CLI.
5
5
  License-Expression: MIT
6
6
  Keywords: knowledge-base,documentation,code-analysis,llm,rag
@@ -61,15 +61,24 @@ Install the CLI toolchain:
61
61
 
62
62
  ```bash
63
63
  pip install source-kb[full]
64
+
65
+ # If you have multiple Python versions, specify the target version:
66
+ py -3.12 -m pip install source-kb[full]
67
+ ```
68
+
69
+ Install skills to your Agent's directory:
70
+
71
+ ```bash
72
+ source-kb install-skills --agent kiro # Kiro
73
+ source-kb install-skills --agent cursor # Cursor
74
+ source-kb install-skills --agent claude # Claude Code
64
75
  ```
65
76
 
66
- Clone the presets repo into your project:
77
+ This automatically installs `presets/` and `examples/` to `~/.source-kb/` (shared globally across all projects).
78
+ To install global assets only:
67
79
 
68
80
  ```bash
69
- git clone https://github.com/anthropics/source-kb-presets.git
70
- cp -r source-kb-presets/skills ./skills
71
- cp -r source-kb-presets/presets ./presets
72
- cp -r source-kb-presets/examples ./examples
81
+ source-kb install-skills --assets-only # Install presets/ + examples/ to ~/.source-kb/
73
82
  ```
74
83
 
75
84
  Then talk to your AI Agent (Kiro / Claude Code / Cursor / Windsurf):
@@ -28,15 +28,24 @@ Install the CLI toolchain:
28
28
 
29
29
  ```bash
30
30
  pip install source-kb[full]
31
+
32
+ # If you have multiple Python versions, specify the target version:
33
+ py -3.12 -m pip install source-kb[full]
34
+ ```
35
+
36
+ Install skills to your Agent's directory:
37
+
38
+ ```bash
39
+ source-kb install-skills --agent kiro # Kiro
40
+ source-kb install-skills --agent cursor # Cursor
41
+ source-kb install-skills --agent claude # Claude Code
31
42
  ```
32
43
 
33
- Clone the presets repo into your project:
44
+ This automatically installs `presets/` and `examples/` to `~/.source-kb/` (shared globally across all projects).
45
+ To install global assets only:
34
46
 
35
47
  ```bash
36
- git clone https://github.com/anthropics/source-kb-presets.git
37
- cp -r source-kb-presets/skills ./skills
38
- cp -r source-kb-presets/presets ./presets
39
- cp -r source-kb-presets/examples ./examples
48
+ source-kb install-skills --assets-only # Install presets/ + examples/ to ~/.source-kb/
40
49
  ```
41
50
 
42
51
  Then talk to your AI Agent (Kiro / Claude Code / Cursor / Windsurf):
@@ -28,15 +28,24 @@
28
28
 
29
29
  ```bash
30
30
  pip install source-kb[full]
31
+
32
+ # 如果系统有多个 Python 版本,指定目标版本安装:
33
+ py -3.12 -m pip install source-kb[full]
34
+ ```
35
+
36
+ 一键安装 Skill 到 Agent 目录:
37
+
38
+ ```bash
39
+ source-kb install-skills --agent kiro # Kiro
40
+ source-kb install-skills --agent cursor # Cursor
41
+ source-kb install-skills --agent claude # Claude Code
31
42
  ```
32
43
 
33
- 克隆 presets 仓库到你的项目中:
44
+ 安装过程会自动将 `presets/` 和 `examples/` 安装到 `~/.source-kb/`(全局共享,所有项目通用)。
45
+ 如需单独安装全局资产:
34
46
 
35
47
  ```bash
36
- git clone https://github.com/anthropics/source-kb-presets.git
37
- cp -r source-kb-presets/skills ./skills
38
- cp -r source-kb-presets/presets ./presets
39
- cp -r source-kb-presets/examples ./examples
48
+ source-kb install-skills --assets-only # 仅安装 presets/ + examples/ 到 ~/.source-kb/
40
49
  ```
41
50
 
42
51
  在 AI Agent(Kiro / Claude Code / Cursor / Windsurf)中直接对话:
@@ -23,10 +23,31 @@ def _discover_commands(subparsers: argparse._SubParsersAction) -> None:
23
23
  module.register(subparsers)
24
24
 
25
25
 
26
+ def _warn_python_version() -> None:
27
+ """Warn if tree-sitter-languages is unavailable (Python 3.13+)."""
28
+ v = sys.version_info
29
+ if v >= (3, 13):
30
+ try:
31
+ import tree_sitter_languages # noqa: F401
32
+ except ImportError:
33
+ print(
34
+ f"[WARNING] Python {v.major}.{v.minor} detected. "
35
+ f"tree-sitter-languages has no wheel for 3.13+, "
36
+ f"skeleton extraction will fall back to regex (lower accuracy).\n"
37
+ f" Recommended: py -3.12 -m pip install source-kb[full]",
38
+ file=sys.stderr,
39
+ )
40
+
41
+
26
42
  def main(argv: list[str] | None = None) -> None:
27
43
  sys.stdout.reconfigure(encoding="utf-8")
28
44
  sys.stderr.reconfigure(encoding="utf-8")
29
45
 
46
+ _warn_python_version()
47
+
48
+ from cli.global_assets import ensure_global_assets
49
+ ensure_global_assets()
50
+
30
51
  from core import __version__
31
52
 
32
53
  parser = argparse.ArgumentParser(
@@ -1,4 +1,5 @@
1
1
  """Allow running as: python -m cli"""
2
+ from __future__ import annotations
2
3
 
3
4
  from cli import main
4
5
 
@@ -0,0 +1,126 @@
1
+ """source-kb anchor-fix — Fix broken cross-document anchor links."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import sys
8
+ from pathlib import Path
9
+
10
+
11
+ def register(subparsers: argparse._SubParsersAction) -> None:
12
+ p = subparsers.add_parser("anchor-fix", help="Fix broken cross-document anchor links")
13
+ p.add_argument("--module-dir", required=True, help="Module documentation directory")
14
+ p.add_argument("--dry-run", action="store_true", help="Report without fixing")
15
+ p.add_argument("--threshold", type=float, default=0.8, help="Fuzzy match threshold (0.0-1.0)")
16
+ p.add_argument("--detect-cascade", metavar="FILE",
17
+ help="Detect cascade: check which links break due to title changes in FILE")
18
+ p.add_argument("--old-content", help="Path to old version of file (for --detect-cascade)")
19
+ p.set_defaults(func=run)
20
+
21
+
22
+ def run(args: argparse.Namespace) -> None:
23
+ module_dir = Path(args.module_dir)
24
+ if not module_dir.is_dir():
25
+ print(f"Error: directory not found: {module_dir}", file=sys.stderr)
26
+ sys.exit(1)
27
+
28
+ if getattr(args, "detect_cascade", None):
29
+ _run_detect_cascade(args, module_dir)
30
+ else:
31
+ _run_fix(args, module_dir)
32
+
33
+
34
+ def _run_fix(args: argparse.Namespace, module_dir: Path) -> None:
35
+ from core.skeleton.anchor_fix import fix_anchors
36
+
37
+ result = fix_anchors(
38
+ module_dir,
39
+ dry_run=args.dry_run,
40
+ similarity_threshold=args.threshold,
41
+ )
42
+
43
+ mode = " (dry-run)" if args.dry_run else ""
44
+ print(f"Anchor fix{mode}: scanned {result.files_scanned} files, checked {result.links_checked} links")
45
+ if result.links_fixed or result.links_degraded:
46
+ print(f" Fixed: {result.links_fixed}, Degraded: {result.links_degraded}")
47
+ for d in result.details:
48
+ action = "fixed" if d["action"] == "fixed" else "degraded"
49
+ print(f" [{action}] {d['file']}: {d['old_link']} -> {d['new_link']}")
50
+ else:
51
+ print(" All links valid.")
52
+
53
+ print(json.dumps({
54
+ "status": "ok", "files_scanned": result.files_scanned,
55
+ "links_checked": result.links_checked, "links_fixed": result.links_fixed,
56
+ "links_degraded": result.links_degraded,
57
+ }, ensure_ascii=False), file=sys.stderr)
58
+
59
+
60
+ def _run_detect_cascade(args: argparse.Namespace, module_dir: Path) -> None:
61
+ from core.skeleton.anchor_fix import detect_cascade
62
+
63
+ changed_file = args.detect_cascade
64
+ target_path = module_dir / changed_file
65
+
66
+ if not target_path.exists():
67
+ print(f"Error: {changed_file} not found in {module_dir}", file=sys.stderr)
68
+ sys.exit(1)
69
+
70
+ new_content = target_path.read_text(encoding="utf-8", errors="replace")
71
+
72
+ if args.old_content:
73
+ old_path = Path(args.old_content)
74
+ if not old_path.exists():
75
+ print(f"Error: old content file not found: {old_path}", file=sys.stderr)
76
+ sys.exit(1)
77
+ old_content = old_path.read_text(encoding="utf-8", errors="replace")
78
+ else:
79
+ # Try git to get previous version
80
+ try:
81
+ import subprocess
82
+ result = subprocess.run(
83
+ ["git", "show", f"HEAD:{changed_file}"],
84
+ cwd=str(module_dir), capture_output=True, text=True, timeout=10,
85
+ )
86
+ if result.returncode == 0:
87
+ old_content = result.stdout
88
+ else:
89
+ print("Error: --old-content required (git HEAD version not available)",
90
+ file=sys.stderr)
91
+ sys.exit(1)
92
+ except Exception:
93
+ print("Error: --old-content required (git not available)", file=sys.stderr)
94
+ sys.exit(1)
95
+
96
+ cascade = detect_cascade(
97
+ module_dir, changed_file, old_content, new_content,
98
+ similarity_threshold=args.threshold,
99
+ )
100
+
101
+ if not cascade.removed_anchors and not cascade.renamed_anchors:
102
+ print(f"No title changes detected in {changed_file}")
103
+ print(json.dumps({"status": "ok", "cascade": False}, ensure_ascii=False),
104
+ file=sys.stderr)
105
+ return
106
+
107
+ print(f"Cascade detection for {changed_file}:")
108
+ if cascade.renamed_anchors:
109
+ print(f" Renamed ({len(cascade.renamed_anchors)}):")
110
+ for r in cascade.renamed_anchors:
111
+ print(f" #{r['old']} -> #{r['new']} (score={r['score']})")
112
+ if cascade.removed_anchors:
113
+ print(f" Removed ({len(cascade.removed_anchors)}):")
114
+ for a in cascade.removed_anchors:
115
+ print(f" #{a}")
116
+ if cascade.affected_links:
117
+ print(f" Affected links ({len(cascade.affected_links)}):")
118
+ for link in cascade.affected_links:
119
+ print(f" {link['file']}: {link['link']}")
120
+
121
+ print(json.dumps({
122
+ "status": "cascade_detected",
123
+ "renamed": len(cascade.renamed_anchors),
124
+ "removed": len(cascade.removed_anchors),
125
+ "affected_links": len(cascade.affected_links),
126
+ }, ensure_ascii=False), file=sys.stderr)
@@ -0,0 +1,53 @@
1
+ """source-kb audit — Low-confidence classification audit."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import sys
8
+ from pathlib import Path
9
+
10
+
11
+ def register(subparsers: argparse._SubParsersAction) -> None:
12
+ p = subparsers.add_parser("audit", help="Low-confidence classification audit")
13
+ p.add_argument("--skeleton", required=True, help="Skeleton JSON path")
14
+ p.add_argument("--preset", required=True, help="Preset name")
15
+ p.add_argument("--threshold", type=float, default=0.7, help="Confidence threshold (default: 0.7)")
16
+ p.set_defaults(func=run)
17
+
18
+
19
+ def run(args: argparse.Namespace) -> None:
20
+ from core.skeleton.classify import find_suspicious_files
21
+ from core.preset import load_preset
22
+ from core.skeleton.query import load_skeleton
23
+
24
+ preset = load_preset(args.preset)
25
+ skeleton_path = Path(args.skeleton)
26
+ entries = load_skeleton(skeleton_path)
27
+ threshold = args.threshold
28
+
29
+ suspicious = find_suspicious_files(entries, preset, threshold=threshold)
30
+
31
+ if suspicious:
32
+ print(f"Found {len(suspicious)} low-confidence files (threshold < {threshold}):\n")
33
+ print("| File | Category | Confidence | Match Reason | Suspicious Signal |")
34
+ print("|------|------|--------|---------|---------|")
35
+ for entry in suspicious:
36
+ cats = ", ".join(entry.categories) if entry.categories else "-"
37
+ reasons = "; ".join(entry.match_reasons[:2]) if entry.match_reasons else "-"
38
+ signals = "; ".join(entry.suspicious_signals[:2]) if entry.suspicious_signals else "-"
39
+ print(f"| {entry.file} | {cats} | {entry.confidence:.2f} | {reasons} | {signals} |")
40
+ else:
41
+ print(f"No low-confidence files (threshold < {threshold})")
42
+
43
+ from core.history import record_global_event
44
+ skeleton_dir = skeleton_path.parent
45
+ knowledge_dir = skeleton_dir.parent if skeleton_dir.name == ".meta" else skeleton_dir
46
+ record_global_event(
47
+ knowledge_dir, "audit",
48
+ docs_updated=[skeleton_path.name],
49
+ trigger="cli",
50
+ )
51
+
52
+ print(json.dumps({"status": "ok", "suspicious": len(suspicious),
53
+ "threshold": threshold}, ensure_ascii=False), file=sys.stderr)
@@ -0,0 +1,69 @@
1
+ """source-kb check-triggers — Detect optional document triggers from changed files."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import sys
8
+ from pathlib import Path
9
+
10
+
11
+ def register(subparsers: argparse._SubParsersAction) -> None:
12
+ p = subparsers.add_parser(
13
+ "check-triggers",
14
+ help="Detect optional documents triggered by source changes",
15
+ )
16
+ p.add_argument("--module-dir", required=True, help="Module documentation directory")
17
+ p.add_argument("--preset", default="java-spring", help="Preset name")
18
+ p.add_argument("--files", nargs="+", help="Changed source files to scan")
19
+ p.add_argument("--source-cache", help="Source cache directory (reads files from here)")
20
+ p.set_defaults(func=run)
21
+
22
+
23
+ def run(args: argparse.Namespace) -> None:
24
+ from core.preset import load_preset
25
+ from core.docs.trigger_detect import detect_triggers_from_dir
26
+
27
+ module_dir = Path(args.module_dir)
28
+ preset = load_preset(args.preset)
29
+
30
+ changed_files: dict[str, str] = {}
31
+
32
+ if args.files:
33
+ source_base = Path(args.source_cache) if args.source_cache else Path(".")
34
+ for filepath in args.files:
35
+ full_path = source_base / filepath
36
+ if full_path.exists():
37
+ try:
38
+ changed_files[filepath] = full_path.read_text(encoding="utf-8", errors="replace")
39
+ except OSError:
40
+ pass
41
+
42
+ if not changed_files:
43
+ print("No changed files to scan.")
44
+ print(json.dumps({"status": "ok", "triggers": []}, ensure_ascii=False),
45
+ file=sys.stderr)
46
+ return
47
+
48
+ triggers = detect_triggers_from_dir(changed_files, preset, module_dir)
49
+
50
+ if not triggers:
51
+ print("No optional document triggers detected.")
52
+ print(json.dumps({"status": "ok", "triggers": []}, ensure_ascii=False),
53
+ file=sys.stderr)
54
+ return
55
+
56
+ print(f"Detected {len(triggers)} document trigger(s):")
57
+ for t in triggers:
58
+ action_label = "CREATE" if t["action"] == "create" else "UPDATE"
59
+ print(f" [{action_label}] {t['filename']} ({t['doc_type']})")
60
+ for m in t["matches"][:3]:
61
+ print(f" matched '{m['keyword']}' in {m['file']}")
62
+
63
+ print(json.dumps({
64
+ "status": "ok",
65
+ "triggers": [
66
+ {"doc_type": t["doc_type"], "filename": t["filename"], "action": t["action"]}
67
+ for t in triggers
68
+ ],
69
+ }, ensure_ascii=False), file=sys.stderr)
@@ -0,0 +1,44 @@
1
+ """source-kb detect — Detect project language, framework, and structure."""
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ import json
6
+ import sys
7
+ from pathlib import Path
8
+
9
+
10
+ def register(subparsers: argparse._SubParsersAction) -> None:
11
+ p = subparsers.add_parser(
12
+ "detect",
13
+ help="Detect project language, framework, structure, and modules",
14
+ )
15
+ p.add_argument("--path", default=".", help="Path to source repo (default: current dir)")
16
+ p.add_argument("--json", action="store_true", dest="as_json", help="Output as JSON")
17
+ p.set_defaults(func=run)
18
+
19
+
20
+ def run(args: argparse.Namespace) -> None:
21
+ from core.bootstrap import detect_project, format_detection_summary
22
+
23
+ repo_path = Path(args.path).resolve()
24
+ if not repo_path.is_dir():
25
+ print(f"Error: path does not exist: {repo_path}")
26
+ sys.exit(1)
27
+
28
+ info = detect_project(repo_path)
29
+
30
+ if args.as_json:
31
+ data = {
32
+ "language": info.language,
33
+ "framework": info.framework,
34
+ "preset": info.preset,
35
+ "structure": info.structure,
36
+ "repo_name": info.repo_name,
37
+ "modules": [
38
+ {"name": m.name, "path": m.path, "type": m.module_type, "description": m.description}
39
+ for m in info.modules
40
+ ],
41
+ }
42
+ print(json.dumps(data, ensure_ascii=False, indent=2))
43
+ else:
44
+ print(format_detection_summary(info))
@@ -34,14 +34,27 @@ def run(args: argparse.Namespace) -> None:
34
34
 
35
35
  module_name = args.module
36
36
  module_dir = knowledge_dir / module_name
37
- source_cache = cache_dir / module_name
38
37
 
39
38
  module_type = "service"
40
- repos = source.get("repos", [])
41
- for repo in repos:
42
- if repo.get("name") == module_name:
43
- module_type = repo.get("type", "service")
39
+ source_cache = cache_dir / module_name
40
+
41
+ modules = kb_config.get("modules", [])
42
+ for mod in modules:
43
+ if mod.get("name") == module_name:
44
+ module_type = mod.get("module_type", mod.get("type", "service"))
45
+ repo_name = mod.get("repo", module_name)
46
+ mod_path = mod.get("path", ".")
47
+ if mod_path and mod_path != ".":
48
+ source_cache = cache_dir / repo_name / mod_path
49
+ else:
50
+ source_cache = cache_dir / repo_name
44
51
  break
52
+ else:
53
+ repos = source.get("repos", [])
54
+ for repo in repos:
55
+ if repo.get("name") == module_name:
56
+ module_type = repo.get("type", "service")
57
+ break
45
58
 
46
59
  mode = args.mode
47
60
 
@@ -0,0 +1,94 @@
1
+ """source-kb history — Show operation history for a module or knowledge base."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import sys
8
+ from datetime import datetime
9
+ from pathlib import Path
10
+
11
+
12
+ def register(subparsers: argparse._SubParsersAction) -> None:
13
+ p = subparsers.add_parser("history", help="Show operation history")
14
+ p.add_argument("--config", help="kb-project.yaml path")
15
+ p.add_argument("--kb", required=True, help="Knowledge base name")
16
+ p.add_argument("--module", help="Module name (omit for global changelog)")
17
+ p.add_argument("--limit", type=int, default=20, help="Max entries (default: 20)")
18
+ p.add_argument("--json", dest="as_json", action="store_true", help="Output as JSON")
19
+ p.set_defaults(func=run)
20
+
21
+
22
+ def run(args: argparse.Namespace) -> None:
23
+ from core.config import load_config, find_config
24
+ from core.history import get_module_history, get_global_changelog
25
+
26
+ config_path = Path(args.config) if args.config else find_config()
27
+ config = load_config(config_path)
28
+ kb_cfg = config.get_kb(args.kb)
29
+
30
+ knowledge_dir = Path(kb_cfg.get("knowledge_dir", f"./knowledge/{args.kb}"))
31
+ if not knowledge_dir.is_absolute():
32
+ config_dir = Path(config.raw.get("_config_dir", ".")).resolve()
33
+ knowledge_dir = (config_dir / knowledge_dir).resolve()
34
+
35
+ if not knowledge_dir.is_dir():
36
+ print(f"Error: knowledge directory not found: {knowledge_dir}", file=sys.stderr)
37
+ sys.exit(1)
38
+
39
+ if args.module:
40
+ module_dir = knowledge_dir / args.module
41
+ if not module_dir.is_dir():
42
+ print(f"Error: module not found: {module_dir}", file=sys.stderr)
43
+ sys.exit(1)
44
+ entries = get_module_history(module_dir, limit=args.limit)
45
+ scope = f"{args.kb}/{args.module}"
46
+ else:
47
+ entries = get_global_changelog(knowledge_dir, limit=args.limit)
48
+ scope = f"{args.kb} (global)"
49
+
50
+ if args.as_json:
51
+ print(json.dumps(entries, ensure_ascii=False, indent=2))
52
+ return
53
+
54
+ if not entries:
55
+ print(f"No history for {scope}")
56
+ print(json.dumps({"status": "ok", "entries": 0}, ensure_ascii=False), file=sys.stderr)
57
+ return
58
+
59
+ print(f"History: {scope} (showing {len(entries)}/{args.limit} max)\n")
60
+
61
+ for entry in entries:
62
+ ts = _format_ts(entry.get("ts", ""))
63
+ op = entry.get("op", "?")
64
+ doc_type = entry.get("doc_type", "")
65
+ version = entry.get("version")
66
+ modules = entry.get("modules_affected", [])
67
+
68
+ line = f" {ts} {op:<12}"
69
+ if doc_type:
70
+ line += f" {doc_type}"
71
+ if version is not None:
72
+ line += f" v{version}"
73
+ if modules:
74
+ line += f" [{', '.join(modules[:3])}]"
75
+
76
+ stats = entry.get("stats", {})
77
+ if stats:
78
+ delta = stats.get("delta", "")
79
+ if delta:
80
+ line += f" ({delta})"
81
+
82
+ print(line)
83
+
84
+ print(json.dumps({"status": "ok", "entries": len(entries)}, ensure_ascii=False), file=sys.stderr)
85
+
86
+
87
+ def _format_ts(ts: str) -> str:
88
+ if not ts:
89
+ return " "
90
+ try:
91
+ dt = datetime.fromisoformat(ts)
92
+ return dt.strftime("%Y-%m-%d %H:%M")
93
+ except (ValueError, TypeError):
94
+ return ts[:16]
@@ -35,14 +35,12 @@ def run_index(args: argparse.Namespace) -> None:
35
35
  knowledge_dir = Path(kb_cfg["knowledge_dir"])
36
36
  collection_name = kb_cfg["collection"]
37
37
 
38
- incremental = bool(args.module or args.files)
39
- if args.module:
40
- knowledge_dir = knowledge_dir / args.module
38
+ module = args.module
41
39
 
42
40
  if args.files:
43
- docs = load_documents(knowledge_dir, file_filter=args.files)
41
+ docs = load_documents(knowledge_dir, module=module, files=args.files)
44
42
  else:
45
- docs = load_documents(knowledge_dir)
43
+ docs = load_documents(knowledge_dir, module=module)
46
44
 
47
45
  if not docs:
48
46
  print(f"No documents found in {knowledge_dir}")
@@ -51,8 +49,22 @@ def run_index(args: argparse.Namespace) -> None:
51
49
 
52
50
  chunks = chunk_documents(docs)
53
51
  print(f"Indexing {len(docs)} docs, {len(chunks)} chunks...")
54
- build_index(chunks, collection_name, config, kb_name=args.kb, incremental=incremental)
52
+ build_index(chunks, collection_name, config, kb_name=args.kb, module=module, files=args.files)
55
53
  print(f"Index built: {len(chunks)} chunks -> collection '{collection_name}'")
54
+
55
+ from core.history import mark_indexed, record_global_event, refresh_kb_state
56
+ if module:
57
+ module_dir = knowledge_dir / module
58
+ if module_dir.is_dir():
59
+ mark_indexed(module_dir, config.embed_model, len(chunks), collection=collection_name)
60
+ else:
61
+ for d in sorted(knowledge_dir.iterdir()):
62
+ if d.is_dir() and not d.name.startswith(".") and (d / ".meta").is_dir():
63
+ mark_indexed(d, config.embed_model, len(chunks), collection=collection_name)
64
+
65
+ record_global_event(knowledge_dir, "index", trigger="cli")
66
+ refresh_kb_state(knowledge_dir)
67
+
56
68
  print(json.dumps({"status": "ok", "docs": len(docs), "chunks": len(chunks),
57
69
  "collection": collection_name}, ensure_ascii=False), file=sys.stderr)
58
70