source-kb 0.2.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. cli/__init__.py +50 -0
  2. cli/__main__.py +5 -0
  3. cli/commands/__init__.py +1 -0
  4. cli/commands/anchor_fix.py +47 -0
  5. cli/commands/diff_doc.py +52 -0
  6. cli/commands/dispatch.py +77 -0
  7. cli/commands/extract.py +72 -0
  8. cli/commands/file_list.py +74 -0
  9. cli/commands/index.py +84 -0
  10. cli/commands/lock.py +89 -0
  11. cli/commands/merge.py +60 -0
  12. cli/commands/merge_delta.py +19 -0
  13. cli/commands/metadata.py +24 -0
  14. cli/commands/pipeline.py +45 -0
  15. cli/commands/post_merge.py +43 -0
  16. cli/commands/query.py +52 -0
  17. cli/commands/render.py +101 -0
  18. cli/commands/scan_repos.py +46 -0
  19. cli/commands/setup.py +94 -0
  20. cli/commands/split.py +196 -0
  21. cli/commands/stale_files.py +98 -0
  22. cli/commands/validate.py +191 -0
  23. core/__init__.py +32 -0
  24. core/config.py +261 -0
  25. core/docs/__init__.py +7 -0
  26. core/docs/section_updater.py +286 -0
  27. core/docs/shared.py +149 -0
  28. core/git.py +294 -0
  29. core/interfaces.py +249 -0
  30. core/monitor/__init__.py +5 -0
  31. core/monitor/progress.py +83 -0
  32. core/monitor/prompt_store.py +49 -0
  33. core/paths.py +141 -0
  34. core/preset.py +237 -0
  35. core/preset_accessors.py +202 -0
  36. core/preset_classify.py +132 -0
  37. core/preset_hooks.py +129 -0
  38. core/preset_profile.py +89 -0
  39. core/prompt/__init__.py +7 -0
  40. core/prompt/__main__.py +147 -0
  41. core/prompt/content.py +320 -0
  42. core/prompt/context_manager.py +164 -0
  43. core/prompt/renderer.py +236 -0
  44. core/prompt/response_parser.py +274 -0
  45. core/prompt/templates.py +357 -0
  46. core/prompt/validate_parity.py +162 -0
  47. core/prompt/variables.py +339 -0
  48. core/rag/__init__.py +22 -0
  49. core/rag/__main__.py +136 -0
  50. core/rag/bm25_index.py +268 -0
  51. core/rag/chunker.py +273 -0
  52. core/rag/embedder.py +151 -0
  53. core/rag/indexer.py +292 -0
  54. core/rag/loader.py +89 -0
  55. core/rag/retriever.py +82 -0
  56. core/skeleton/__init__.py +11 -0
  57. core/skeleton/__main__.py +934 -0
  58. core/skeleton/anchor_fix.py +250 -0
  59. core/skeleton/classify.py +331 -0
  60. core/skeleton/cmd_anchor_fix.py +43 -0
  61. core/skeleton/cmd_diff_doc.py +44 -0
  62. core/skeleton/cmd_lock.py +87 -0
  63. core/skeleton/cmd_merge_delta.py +41 -0
  64. core/skeleton/community.py +233 -0
  65. core/skeleton/dependency_graph.py +306 -0
  66. core/skeleton/diff_doc.py +248 -0
  67. core/skeleton/dispatch.py +273 -0
  68. core/skeleton/dispatch_render.py +319 -0
  69. core/skeleton/dispatch_source.py +111 -0
  70. core/skeleton/extract.py +218 -0
  71. core/skeleton/extract_methods.py +298 -0
  72. core/skeleton/file_list.py +239 -0
  73. core/skeleton/impact.py +278 -0
  74. core/skeleton/jar_download.py +177 -0
  75. core/skeleton/jar_resolver.py +186 -0
  76. core/skeleton/loader.py +162 -0
  77. core/skeleton/merge.py +278 -0
  78. core/skeleton/merge_delta.py +229 -0
  79. core/skeleton/metadata.py +96 -0
  80. core/skeleton/metadata_builders.py +264 -0
  81. core/skeleton/module_dag.py +330 -0
  82. core/skeleton/parsers/__init__.py +71 -0
  83. core/skeleton/parsers/jqassistant.py +300 -0
  84. core/skeleton/parsers/jqassistant_cypher.py +225 -0
  85. core/skeleton/parsers/regex.py +171 -0
  86. core/skeleton/parsers/treesitter.py +324 -0
  87. core/skeleton/parsers/treesitter_java.py +284 -0
  88. core/skeleton/parsers/treesitter_multi.py +289 -0
  89. core/skeleton/pom_parser.py +299 -0
  90. core/skeleton/post_merge.py +295 -0
  91. core/skeleton/post_merge_llm.py +82 -0
  92. core/skeleton/query.py +195 -0
  93. core/skeleton/shard_context.py +177 -0
  94. core/skeleton/split.py +180 -0
  95. core/skeleton/split_cache.py +107 -0
  96. core/skeleton/split_feedback.py +174 -0
  97. core/skeleton/split_plan.py +219 -0
  98. core/skeleton/split_plan_helpers.py +305 -0
  99. core/skeleton/split_plan_llm.py +274 -0
  100. core/utils.py +135 -0
  101. core/validators/__init__.py +65 -0
  102. core/validators/__main__.py +215 -0
  103. core/validators/consistency.py +203 -0
  104. core/validators/coverage.py +171 -0
  105. core/validators/duplicates.py +76 -0
  106. core/validators/engine.py +224 -0
  107. core/validators/links.py +76 -0
  108. core/validators/sampling.py +169 -0
  109. core/validators/structure.py +144 -0
  110. engine/__init__.py +7 -0
  111. engine/assembler.py +231 -0
  112. engine/confirm.py +65 -0
  113. engine/dedup.py +106 -0
  114. engine/main.py +211 -0
  115. engine/pipeline/__init__.py +163 -0
  116. engine/pipeline/recovery.py +250 -0
  117. engine/pipeline/steps/__init__.py +23 -0
  118. engine/pipeline/steps/audit.py +220 -0
  119. engine/pipeline/steps/audit_apply.py +195 -0
  120. engine/pipeline/steps/audit_helpers.py +155 -0
  121. engine/pipeline/steps/classify_llm.py +236 -0
  122. engine/pipeline/steps/classify_prompt.py +223 -0
  123. engine/pipeline/steps/finalize.py +160 -0
  124. engine/pipeline/steps/generate.py +169 -0
  125. engine/pipeline/steps/generate_batch.py +197 -0
  126. engine/pipeline/steps/generate_recovery.py +170 -0
  127. engine/pipeline/steps/llm_plan_split.py +253 -0
  128. engine/pipeline/steps/lock.py +64 -0
  129. engine/pipeline/steps/preflight.py +237 -0
  130. engine/pipeline/steps/preflight_adjust.py +147 -0
  131. engine/pipeline/steps/pregenerate.py +130 -0
  132. engine/pipeline/steps/quality.py +81 -0
  133. engine/pipeline/steps/skeleton.py +149 -0
  134. engine/pipeline/steps/source.py +163 -0
  135. engine/pipeline/steps/sync.py +117 -0
  136. engine/pipeline/steps/sync_finalize.py +237 -0
  137. engine/pipeline/steps/sync_update.py +341 -0
  138. engine/pipelines.py +91 -0
  139. engine/runner.py +335 -0
  140. engine/strategies/__init__.py +86 -0
  141. engine/strategies/api.py +128 -0
  142. engine/strategies/delegated.py +50 -0
  143. engine/strategies/dryrun.py +25 -0
  144. engine/two_phase.py +143 -0
  145. mcp_server/__init__.py +73 -0
  146. mcp_server/__main__.py +5 -0
  147. mcp_server/tools/__init__.py +1 -0
  148. mcp_server/tools/config.py +63 -0
  149. mcp_server/tools/discovery.py +276 -0
  150. mcp_server/tools/generation.py +184 -0
  151. mcp_server/tools/planning.py +144 -0
  152. mcp_server/tools/source.py +175 -0
  153. mcp_server/tools/validation.py +140 -0
  154. mcp_server/tools/workflow.py +166 -0
  155. mcp_server/workflow_loader.py +204 -0
  156. presets/generic/audit_dimensions.md +132 -0
  157. presets/generic/doc_types.yaml +152 -0
  158. presets/generic/preset.yaml +115 -0
  159. presets/java-spring/audit_dimensions.md +228 -0
  160. presets/java-spring/audit_dimensions.yaml +203 -0
  161. presets/java-spring/doc_types.yaml +269 -0
  162. presets/java-spring/hooks.py +122 -0
  163. presets/java-spring/preset.yaml +341 -0
  164. presets/java-spring/templates/README.md +34 -0
  165. presets/java-spring/templates/audit-system.md +15 -0
  166. presets/java-spring/templates/subagent-aop.md +105 -0
  167. presets/java-spring/templates/subagent-api.md +63 -0
  168. presets/java-spring/templates/subagent-architecture.md +111 -0
  169. presets/java-spring/templates/subagent-async-events.md +107 -0
  170. presets/java-spring/templates/subagent-audit-api-contracts.md +40 -0
  171. presets/java-spring/templates/subagent-audit-architecture.md +38 -0
  172. presets/java-spring/templates/subagent-audit-business.md +40 -0
  173. presets/java-spring/templates/subagent-audit-data-models.md +40 -0
  174. presets/java-spring/templates/subagent-business.md +129 -0
  175. presets/java-spring/templates/subagent-caching.md +75 -0
  176. presets/java-spring/templates/subagent-database-access.md +114 -0
  177. presets/java-spring/templates/subagent-enum.md +75 -0
  178. presets/java-spring/templates/subagent-error-handling.md +91 -0
  179. presets/java-spring/templates/subagent-external-integrations.md +80 -0
  180. presets/java-spring/templates/subagent-index.md +122 -0
  181. presets/java-spring/templates/subagent-messaging.md +97 -0
  182. presets/java-spring/templates/subagent-model.md +88 -0
  183. presets/java-spring/templates/subagent-observability.md +91 -0
  184. presets/java-spring/templates/subagent-scheduled.md +81 -0
  185. presets/java-spring/templates/subagent-security.md +102 -0
  186. presets/java-spring/templates/subagent-structure.md +101 -0
  187. presets/java-spring/templates/subagent-sync-section.md +34 -0
  188. presets/java-spring/templates/subagent-utils.md +73 -0
  189. presets/java-spring/templates/sync-system.md +8 -0
  190. presets/java-spring/workflow-extensions.md +112 -0
  191. skills/__init__.py +1 -0
  192. skills/_shared/README.md +30 -0
  193. skills/_shared/doc-coverage-shared.md +134 -0
  194. skills/_shared/doc-quality-standard.md +1058 -0
  195. skills/_shared/doc-subagent-rules.md +762 -0
  196. skills/_shared/windows-compat.md +89 -0
  197. skills/kb-audit/SKILL.md +52 -0
  198. skills/kb-audit/rules.md +88 -0
  199. skills/kb-audit/steps/step-01-prepare.md +75 -0
  200. skills/kb-audit/steps/step-02-audit.md +96 -0
  201. skills/kb-audit/steps/step-03-verify.md +65 -0
  202. skills/kb-audit/steps/step-04-report.md +64 -0
  203. skills/kb-init/SKILL.md +146 -0
  204. skills/kb-init/rules.md +187 -0
  205. skills/kb-init/steps/step-01-scope.md +62 -0
  206. skills/kb-init/steps/step-02-source.md +410 -0
  207. skills/kb-init/steps/step-03-generate.md +307 -0
  208. skills/kb-init/steps/step-04-quality.md +92 -0
  209. skills/kb-init/steps/step-05-finalize.md +132 -0
  210. skills/kb-init/templates/core/execution-modes.md +29 -0
  211. skills/kb-init/templates/core/output-only.md +4 -0
  212. skills/kb-init/templates/core/readwrite.md +33 -0
  213. skills/kb-search/SKILL.md +138 -0
  214. skills/kb-search/rules.md +64 -0
  215. skills/kb-sync/SKILL.md +43 -0
  216. skills/kb-sync/rules.md +70 -0
  217. skills/kb-sync/scripts/rebuild_module.py +91 -0
  218. skills/kb-sync/scripts/scan_repos.py +687 -0
  219. skills/kb-sync/steps/step-01-detect.md +72 -0
  220. skills/kb-sync/steps/step-02-update.md +71 -0
  221. skills/kb-sync/steps/step-03-verify.md +47 -0
  222. skills/kb-sync/steps/step-04-finalize.md +52 -0
  223. source_kb-0.2.2.dist-info/METADATA +194 -0
  224. source_kb-0.2.2.dist-info/RECORD +228 -0
  225. source_kb-0.2.2.dist-info/WHEEL +5 -0
  226. source_kb-0.2.2.dist-info/entry_points.txt +3 -0
  227. source_kb-0.2.2.dist-info/licenses/LICENSE +21 -0
  228. source_kb-0.2.2.dist-info/top_level.txt +6 -0
