source-kb 0.2.26__tar.gz → 0.2.28__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 (214) hide show
  1. {source_kb-0.2.26/source_kb.egg-info → source_kb-0.2.28}/PKG-INFO +1 -1
  2. source_kb-0.2.28/cli/commands/cross_graph.py +27 -0
  3. source_kb-0.2.28/cli/commands/dispatch_preview.py +28 -0
  4. source_kb-0.2.28/cli/commands/graph.py +27 -0
  5. source_kb-0.2.28/cli/commands/impact.py +32 -0
  6. source_kb-0.2.28/cli/commands/inject_version.py +22 -0
  7. source_kb-0.2.28/cli/commands/sync_prompt.py +20 -0
  8. {source_kb-0.2.26 → source_kb-0.2.28}/core/__init__.py +2 -0
  9. {source_kb-0.2.26 → source_kb-0.2.28}/core/docs/__init__.py +2 -0
  10. {source_kb-0.2.26 → source_kb-0.2.28}/core/docs/shared.py +1 -1
  11. {source_kb-0.2.26 → source_kb-0.2.28}/core/monitor/__init__.py +2 -0
  12. {source_kb-0.2.26 → source_kb-0.2.28}/core/paths.py +30 -0
  13. {source_kb-0.2.26 → source_kb-0.2.28}/core/prompt/__init__.py +2 -0
  14. {source_kb-0.2.26 → source_kb-0.2.28}/core/prompt/__main__.py +40 -28
  15. {source_kb-0.2.26 → source_kb-0.2.28}/core/prompt/content.py +30 -4
  16. {source_kb-0.2.26 → source_kb-0.2.28}/core/prompt/renderer.py +55 -3
  17. {source_kb-0.2.26 → source_kb-0.2.28}/core/rag/__init__.py +2 -0
  18. {source_kb-0.2.26 → source_kb-0.2.28}/core/rag/__main__.py +27 -22
  19. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/__init__.py +2 -0
  20. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/__main__.py +34 -243
  21. source_kb-0.2.28/core/skeleton/_dispatch.py +348 -0
  22. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/cmd_anchor_fix.py +4 -6
  23. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/cmd_cross_graph.py +11 -10
  24. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/cmd_diff_doc.py +7 -11
  25. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/cmd_dispatch_preview.py +20 -19
  26. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/cmd_extract.py +31 -24
  27. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/cmd_file_list.py +24 -20
  28. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/cmd_graph.py +3 -8
  29. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/cmd_impact.py +23 -17
  30. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/cmd_inject_version.py +1 -4
  31. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/cmd_lock.py +5 -7
  32. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/cmd_merge_delta.py +8 -12
  33. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/cmd_report.py +1 -6
  34. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/cmd_split_apply.py +2 -5
  35. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/cmd_split_files.py +10 -8
  36. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/cmd_stale_files.py +8 -5
  37. source_kb-0.2.28/core/skeleton/cmd_sync_prompt.py +151 -0
  38. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/parsers/treesitter_multi.py +2 -8
  39. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/section_ops.py +1 -1
  40. {source_kb-0.2.26 → source_kb-0.2.28}/core/utils.py +45 -0
  41. {source_kb-0.2.26 → source_kb-0.2.28}/core/validators/__main__.py +74 -37
  42. source_kb-0.2.28/presets/java-spring/common-rules.md +12 -0
  43. {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/doc_types.yaml +4 -0
  44. {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-aop.md +1 -12
  45. {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-api.md +1 -4
  46. {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-architecture.md +1 -4
  47. {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-async-events.md +1 -12
  48. {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-business.md +1 -4
  49. {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-caching.md +1 -12
  50. {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-data-flow.md +1 -12
  51. {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-database-access.md +1 -12
  52. {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-deployment.md +1 -12
  53. {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-enum.md +1 -12
  54. {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-error-handling.md +1 -12
  55. {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-extension-points.md +1 -12
  56. {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-external-integrations.md +1 -12
  57. {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-messaging.md +1 -12
  58. {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-model.md +1 -4
  59. {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-observability.md +1 -4
  60. {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-public-api.md +1 -12
  61. {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-rate-limiting.md +1 -12
  62. {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-routing.md +1 -12
  63. {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-runbook.md +1 -12
  64. {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-scheduled.md +1 -12
  65. {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-scheduling.md +1 -12
  66. {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-security.md +1 -12
  67. {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-starters.md +1 -12
  68. {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-structure.md +1 -4
  69. {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-utils.md +1 -4
  70. {source_kb-0.2.26 → source_kb-0.2.28}/pyproject.toml +1 -1
  71. {source_kb-0.2.26 → source_kb-0.2.28/source_kb.egg-info}/PKG-INFO +1 -1
  72. {source_kb-0.2.26 → source_kb-0.2.28}/source_kb.egg-info/SOURCES.txt +9 -0
  73. {source_kb-0.2.26 → source_kb-0.2.28}/LICENSE +0 -0
  74. {source_kb-0.2.26 → source_kb-0.2.28}/MANIFEST.in +0 -0
  75. {source_kb-0.2.26 → source_kb-0.2.28}/README.en.md +0 -0
  76. {source_kb-0.2.26 → source_kb-0.2.28}/README.md +0 -0
  77. {source_kb-0.2.26 → source_kb-0.2.28}/cli/__init__.py +0 -0
  78. {source_kb-0.2.26 → source_kb-0.2.28}/cli/__main__.py +0 -0
  79. {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/__init__.py +0 -0
  80. {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/anchor_fix.py +0 -0
  81. {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/audit.py +0 -0
  82. {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/check_triggers.py +0 -0
  83. {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/detect.py +0 -0
  84. {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/diff_doc.py +0 -0
  85. {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/dispatch.py +0 -0
  86. {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/extract.py +0 -0
  87. {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/file_list.py +0 -0
  88. {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/history_cmd.py +0 -0
  89. {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/index.py +0 -0
  90. {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/install_skills.py +0 -0
  91. {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/jar_resolve.py +0 -0
  92. {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/lock.py +0 -0
  93. {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/merge.py +0 -0
  94. {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/merge_delta.py +0 -0
  95. {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/metadata.py +0 -0
  96. {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/module_dag.py +0 -0
  97. {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/post_merge.py +0 -0
  98. {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/query.py +0 -0
  99. {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/query_preset.py +0 -0
  100. {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/record_feedback.py +0 -0
  101. {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/render.py +0 -0
  102. {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/report.py +0 -0
  103. {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/scan_repos.py +0 -0
  104. {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/score.py +0 -0
  105. {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/setup.py +0 -0
  106. {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/split.py +0 -0
  107. {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/stale_files.py +0 -0
  108. {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/status.py +0 -0
  109. {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/validate.py +0 -0
  110. {source_kb-0.2.26 → source_kb-0.2.28}/cli/global_assets.py +0 -0
  111. {source_kb-0.2.26 → source_kb-0.2.28}/core/bootstrap.py +0 -0
  112. {source_kb-0.2.26 → source_kb-0.2.28}/core/config.py +0 -0
  113. {source_kb-0.2.26 → source_kb-0.2.28}/core/docs/section_updater.py +0 -0
  114. {source_kb-0.2.26 → source_kb-0.2.28}/core/docs/trigger_detect.py +0 -0
  115. {source_kb-0.2.26 → source_kb-0.2.28}/core/git.py +0 -0
  116. {source_kb-0.2.26 → source_kb-0.2.28}/core/history.py +0 -0
  117. {source_kb-0.2.26 → source_kb-0.2.28}/core/interfaces.py +0 -0
  118. {source_kb-0.2.26 → source_kb-0.2.28}/core/monitor/progress.py +0 -0
  119. {source_kb-0.2.26 → source_kb-0.2.28}/core/monitor/prompt_store.py +0 -0
  120. {source_kb-0.2.26 → source_kb-0.2.28}/core/preset.py +0 -0
  121. {source_kb-0.2.26 → source_kb-0.2.28}/core/preset_accessors.py +0 -0
  122. {source_kb-0.2.26 → source_kb-0.2.28}/core/preset_classify.py +0 -0
  123. {source_kb-0.2.26 → source_kb-0.2.28}/core/preset_hooks.py +0 -0
  124. {source_kb-0.2.26 → source_kb-0.2.28}/core/preset_profile.py +0 -0
  125. {source_kb-0.2.26 → source_kb-0.2.28}/core/prompt/context_manager.py +0 -0
  126. {source_kb-0.2.26 → source_kb-0.2.28}/core/prompt/response_parser.py +0 -0
  127. {source_kb-0.2.26 → source_kb-0.2.28}/core/prompt/templates.py +0 -0
  128. {source_kb-0.2.26 → source_kb-0.2.28}/core/prompt/validate_parity.py +0 -0
  129. {source_kb-0.2.26 → source_kb-0.2.28}/core/prompt/variables.py +0 -0
  130. {source_kb-0.2.26 → source_kb-0.2.28}/core/rag/bm25_index.py +0 -0
  131. {source_kb-0.2.26 → source_kb-0.2.28}/core/rag/chunker.py +0 -0
  132. {source_kb-0.2.26 → source_kb-0.2.28}/core/rag/embedder.py +0 -0
  133. {source_kb-0.2.26 → source_kb-0.2.28}/core/rag/indexer.py +0 -0
  134. {source_kb-0.2.26 → source_kb-0.2.28}/core/rag/loader.py +0 -0
  135. {source_kb-0.2.26 → source_kb-0.2.28}/core/rag/retriever.py +0 -0
  136. {source_kb-0.2.26 → source_kb-0.2.28}/core/scan_repos.py +0 -0
  137. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/anchor_fix.py +0 -0
  138. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/classify.py +0 -0
  139. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/community.py +0 -0
  140. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/cross_module_graph.py +0 -0
  141. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/dependency_graph.py +0 -0
  142. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/diff_doc.py +0 -0
  143. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/dispatch.py +0 -0
  144. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/dispatch_render.py +0 -0
  145. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/dispatch_source.py +0 -0
  146. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/extract.py +0 -0
  147. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/extract_methods.py +0 -0
  148. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/file_list.py +0 -0
  149. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/graph.py +0 -0
  150. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/impact.py +0 -0
  151. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/impact_cross_module.py +0 -0
  152. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/jar_download.py +0 -0
  153. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/jar_resolver.py +0 -0
  154. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/loader.py +0 -0
  155. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/merge.py +0 -0
  156. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/merge_delta.py +0 -0
  157. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/metadata.py +0 -0
  158. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/metadata_builders.py +0 -0
  159. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/module_dag.py +0 -0
  160. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/parsers/__init__.py +0 -0
  161. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/parsers/jqassistant.py +0 -0
  162. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/parsers/jqassistant_cypher.py +0 -0
  163. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/parsers/regex.py +0 -0
  164. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/parsers/treesitter.py +0 -0
  165. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/parsers/treesitter_java.py +0 -0
  166. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/pom_parser.py +0 -0
  167. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/post_merge.py +0 -0
  168. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/post_merge_llm.py +0 -0
  169. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/query.py +0 -0
  170. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/shard_context.py +0 -0
  171. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/split.py +0 -0
  172. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/split_cache.py +0 -0
  173. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/split_feedback.py +0 -0
  174. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/split_plan.py +0 -0
  175. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/split_plan_helpers.py +0 -0
  176. {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/split_plan_llm.py +0 -0
  177. {source_kb-0.2.26 → source_kb-0.2.28}/core/validators/__init__.py +0 -0
  178. {source_kb-0.2.26 → source_kb-0.2.28}/core/validators/consistency.py +0 -0
  179. {source_kb-0.2.26 → source_kb-0.2.28}/core/validators/coverage.py +0 -0
  180. {source_kb-0.2.26 → source_kb-0.2.28}/core/validators/density.py +0 -0
  181. {source_kb-0.2.26 → source_kb-0.2.28}/core/validators/duplicates.py +0 -0
  182. {source_kb-0.2.26 → source_kb-0.2.28}/core/validators/engine.py +0 -0
  183. {source_kb-0.2.26 → source_kb-0.2.28}/core/validators/links.py +0 -0
  184. {source_kb-0.2.26 → source_kb-0.2.28}/core/validators/sampling.py +0 -0
  185. {source_kb-0.2.26 → source_kb-0.2.28}/core/validators/score.py +0 -0
  186. {source_kb-0.2.26 → source_kb-0.2.28}/core/validators/structure.py +0 -0
  187. {source_kb-0.2.26 → source_kb-0.2.28}/examples/README.md +0 -0
  188. {source_kb-0.2.26 → source_kb-0.2.28}/examples/__init__.py +0 -0
  189. {source_kb-0.2.26 → source_kb-0.2.28}/examples/monorepo-backend/kb-project.yaml +0 -0
  190. {source_kb-0.2.26 → source_kb-0.2.28}/examples/multi-module-ecommerce/kb-project.yaml +0 -0
  191. {source_kb-0.2.26 → source_kb-0.2.28}/examples/spring-petclinic/README.md +0 -0
  192. {source_kb-0.2.26 → source_kb-0.2.28}/examples/spring-petclinic/kb-project.yaml +0 -0
  193. {source_kb-0.2.26 → source_kb-0.2.28}/presets/generic/audit_dimensions.md +0 -0
  194. {source_kb-0.2.26 → source_kb-0.2.28}/presets/generic/doc_types.yaml +0 -0
  195. {source_kb-0.2.26 → source_kb-0.2.28}/presets/generic/preset.yaml +0 -0
  196. {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/audit_dimensions.md +0 -0
  197. {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/audit_dimensions.yaml +0 -0
  198. {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/hooks.py +0 -0
  199. {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/preset.yaml +0 -0
  200. {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/README.md +0 -0
  201. {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/audit-system.md +0 -0
  202. {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-audit-api-contracts.md +0 -0
  203. {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-audit-architecture.md +0 -0
  204. {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-audit-business.md +0 -0
  205. {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-audit-data-models.md +0 -0
  206. {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-index.md +0 -0
  207. {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-sync-section.md +0 -0
  208. {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/sync-system.md +0 -0
  209. {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/workflow-extensions.md +0 -0
  210. {source_kb-0.2.26 → source_kb-0.2.28}/setup.cfg +0 -0
  211. {source_kb-0.2.26 → source_kb-0.2.28}/source_kb.egg-info/dependency_links.txt +0 -0
  212. {source_kb-0.2.26 → source_kb-0.2.28}/source_kb.egg-info/entry_points.txt +0 -0
  213. {source_kb-0.2.26 → source_kb-0.2.28}/source_kb.egg-info/requires.txt +0 -0
  214. {source_kb-0.2.26 → source_kb-0.2.28}/source_kb.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: source-kb
3
- Version: 0.2.26
3
+ Version: 0.2.28
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
@@ -0,0 +1,27 @@
1
+ """source-kb cross-graph — Query cross-module call graph."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ from pathlib import Path
7
+
8
+
9
+ def register(subparsers: argparse._SubParsersAction) -> None:
10
+ p = subparsers.add_parser("cross-graph", help="Query cross-module call graph")
11
+ p.add_argument("--knowledge-dir", required=True, help="Knowledge base directory")
12
+ p.add_argument("action", choices=["stats", "callers", "callees", "dependents", "rebuild"],
13
+ help="Cross-graph query action")
14
+ p.add_argument("--module", help="Module name (for callers/callees/dependents)")
15
+ p.add_argument("--target", help="Target class name (for callers/callees)")
16
+ p.set_defaults(func=run)
17
+
18
+
19
+ def run(args: argparse.Namespace) -> None:
20
+ from core.skeleton.cmd_cross_graph import cmd_cross_graph
21
+
22
+ cmd_cross_graph(
23
+ knowledge_dir=Path(args.knowledge_dir),
24
+ action=args.action,
25
+ module=args.module,
26
+ target=args.target,
27
+ )
@@ -0,0 +1,28 @@
1
+ """source-kb dispatch-preview — Generate dispatch plan preview (lightweight)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ from pathlib import Path
7
+
8
+
9
+ def register(subparsers: argparse._SubParsersAction) -> None:
10
+ p = subparsers.add_parser("dispatch-preview", help="Generate dispatch plan preview (lightweight)")
11
+ p.add_argument("--config", help="kb-project.yaml path")
12
+ p.add_argument("--kb", required=True, help="Knowledge base name")
13
+ p.add_argument("--module", required=True, help="Module name")
14
+ p.add_argument("--mode", default="readwrite", choices=["readwrite", "output-only"])
15
+ p.add_argument("--module-type", help="Module type override")
16
+ p.set_defaults(func=run)
17
+
18
+
19
+ def run(args: argparse.Namespace) -> None:
20
+ from core.skeleton.cmd_dispatch_preview import cmd_dispatch_preview
21
+
22
+ cmd_dispatch_preview(
23
+ config=Path(args.config) if args.config else None,
24
+ kb=args.kb,
25
+ module=args.module,
26
+ mode=args.mode or "readwrite",
27
+ module_type=args.module_type,
28
+ )
@@ -0,0 +1,27 @@
1
+ """source-kb graph — Query module call graph."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ from pathlib import Path
7
+
8
+
9
+ def register(subparsers: argparse._SubParsersAction) -> None:
10
+ p = subparsers.add_parser("graph", help="Query module call graph")
11
+ p.add_argument("--module-dir", required=True, help="Module knowledge directory")
12
+ p.add_argument("action", choices=["stats", "callers", "callees", "dependents", "dependencies"],
13
+ help="Graph query action")
14
+ p.add_argument("--target", help="Target class name for query")
15
+ p.add_argument("--depth", type=int, default=2, help="Traversal depth (default: 2)")
16
+ p.set_defaults(func=run)
17
+
18
+
19
+ def run(args: argparse.Namespace) -> None:
20
+ from core.skeleton.cmd_graph import cmd_graph
21
+
22
+ cmd_graph(
23
+ module_dir=Path(args.module_dir),
24
+ action=args.action,
25
+ target=args.target,
26
+ depth=args.depth,
27
+ )
@@ -0,0 +1,32 @@
1
+ """source-kb impact — Analyze change impact at section level."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ from pathlib import Path
7
+
8
+
9
+ def register(subparsers: argparse._SubParsersAction) -> None:
10
+ p = subparsers.add_parser("impact", help="Analyze change impact at section level")
11
+ p.add_argument("--module-dir", required=True, help="Module knowledge directory")
12
+ p.add_argument("--preset", required=True, help="Preset name")
13
+ p.add_argument("--source-cache", help="Source cache directory")
14
+ p.add_argument("--files", nargs="+", required=True, help="Changed file paths")
15
+ p.add_argument("--changes-json", help="Path to change details JSON")
16
+ p.add_argument("--include-cross-module", action="store_true", help="Enable cross-module impact")
17
+ p.add_argument("--knowledge-dir", help="Knowledge dir (required for cross-module)")
18
+ p.set_defaults(func=run)
19
+
20
+
21
+ def run(args: argparse.Namespace) -> None:
22
+ from core.skeleton.cmd_impact import cmd_impact
23
+
24
+ cmd_impact(
25
+ module_dir=Path(args.module_dir),
26
+ preset=args.preset,
27
+ source_cache=Path(args.source_cache) if args.source_cache else None,
28
+ files=args.files,
29
+ changes_json=Path(args.changes_json) if args.changes_json else None,
30
+ knowledge_dir=Path(args.knowledge_dir) if args.knowledge_dir else None,
31
+ include_cross_module=args.include_cross_module,
32
+ )
@@ -0,0 +1,22 @@
1
+ """source-kb inject-version — Inject version headers into generated docs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ from pathlib import Path
7
+
8
+
9
+ def register(subparsers: argparse._SubParsersAction) -> None:
10
+ p = subparsers.add_parser("inject-version", help="Inject version headers into generated docs")
11
+ p.add_argument("--module-dir", required=True, help="Module knowledge directory")
12
+ p.add_argument("--source-commit", default="", help="Source commit hash")
13
+ p.set_defaults(func=run)
14
+
15
+
16
+ def run(args: argparse.Namespace) -> None:
17
+ from core.skeleton.cmd_inject_version import cmd_inject_version
18
+
19
+ cmd_inject_version(
20
+ module_dir=Path(args.module_dir),
21
+ source_commit=args.source_commit or "",
22
+ )
@@ -0,0 +1,20 @@
1
+ """source-kb sync-prompt — Generate section-level sync prompts from impact JSON."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+
7
+
8
+ def register(subparsers: argparse._SubParsersAction) -> None:
9
+ p = subparsers.add_parser("sync-prompt", help="Generate section-level sync prompts from impact analysis")
10
+ p.add_argument("--module-dir", required=True, help="Module knowledge directory")
11
+ p.add_argument("--source-cache", help="Source cache directory")
12
+ p.add_argument("--impact-json", help="Path to impact analysis JSON (or stdin)")
13
+ p.add_argument("--output-dir", help="Output directory for prompt files")
14
+ p.add_argument("--force", action="store_true", help="Skip fingerprint check, regenerate all")
15
+ p.set_defaults(func=run)
16
+
17
+
18
+ def run(args: argparse.Namespace) -> None:
19
+ from core.skeleton.cmd_sync_prompt import run as _impl
20
+ _impl(args)
@@ -17,6 +17,8 @@ Layers:
17
17
  core.docs — Shared document generation
18
18
  """
19
19
 
20
+ from __future__ import annotations
21
+
20
22
  import logging
21
23
  from importlib.metadata import version, PackageNotFoundError
22
24
 
@@ -5,3 +5,5 @@ Generates cross-module summary documents in _shared/ directory:
5
5
  - cross-module-calls.md
6
6
  - error-codes.md (if applicable)
7
7
  """
8
+
9
+ from __future__ import annotations
@@ -134,7 +134,7 @@ def _generate_error_codes(shared_dir: Path, modules: list[Path]) -> bool:
134
134
  import re
135
135
 
136
136
  error_code_re = re.compile(r'^\|\s*(\w+[-_]?\d+)\s*\|')
137
- header_re = re.compile(r'^#{2,4}\s+.*错误码|error.?code', re.IGNORECASE)
137
+ header_re = re.compile(r'^#{2,4}\s+.*错误码|error.?code', re.IGNORECASE) # matches Chinese "error code" or English
138
138
 
139
139
  all_codes: list[tuple[str, str, str]] = [] # (module, code, line)
140
140
 
@@ -3,3 +3,5 @@
3
3
  Provides structured progress file read/write and heartbeat timeout detection.
4
4
  Used by both CLI (automatic heartbeat) and Agent (manual progress check).
5
5
  """
6
+
7
+ from __future__ import annotations
@@ -98,6 +98,36 @@ def cross_module_graph_path(knowledge_dir: Path) -> Path:
98
98
  return knowledge_dir / "_shared" / "cross-module-graph.json"
99
99
 
100
100
 
101
+ def dispatch_tasks_path(module_dir: Path) -> Path:
102
+ """Path to dispatch tasks manifest."""
103
+ return meta_dir(module_dir) / "dispatch-tasks.json"
104
+
105
+
106
+ def dispatch_plan_path(module_dir: Path) -> Path:
107
+ """Path to dispatch plan markdown."""
108
+ return meta_dir(module_dir) / "dispatch-plan.md"
109
+
110
+
111
+ def heartbeat_path(module_dir: Path) -> Path:
112
+ """Path to generation heartbeat file."""
113
+ return meta_dir(module_dir) / "heartbeat.txt"
114
+
115
+
116
+ def outlines_dir(module_dir: Path) -> Path:
117
+ """Path to outlines directory for two-phase generation."""
118
+ return meta_dir(module_dir) / "outlines"
119
+
120
+
121
+ def classify_cache_path(module_dir: Path) -> Path:
122
+ """Path to LLM classification cache."""
123
+ return meta_dir(module_dir) / ".llm-classify-cache.json"
124
+
125
+
126
+ def impact_result_path(module_dir: Path) -> Path:
127
+ """Path to impact analysis result."""
128
+ return meta_dir(module_dir) / "impact-result.json"
129
+
130
+
101
131
  def shard_file_list_path(module_dir: Path, doc_type: str, shard_name: str) -> Path:
102
132
  """File list path for a shard: .meta/file-lists/{doc_type}-{shard_name}.txt"""
103
133
  return file_list_dir(module_dir) / f"{doc_type}-{shard_name}.txt"
@@ -5,3 +5,5 @@ Sub-modules:
5
5
  content — Source reading, skeleton reading, context building
6
6
  templates — PromptTemplate dataclass + registry
7
7
  """
8
+
9
+ from __future__ import annotations
@@ -14,78 +14,81 @@ from pathlib import Path
14
14
  sys.stdout.reconfigure(encoding="utf-8")
15
15
 
16
16
 
17
- def cmd_render(args: argparse.Namespace) -> None:
17
+ def cmd_render(
18
+ module: str,
19
+ kb: str,
20
+ doc_type: str,
21
+ config: Path | None = None,
22
+ template: str | None = None,
23
+ mode: str = "readwrite",
24
+ output: Path | None = None,
25
+ extra: list[str] | None = None,
26
+ ) -> None:
18
27
  """Render a sub-agent prompt from template."""
19
28
  from core.config import load_config
20
29
  from core.preset import load_preset
21
30
  from core.prompt.renderer import render_prompt
22
31
 
23
- config = load_config(Path(args.config) if args.config else None)
24
- kb_config = config.get_kb(args.kb)
32
+ cfg = load_config(config)
33
+ kb_config = cfg.get_kb(kb)
25
34
  preset_name = kb_config.get("preset", "generic")
26
35
  preset = load_preset(preset_name)
27
36
 
28
- # In Agent mode, use ReferenceAssembler (paths only, no inline content)
29
37
  from core.prompt.variables import ReferencePromptAssembler
30
38
  assembler = ReferencePromptAssembler(project_root=Path(".").resolve(), preset=preset)
31
39
 
32
- # Resolve template: explicit --template takes priority, otherwise lookup from doc_types.yaml
33
- template_name = args.template
40
+ template_name = template
34
41
  if not template_name:
35
42
  doc_types = preset.get("doc_types", {})
36
- dt_cfg = doc_types.get(args.doc_type, {})
43
+ dt_cfg = doc_types.get(doc_type, {})
37
44
  template_name = dt_cfg.get("template")
38
45
  if not template_name:
39
- print(f"Error: no template mapping for doc-type '{args.doc_type}' in {preset_name}/doc_types.yaml. "
46
+ print(f"Error: no template mapping for doc-type '{doc_type}' in {preset_name}/doc_types.yaml. "
40
47
  f"Specify --template explicitly.", file=sys.stderr)
41
48
  sys.exit(1)
42
49
 
43
- # Find template
44
50
  template_path = _find_template(template_name, preset_name)
45
51
  if not template_path:
46
52
  print(f"Error: template not found: {template_name}", file=sys.stderr)
47
53
  sys.exit(1)
48
54
 
49
- # Parse extras
50
55
  extras = {}
51
- if args.extra:
52
- for item in args.extra:
56
+ if extra:
57
+ for item in extra:
53
58
  if "=" in item:
54
59
  k, v = item.split("=", 1)
55
60
  extras[k] = v
56
61
 
57
- # Load execution snippet
58
62
  execution_snippet = ""
59
- if args.mode == "readwrite":
63
+ if mode == "readwrite":
60
64
  snippet_path = _find_execution_snippet("readwrite.md")
61
65
  if snippet_path:
62
66
  execution_snippet = snippet_path.read_text(encoding="utf-8")
63
- elif args.mode == "output-only":
67
+ elif mode == "output-only":
64
68
  snippet_path = _find_execution_snippet("output-only.md")
65
69
  if snippet_path:
66
70
  execution_snippet = snippet_path.read_text(encoding="utf-8")
67
71
 
68
72
  rendered = render_prompt(
69
73
  template_path=template_path,
70
- config=config.raw,
71
- kb_name=args.kb,
72
- module_name=args.module,
73
- doc_type=args.doc_type,
74
+ config=cfg.raw,
75
+ kb_name=kb,
76
+ module_name=module,
77
+ doc_type=doc_type,
74
78
  assembler=assembler,
75
79
  extras=extras,
76
80
  execution_snippet=execution_snippet,
77
81
  preset=preset,
78
82
  )
79
83
 
80
- if args.output:
81
- Path(args.output).parent.mkdir(parents=True, exist_ok=True)
82
- Path(args.output).write_text(rendered, encoding="utf-8")
83
- print(f"Rendered to: {args.output} ({len(rendered)} chars)")
84
+ if output:
85
+ output.parent.mkdir(parents=True, exist_ok=True)
86
+ output.write_text(rendered, encoding="utf-8")
87
+ print(f"Rendered to: {output} ({len(rendered)} chars)")
84
88
  else:
85
- # Default: write to .meta/prompts/{doc_type}.md to avoid flooding stdout
86
- meta_prompts = Path(f"knowledge/{args.module}/.meta/prompts")
89
+ meta_prompts = Path(f"knowledge/{module}/.meta/prompts")
87
90
  meta_prompts.mkdir(parents=True, exist_ok=True)
88
- out_path = meta_prompts / f"{args.doc_type}.md"
91
+ out_path = meta_prompts / f"{doc_type}.md"
89
92
  out_path.write_text(rendered, encoding="utf-8")
90
93
  print(f"Rendered to: {out_path} ({len(rendered)} chars)")
91
94
 
@@ -121,7 +124,7 @@ def _find_execution_snippet(filename: str) -> Path | None:
121
124
  return None
122
125
 
123
126
 
124
- def main():
127
+ def main() -> None:
125
128
  import warnings
126
129
  warnings.warn(
127
130
  "Use 'source-kb render' instead of 'python -m core.prompt render'",
@@ -151,7 +154,16 @@ def main():
151
154
  sys.exit(1)
152
155
 
153
156
  if args.command == "render":
154
- cmd_render(args)
157
+ cmd_render(
158
+ module=args.module,
159
+ kb=args.kb,
160
+ doc_type=args.doc_type,
161
+ config=Path(args.config) if args.config else None,
162
+ template=args.template,
163
+ mode=args.mode,
164
+ output=Path(args.output) if args.output else None,
165
+ extra=args.extra,
166
+ )
155
167
  elif args.command == "validate-parity":
156
168
  from core.prompt.validate_parity import main as parity_main
157
169
  # Re-parse with validate_parity's own parser
@@ -95,10 +95,18 @@ def build_prior_docs_context(
95
95
  remaining = max_chars
96
96
  for dep_type in dep_docs:
97
97
  filename = _doc_type_to_filename(dep_type)
98
- doc_path = module_dir / filename
99
- if not doc_path.exists():
100
- continue
101
- summary = _extract_doc_summary(doc_path)
98
+ # Prefer pre-computed summary (fast, no full-file read)
99
+ summary_file = module_dir / ".meta" / "summaries" / f"{dep_type}.txt"
100
+ if summary_file.exists():
101
+ try:
102
+ summary = summary_file.read_text(encoding="utf-8").strip()
103
+ except (OSError, UnicodeDecodeError):
104
+ summary = ""
105
+ else:
106
+ doc_path = module_dir / filename
107
+ if not doc_path.exists():
108
+ continue
109
+ summary = _extract_doc_summary(doc_path)
102
110
  if not summary:
103
111
  continue
104
112
  section = f"### {filename}\n{summary}"
@@ -293,6 +301,11 @@ def _extract_doc_summary(path: Path, max_lines: int = 15) -> str:
293
301
  content = path.read_text(encoding="utf-8")
294
302
  except (OSError, UnicodeDecodeError):
295
303
  return ""
304
+ return _summarize_content(content)
305
+
306
+
307
+ def _summarize_content(content: str) -> str:
308
+ """Extract title + heading list from document content string."""
296
309
  lines = content.splitlines()
297
310
  title = ""
298
311
  for line in lines[:5]:
@@ -308,6 +321,19 @@ def _extract_doc_summary(path: Path, max_lines: int = 15) -> str:
308
321
  return "\n".join(parts)
309
322
 
310
323
 
324
+ def write_doc_summary(module_dir: Path, doc_type: str, content: str) -> None:
325
+ """Write pre-computed summary for a generated document.
326
+
327
+ Stored at .meta/summaries/{doc_type}.txt for fast lookup by build_prior_docs_context.
328
+ """
329
+ summary = _summarize_content(content)
330
+ if not summary:
331
+ return
332
+ summaries_dir = module_dir / ".meta" / "summaries"
333
+ summaries_dir.mkdir(parents=True, exist_ok=True)
334
+ (summaries_dir / f"{doc_type}.txt").write_text(summary, encoding="utf-8")
335
+
336
+
311
337
  def _extract_shard_summary(path: Path) -> str:
312
338
  """Extract heading list from a shard document."""
313
339
  try:
@@ -89,14 +89,22 @@ def render_prompt(
89
89
  # Delegate content assembly to the injected strategy
90
90
  file_list_override = (extras or {}).get("file_list_override")
91
91
  file_list = assembler.resolve_file_list(module_dir, doc_type, file_list_override=file_list_override)
92
+ skeleton_content = assembler.resolve_skeleton_content(module_dir)
93
+
94
+ # Budget-based source reading: estimate fixed overhead, cap source to remaining budget
95
+ source_budget = _compute_source_budget(template, skeleton_content, assembler)
96
+ budget_kwargs = {"budget_bytes": source_budget} if source_budget is not None else {}
97
+
92
98
  if file_list_override and Path(file_list_override).exists():
93
99
  override_content = Path(file_list_override).read_text(encoding="utf-8").strip()
94
100
  source_content = assembler.resolve_source_content_from_paths(
95
- module_dir, doc_type, source_cache, override_content.splitlines()
101
+ module_dir, doc_type, source_cache, override_content.splitlines(),
102
+ **budget_kwargs,
96
103
  )
97
104
  else:
98
- source_content = assembler.resolve_source_content(module_dir, doc_type, source_cache)
99
- skeleton_content = assembler.resolve_skeleton_content(module_dir)
105
+ source_content = assembler.resolve_source_content(
106
+ module_dir, doc_type, source_cache, **budget_kwargs,
107
+ )
100
108
 
101
109
  # Compute metadata
102
110
  from core.skeleton.metadata import load_pregenerated
@@ -172,6 +180,13 @@ def render_prompt(
172
180
  if prior:
173
181
  variables["prior_docs_context"] = prior
174
182
 
183
+ # Load shared common-rules snippet from preset directory
184
+ preset_dir = preset.get("_preset_dir", "") if preset else ""
185
+ if preset_dir:
186
+ common_rules_path = Path(preset_dir) / "common-rules.md"
187
+ if common_rules_path.exists():
188
+ variables["common_rules"] = common_rules_path.read_text(encoding="utf-8").strip()
189
+
175
190
  # Merge extras (user-provided overrides)
176
191
  variables.update({k: v for k, v in extras.items() if not k.startswith("__")})
177
192
 
@@ -234,3 +249,40 @@ def _find_repo(config: dict, kb_name: str, module_name: str) -> dict:
234
249
  if repo["name"] == module_name:
235
250
  return repo
236
251
  return {"name": module_name}
252
+
253
+
254
+ def _compute_source_budget(template: str, skeleton_content: str, assembler) -> int | None:
255
+ """Estimate remaining byte budget for source content.
256
+
257
+ Uses context_manager.estimate_tokens to calculate how much of the 128K context
258
+ is already consumed by template + skeleton, then returns remaining bytes.
259
+ Returns None if budget is not constraining (plenty of room).
260
+ """
261
+ from core.prompt.context_manager import estimate_tokens
262
+
263
+ max_context_tokens = 128_000
264
+ reserve_output_tokens = 16_000
265
+ available_tokens = max_context_tokens - reserve_output_tokens
266
+
267
+ template_tokens = estimate_tokens(template)
268
+ skeleton_tokens = estimate_tokens(skeleton_content)
269
+ overhead_tokens = 2000 # metadata, prior_docs, file_list, etc.
270
+
271
+ used_tokens = template_tokens + skeleton_tokens + overhead_tokens
272
+ remaining_tokens = available_tokens - used_tokens
273
+
274
+ if remaining_tokens <= 0:
275
+ return 10_000 # minimum floor
276
+
277
+ # Convert tokens to bytes (conservative: ~3 bytes per token for code)
278
+ remaining_bytes = remaining_tokens * 3
279
+
280
+ # Only constrain if budget is tighter than the assembler's own max
281
+ if not hasattr(assembler, '_max_source'):
282
+ return None
283
+ try:
284
+ if remaining_bytes >= assembler._max_source:
285
+ return None
286
+ except TypeError:
287
+ return None
288
+ return remaining_bytes
@@ -4,6 +4,8 @@ Provides document loading, chunking, index building, and retrieval.
4
4
  Embedding backend is configurable via Config (ollama, openai, dashscope, chromadb-default).
5
5
  """
6
6
 
7
+ from __future__ import annotations
8
+
7
9
  from core.rag.loader import Document, load_documents
8
10
  from core.rag.chunker import Chunk, chunk_documents
9
11
  from core.rag.embedder import get_embeddings
@@ -16,17 +16,17 @@ from pathlib import Path
16
16
  sys.stdout.reconfigure(encoding="utf-8")
17
17
 
18
18
 
19
- def cmd_search(args: argparse.Namespace) -> None:
19
+ def cmd_search(query: str, kb: str, config: Path | None = None) -> None:
20
20
  """Search knowledge base."""
21
21
  from core.config import load_config, find_config
22
22
  from core.rag.retriever import retrieve
23
23
 
24
- config_path = Path(args.config) if args.config else find_config()
25
- config = load_config(config_path)
24
+ config_path = config if config else find_config()
25
+ cfg = load_config(config_path)
26
26
 
27
- results = retrieve(args.query, config, kb_name=args.kb)
27
+ results = retrieve(query, cfg, kb_name=kb)
28
28
  if not results:
29
- print(f"No results for '{args.query}'")
29
+ print(f"No results for '{query}'")
30
30
  print(json.dumps({"status": "ok", "matches": 0}, ensure_ascii=False), file=sys.stderr)
31
31
  return
32
32
 
@@ -44,16 +44,16 @@ def cmd_search(args: argparse.Namespace) -> None:
44
44
  print(json.dumps({"status": "ok", "matches": len(results)}, ensure_ascii=False), file=sys.stderr)
45
45
 
46
46
 
47
- def cmd_index(args: argparse.Namespace) -> None:
47
+ def cmd_index(kb: str, config: Path | None = None) -> None:
48
48
  """Build/rebuild full vector index."""
49
49
  from core.config import load_config, find_config
50
50
  from core.rag.loader import load_documents
51
51
  from core.rag.chunker import chunk_documents
52
52
  from core.rag.indexer import build_index
53
53
 
54
- config_path = Path(args.config) if args.config else find_config()
55
- config = load_config(config_path)
56
- kb_cfg = config.get_kb(args.kb)
54
+ config_path = config if config else find_config()
55
+ cfg = load_config(config_path)
56
+ kb_cfg = cfg.get_kb(kb)
57
57
  knowledge_dir = kb_cfg["knowledge_dir"]
58
58
  collection_name = kb_cfg["collection"]
59
59
 
@@ -65,30 +65,30 @@ def cmd_index(args: argparse.Namespace) -> None:
65
65
 
66
66
  chunks = chunk_documents(docs)
67
67
  print(f"Indexing {len(docs)} docs, {len(chunks)} chunks...")
68
- build_index(chunks, collection_name, config, kb_name=args.kb)
68
+ build_index(chunks, collection_name, cfg, kb_name=kb)
69
69
  print(f"Index built: {len(chunks)} chunks -> collection '{collection_name}'")
70
70
  print(json.dumps({"status": "ok", "docs": len(docs), "chunks": len(chunks),
71
71
  "collection": collection_name}, ensure_ascii=False), file=sys.stderr)
72
72
 
73
73
 
74
- def cmd_rebuild(args: argparse.Namespace) -> None:
74
+ def cmd_rebuild(kb: str, config: Path | None = None, module: str | None = None, files: list[str] | None = None) -> None:
75
75
  """Incremental rebuild — re-index only specified files."""
76
76
  from core.config import load_config, find_config
77
77
  from core.rag.loader import load_documents
78
78
  from core.rag.chunker import chunk_documents
79
79
  from core.rag.indexer import build_index
80
80
 
81
- config_path = Path(args.config) if args.config else find_config()
82
- config = load_config(config_path)
83
- kb_cfg = config.get_kb(args.kb)
81
+ config_path = config if config else find_config()
82
+ cfg = load_config(config_path)
83
+ kb_cfg = cfg.get_kb(kb)
84
84
  knowledge_dir = Path(kb_cfg["knowledge_dir"])
85
85
  collection_name = kb_cfg["collection"]
86
86
 
87
- files = args.files or []
88
- if files:
89
- docs = load_documents(knowledge_dir, module=args.module, files=files)
87
+ resolved_files = files or []
88
+ if resolved_files:
89
+ docs = load_documents(knowledge_dir, module=module, files=resolved_files)
90
90
  else:
91
- docs = load_documents(knowledge_dir, module=args.module)
91
+ docs = load_documents(knowledge_dir, module=module)
92
92
 
93
93
  if not docs:
94
94
  print(f"No documents to rebuild")
@@ -97,13 +97,13 @@ def cmd_rebuild(args: argparse.Namespace) -> None:
97
97
 
98
98
  chunks = chunk_documents(docs)
99
99
  print(f"Rebuilding {len(docs)} docs, {len(chunks)} chunks...")
100
- build_index(chunks, collection_name, config, kb_name=args.kb, module=args.module, files=args.files or None)
100
+ build_index(chunks, collection_name, cfg, kb_name=kb, module=module, files=resolved_files or None)
101
101
  print(f"Rebuilt: {len(chunks)} chunks -> collection '{collection_name}'")
102
102
  print(json.dumps({"status": "ok", "docs": len(docs), "chunks": len(chunks)},
103
103
  ensure_ascii=False), file=sys.stderr)
104
104
 
105
105
 
106
- def main():
106
+ def main() -> None:
107
107
  parser = argparse.ArgumentParser(prog="python -m core.rag", description="RAG tools")
108
108
  parser.add_argument("--config", help="kb-project.yaml path (auto-detected)")
109
109
  sub = parser.add_subparsers(dest="command")
@@ -125,8 +125,13 @@ def main():
125
125
  parser.print_help()
126
126
  sys.exit(1)
127
127
 
128
- commands = {"search": cmd_search, "index": cmd_index, "rebuild": cmd_rebuild}
129
- commands[args.command](args)
128
+ config_path = Path(args.config) if args.config else None
129
+ if args.command == "search":
130
+ cmd_search(query=args.query, kb=args.kb, config=config_path)
131
+ elif args.command == "index":
132
+ cmd_index(kb=args.kb, config=config_path)
133
+ elif args.command == "rebuild":
134
+ cmd_rebuild(kb=args.kb, config=config_path, module=args.module, files=args.files)
130
135
 
131
136
 
132
137
  if __name__ == "__main__":
@@ -9,3 +9,5 @@ Sub-modules:
9
9
  metadata — Global metadata pre-generation
10
10
  merge — Shard merge (fast) + post-merge refinement
11
11
  """
12
+
13
+ from __future__ import annotations