@@ -0,0 +1,339 @@
1
+ """Prompt variables computation — transforms content into template-ready strings.
2
+
3
+ Computes all template variables needed for prompt rendering. This module is
4
+ responsible for *formatting* (converting raw data into template-usable strings),
5
+ while content.py handles *reading* (I/O from files).
6
+
7
+ No CLI I/O, no LLM calls.
8
+
9
+ Usage:
10
+ from core.prompt.variables import PromptVariables, compute_variables
11
+
12
+ variables = compute_variables(
13
+ module_dir=module_dir,
14
+ doc_type="business-logic",
15
+ config=config,
16
+ kb_name="point",
17
+ module_name="point-base",
18
+ )
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import logging
24
+ from dataclasses import dataclass, field
25
+ from pathlib import Path
26
+ from typing import Any
27
+
28
+ from core.interfaces import PromptAssembler
29
+
30
+ logger = logging.getLogger(__name__)
31
+
32
+
33
+ # ---------------------------------------------------------------------------
34
+ # PromptVariables dataclass
35
+ # ---------------------------------------------------------------------------
36
+
37
+
38
+ @dataclass
39
+ class PromptVariables:
40
+ """All variables needed for prompt template rendering."""
41
+
42
+ module_name: str = ""
43
+ module_description: str = ""
44
+ module_dir: str = ""
45
+ doc_type: str = ""
46
+ source_cache_path: str = ""
47
+ output_path: str = ""
48
+ file_list: str = ""
49
+ source_content: str = ""
50
+ skeleton_content: str = ""
51
+ high_methods: str = ""
52
+ generated_docs: str = ""
53
+ sibling_modules: str = ""
54
+ global_metadata: str = ""
55
+ prior_docs_context: str = ""
56
+ shard_context: str = ""
57
+ branch: str = "main"
58
+ module_type: str = "service"
59
+ extras: dict[str, str] = field(default_factory=dict)
60
+
61
+ def to_dict(self) -> dict[str, str]:
62
+ """Convert to flat dict for template substitution."""
63
+ result = {
64
+ "module_name": self.module_name,
65
+ "module_description": self.module_description,
66
+ "module_dir": self.module_dir,
67
+ "doc_type": self.doc_type,
68
+ "source_cache_path": self.source_cache_path,
69
+ "output_path": self.output_path,
70
+ "file_list": self.file_list,
71
+ "source_content": self.source_content,
72
+ "skeleton_content": self.skeleton_content,
73
+ "high_methods": self.high_methods,
74
+ "generated_docs": self.generated_docs,
75
+ "generated_docs_files": self.generated_docs,
76
+ "sibling_modules": self.sibling_modules,
77
+ "global_metadata": self.global_metadata,
78
+ "prior_docs_context": self.prior_docs_context,
79
+ "shard_context": self.shard_context,
80
+ "branch": self.branch,
81
+ "module_type": self.module_type,
82
+ }
83
+ result.update(self.extras)
84
+ return result
85
+
86
+
87
+ # ---------------------------------------------------------------------------
88
+ # Variable computation
89
+ # ---------------------------------------------------------------------------
90
+
91
+
92
+ def compute_variables(
93
+ module_dir: Path,
94
+ doc_type: str,
95
+ config: dict[str, Any],
96
+ kb_name: str,
97
+ module_name: str,
98
+ source_cache: Path | None = None,
99
+ preset: dict[str, Any] | None = None,
100
+ extras: dict[str, str] | None = None,
101
+ shard_context: str = "",
102
+ ) -> PromptVariables:
103
+ """Compute all prompt variables from config and file system state.
104
+
105
+ Args:
106
+ module_dir: Module knowledge directory
107
+ doc_type: Document type key
108
+ config: Full kb-project.yaml config dict
109
+ kb_name: Knowledge base name
110
+ module_name: Module name
111
+ source_cache: Source code cache directory (auto-resolved if None)
112
+ preset: Preset config dict (optional)
113
+ extras: Additional variables to inject
114
+ shard_context: Pre-computed shard context string
115
+
116
+ Returns:
117
+ PromptVariables with all fields populated
118
+ """
119
+ from core.prompt.content import (
120
+ compute_high_methods,
121
+ scan_generated_docs,
122
+ compute_sibling_modules,
123
+ build_prior_docs_context,
124
+ )
125
+ from core.skeleton.metadata import load_pregenerated
126
+
127
+ kb_config = config["knowledge_bases"][kb_name]
128
+ repo_info = _find_repo(config, kb_name, module_name)
129
+
130
+ # Resolve source_cache if not provided
131
+ if source_cache is None:
132
+ source = kb_config.get("source", {})
133
+ base_dir = Path(config.get("_config_dir", ".")).resolve()
134
+ cache_dir = Path(source.get("cache_dir", "./.source-cache"))
135
+ if not cache_dir.is_absolute():
136
+ cache_dir = (base_dir / cache_dir).resolve()
137
+ if source.get("structure") == "monorepo":
138
+ repo_name = source.get("repo_name", "repo")
139
+ module_path = repo_info.get("path", module_name)
140
+ source_cache = cache_dir / repo_name / module_path
141
+ else:
142
+ source_cache = cache_dir / module_name
143
+
144
+ variables = PromptVariables(
145
+ module_name=module_name,
146
+ module_description=repo_info.get("description", f"{module_name} module"),
147
+ module_dir=str(module_dir).replace("\\", "/"),
148
+ doc_type=doc_type,
149
+ source_cache_path=str(source_cache).replace("\\", "/"),
150
+ output_path=str(module_dir).replace("\\", "/"),
151
+ high_methods=compute_high_methods(module_dir),
152
+ generated_docs=scan_generated_docs(module_dir),
153
+ sibling_modules=compute_sibling_modules(config, kb_name, module_name),
154
+ global_metadata=load_pregenerated(module_dir),
155
+ prior_docs_context=build_prior_docs_context(module_dir, doc_type, preset=preset),
156
+ shard_context=shard_context,
157
+ branch=repo_info.get("branch", "main"),
158
+ module_type=repo_info.get("type", "service"),
159
+ extras=extras or {},
160
+ )
161
+
162
+ return variables
163
+
164
+
165
+ def format_file_list_size(file_list_content: str) -> str:
166
+ """Format file list content with size statistics.
167
+
168
+ Args:
169
+ file_list_content: Raw file list text
170
+
171
+ Returns:
172
+ Formatted string with file count appended
173
+ """
174
+ if not file_list_content or file_list_content.startswith("("):
175
+ return file_list_content
176
+
177
+ lines = [l for l in file_list_content.splitlines() if l.strip()]
178
+ count = len(lines)
179
+ return f"{file_list_content}\n\n Total: {count} files"
180
+
181
+
182
+ def format_high_methods_summary(high_methods: str, max_items: int = 30) -> str:
183
+ """Truncate high methods list if too long.
184
+
185
+ Args:
186
+ high_methods: Raw high methods text
187
+ max_items: Maximum number of items to show
188
+
189
+ Returns:
190
+ Truncated string with count note if needed
191
+ """
192
+ if not high_methods or high_methods.startswith("("):
193
+ return high_methods
194
+
195
+ lines = high_methods.splitlines()
196
+ if len(lines) <= max_items:
197
+ return high_methods
198
+
199
+ truncated = "\n".join(lines[:max_items])
200
+ return f"{truncated}\n\n... {len(lines) - max_items} more high-complexity methods"
201
+
202
+
203
+ # ---------------------------------------------------------------------------
204
+ # Internal helpers
205
+ # ---------------------------------------------------------------------------
206
+
207
+
208
+ def _find_repo(config: dict[str, Any], kb_name: str, module_name: str) -> dict[str, Any]:
209
+ """Find repo/module config entry by name."""
210
+ kb = config["knowledge_bases"][kb_name]
211
+ source = kb["source"]
212
+ if source.get("structure") == "monorepo":
213
+ for mod in source.get("modules", []):
214
+ if mod["name"] == module_name:
215
+ return mod
216
+ else:
217
+ for repo in source.get("repos", []):
218
+ if repo["name"] == module_name:
219
+ return repo
220
+ return {"name": module_name}
221
+
222
+
223
+ # ---------------------------------------------------------------------------
224
+ # ReferencePromptAssembler — Agent mode (paths only, no inline content)
225
+ # ---------------------------------------------------------------------------
226
+
227
+
228
+ class ReferencePromptAssembler(PromptAssembler):
229
+ """Emits file path references instead of inlining content.
230
+
231
+ Used in Agent/readwrite mode where sub-agents can read files themselves.
232
+ Implements the PromptAssembler interface from core.interfaces.
233
+
234
+ Follows the old engine/skeleton/render_prompt.py three-way branching:
235
+ 1. Global_View_Doc (from preset doc_types.yaml global_view: true) → skeleton summary reference
236
+ 2. Agent mode (readwrite) + Regular_Doc → file-list path reference
237
+ 3. CLI mode (output-only) + Regular_Doc → inline file content (handled by InlinePromptAssembler)
238
+ """
239
+
240
+ def __init__(self, project_root: Path | None = None, preset: dict | None = None):
241
+ self._project_root = project_root or Path(".").resolve()
242
+ self._global_view_types = self._load_global_view_types(preset)
243
+
244
+ @staticmethod
245
+ def _load_global_view_types(preset: dict | None) -> set[str]:
246
+ """Load global_view doc types from preset config dynamically."""
247
+ if not preset:
248
+ return {"source-tree-analysis", "index"}
249
+ from core.preset import get_global_view_types
250
+ return set(get_global_view_types(preset))
251
+
252
+ def _relative(self, path: Path) -> str:
253
+ """Convert absolute path to project-relative path string."""
254
+ try:
255
+ return str(path.relative_to(self._project_root)).replace("\\", "/")
256
+ except ValueError:
257
+ return str(path).replace("\\", "/")
258
+
259
+ def resolve_file_list(self, module_dir: Path, doc_type: str,
260
+ file_list_override: str | None = None) -> str:
261
+ """Return file list as relative path reference — sub-agent reads it.
262
+
263
+ For Global_View_Doc types, returns skeleton summary reference instead.
264
+
265
+ Args:
266
+ module_dir: Module documentation directory
267
+ doc_type: Document type key
268
+ file_list_override: Optional override path (for shard-specific file lists)
269
+ """
270
+ if doc_type in self._global_view_types:
271
+ return self._skeleton_summary_reference(module_dir)
272
+
273
+ # Check override first (shard-specific file list)
274
+ if file_list_override:
275
+ override_path = Path(file_list_override)
276
+ if override_path.exists():
277
+ content = override_path.read_text(encoding="utf-8").strip()
278
+ lines = [l.strip() for l in content.splitlines()
279
+ if l.strip() and not l.strip().startswith("#")]
280
+ rel_path = self._relative(override_path)
281
+ return (
282
+ f"File list path: `{rel_path}`\n"
283
+ f"Total files: {len(lines)} source files\n\n"
284
+ f"Please read the file list above to get source file paths, then read source files grouped by package."
285
+ )
286
+
287
+ # Regular doc: file-list path reference
288
+ from core.paths import resolve_file_list
289
+ fl = resolve_file_list(module_dir, doc_type)
290
+ if fl is None or not fl.exists():
291
+ return "File list does not exist, please run `source-kb file-list` first to generate it"
292
+ content = fl.read_text(encoding="utf-8").strip()
293
+ if not content:
294
+ return "(No matching files)"
295
+ lines = [l.strip() for l in content.splitlines()
296
+ if l.strip() and not l.strip().startswith("#")]
297
+ rel_path = self._relative(fl)
298
+ return (
299
+ f"File list path: `{rel_path}`\n"
300
+ f"Total files: {len(lines)} source files\n\n"
301
+ f"Please read the file list above to get source file paths, then read source files grouped by package."
302
+ )
303
+
304
+ def _skeleton_summary_reference(self, module_dir: Path) -> str:
305
+ """Return skeleton summary path + source cache hint for Global_View_Doc types."""
306
+ from core.paths import resolve_skeleton_summary
307
+ summary = resolve_skeleton_summary(module_dir)
308
+ if summary and summary.exists():
309
+ rel_path = self._relative(summary)
310
+ # Also provide source_cache path for optional deep-dive
311
+ source_cache_hint = self._relative(module_dir).replace("knowledge/", ".source-cache/").rsplit("/", 1)[0]
312
+ return (
313
+ f"Skeleton summary path: `{rel_path}`\n\n"
314
+ f"This is a global view document type, generated primarily from the skeleton summary — no need to read source files individually.\n"
315
+ f"If you need implementation details of a specific class, source code is located in: `.source-cache/` under the corresponding module directory."
316
+ )
317
+ return (
318
+ "Skeleton summary file does not exist, please run "
319
+ "`source-kb extract --repo ... --preset ... --output ... --summary` first to generate it"
320
+ )
321
+
322
+ def resolve_source_content(self, module_dir: Path, doc_type: str, source_cache: Path) -> str:
323
+ """Return empty — sub-agent reads files itself via file-list."""
324
+ return ""
325
+
326
+ def resolve_skeleton_content(self, module_dir: Path) -> str:
327
+ """Return skeleton path reference."""
328
+ from core.paths import resolve_skeleton, resolve_skeleton_summary
329
+ summary = resolve_skeleton_summary(module_dir)
330
+ if summary and summary.exists():
331
+ return f"Skeleton summary path: `{self._relative(summary)}`\n(Please use the Read tool to read it)"
332
+ resolved = resolve_skeleton(module_dir)
333
+ if resolved and resolved.exists():
334
+ return f"Skeleton path: `{self._relative(resolved)}`\n(Please use the Read tool to read it)"
335
+ return "(Skeleton does not exist)"
336
+
337
+ def should_append_source(self) -> bool:
338
+ """Agent mode does not append source — sub-agents read files themselves."""
339
+ return False
core/rag/__init__.py ADDED
@@ -0,0 +1,22 @@
1
+ """core.rag — vector index (chunking, embedding, retrieval).
2
+
3
+ Provides document loading, chunking, index building, and retrieval.
4
+ Embedding backend is configurable via Config (ollama, openai, dashscope, chromadb-default).
5
+ """
6
+
7
+ from core.rag.loader import Document, load_documents
8
+ from core.rag.chunker import Chunk, chunk_documents
9
+ from core.rag.embedder import get_embeddings
10
+ from core.rag.indexer import build_index, get_collection
11
+ from core.rag.retriever import retrieve
12
+
13
+ __all__ = [
14
+ "Document",
15
+ "load_documents",
16
+ "Chunk",
17
+ "chunk_documents",
18
+ "get_embeddings",
19
+ "build_index",
20
+ "get_collection",
21
+ "retrieve",
22
+ ]
core/rag/__main__.py ADDED
@@ -0,0 +1,136 @@
1
+ """CLI entry points for RAG operations (search, index, rebuild).
2
+
3
+ Provides sub-commands for Agent mode:
4
+ python -m core.rag search --kb my-kb "query"
5
+ python -m core.rag index --kb my-kb
6
+ python -m core.rag rebuild --kb my-kb --module my-module --files a.md b.md
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import argparse
12
+ import json
13
+ import sys
14
+ from pathlib import Path
15
+
16
+ sys.stdout.reconfigure(encoding="utf-8")
17
+
18
+
19
+ def cmd_search(args: argparse.Namespace) -> None:
20
+ """Search knowledge base."""
21
+ from core.config import load_config, find_config
22
+ from core.rag.retriever import retrieve
23
+
24
+ config_path = Path(args.config) if args.config else find_config()
25
+ config = load_config(config_path)
26
+
27
+ results = retrieve(args.query, config, kb_name=args.kb)
28
+ if not results:
29
+ print(f"No results for '{args.query}'")
30
+ print(json.dumps({"status": "ok", "matches": 0}, ensure_ascii=False), file=sys.stderr)
31
+ return
32
+
33
+ for i, r in enumerate(results, 1):
34
+ score = r["score"]
35
+ source = r["metadata"].get("source", "?")
36
+ section = r["metadata"].get("section", "")
37
+ header = f"[{i}] {source}"
38
+ if section:
39
+ header += f" > {section}"
40
+ print(f"{header} (score: {score:.3f})")
41
+ print(f" {r['text'][:200]}...")
42
+ print()
43
+
44
+ print(json.dumps({"status": "ok", "matches": len(results)}, ensure_ascii=False), file=sys.stderr)
45
+
46
+
47
+ def cmd_index(args: argparse.Namespace) -> None:
48
+ """Build/rebuild full vector index."""
49
+ from core.config import load_config, find_config
50
+ from core.rag.loader import load_documents
51
+ from core.rag.chunker import chunk_documents
52
+ from core.rag.indexer import build_index
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)
57
+ knowledge_dir = kb_cfg["knowledge_dir"]
58
+ collection_name = kb_cfg["collection"]
59
+
60
+ docs = load_documents(knowledge_dir)
61
+ if not docs:
62
+ print(f"No documents found in {knowledge_dir}")
63
+ print(json.dumps({"status": "error", "message": "no documents"}, ensure_ascii=False), file=sys.stderr)
64
+ return
65
+
66
+ chunks = chunk_documents(docs)
67
+ print(f"Indexing {len(docs)} docs, {len(chunks)} chunks...")
68
+ build_index(chunks, collection_name, config, kb_name=args.kb)
69
+ print(f"Index built: {len(chunks)} chunks -> collection '{collection_name}'")
70
+ print(json.dumps({"status": "ok", "docs": len(docs), "chunks": len(chunks),
71
+ "collection": collection_name}, ensure_ascii=False), file=sys.stderr)
72
+
73
+
74
+ def cmd_rebuild(args: argparse.Namespace) -> None:
75
+ """Incremental rebuild — re-index only specified files."""
76
+ from core.config import load_config, find_config
77
+ from core.rag.loader import load_documents
78
+ from core.rag.chunker import chunk_documents
79
+ from core.rag.indexer import build_index
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)
84
+ knowledge_dir = Path(kb_cfg["knowledge_dir"])
85
+ collection_name = kb_cfg["collection"]
86
+
87
+ if args.module:
88
+ knowledge_dir = knowledge_dir / args.module
89
+
90
+ files = args.files or []
91
+ if files:
92
+ docs = load_documents(knowledge_dir, file_filter=files)
93
+ else:
94
+ docs = load_documents(knowledge_dir)
95
+
96
+ if not docs:
97
+ print(f"No documents to rebuild")
98
+ print(json.dumps({"status": "ok", "rebuilt": 0}, ensure_ascii=False), file=sys.stderr)
99
+ return
100
+
101
+ chunks = chunk_documents(docs)
102
+ print(f"Rebuilding {len(docs)} docs, {len(chunks)} chunks...")
103
+ build_index(chunks, collection_name, config, kb_name=args.kb, incremental=True)
104
+ print(f"Rebuilt: {len(chunks)} chunks -> collection '{collection_name}'")
105
+ print(json.dumps({"status": "ok", "docs": len(docs), "chunks": len(chunks)},
106
+ ensure_ascii=False), file=sys.stderr)
107
+
108
+
109
+ def main():
110
+ parser = argparse.ArgumentParser(prog="python -m core.rag", description="RAG tools")
111
+ parser.add_argument("--config", help="kb-project.yaml path (auto-detected)")
112
+ sub = parser.add_subparsers(dest="command")
113
+
114
+ p = sub.add_parser("search", help="Search knowledge base")
115
+ p.add_argument("--kb", required=True, help="Knowledge base name")
116
+ p.add_argument("query", help="Search query")
117
+
118
+ p = sub.add_parser("index", help="Build/rebuild full vector index")
119
+ p.add_argument("--kb", required=True, help="Knowledge base name")
120
+
121
+ p = sub.add_parser("rebuild", help="Incremental rebuild (specific files)")
122
+ p.add_argument("--kb", required=True, help="Knowledge base name")
123
+ p.add_argument("--module", help="Module name")
124
+ p.add_argument("--files", nargs="*", help="Specific files to rebuild")
125
+
126
+ args = parser.parse_args()
127
+ if not args.command:
128
+ parser.print_help()
129
+ sys.exit(1)
130
+
131
+ commands = {"search": cmd_search, "index": cmd_index, "rebuild": cmd_rebuild}
132
+ commands[args.command](args)
133
+
134
+
135
+ if __name__ == "__main__":
136
+ main()