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.
- cli/__init__.py +50 -0
- cli/__main__.py +5 -0
- cli/commands/__init__.py +1 -0
- cli/commands/anchor_fix.py +47 -0
- cli/commands/diff_doc.py +52 -0
- cli/commands/dispatch.py +77 -0
- cli/commands/extract.py +72 -0
- cli/commands/file_list.py +74 -0
- cli/commands/index.py +84 -0
- cli/commands/lock.py +89 -0
- cli/commands/merge.py +60 -0
- cli/commands/merge_delta.py +19 -0
- cli/commands/metadata.py +24 -0
- cli/commands/pipeline.py +45 -0
- cli/commands/post_merge.py +43 -0
- cli/commands/query.py +52 -0
- cli/commands/render.py +101 -0
- cli/commands/scan_repos.py +46 -0
- cli/commands/setup.py +94 -0
- cli/commands/split.py +196 -0
- cli/commands/stale_files.py +98 -0
- cli/commands/validate.py +191 -0
- core/__init__.py +32 -0
- core/config.py +261 -0
- core/docs/__init__.py +7 -0
- core/docs/section_updater.py +286 -0
- core/docs/shared.py +149 -0
- core/git.py +294 -0
- core/interfaces.py +249 -0
- core/monitor/__init__.py +5 -0
- core/monitor/progress.py +83 -0
- core/monitor/prompt_store.py +49 -0
- core/paths.py +141 -0
- core/preset.py +237 -0
- core/preset_accessors.py +202 -0
- core/preset_classify.py +132 -0
- core/preset_hooks.py +129 -0
- core/preset_profile.py +89 -0
- core/prompt/__init__.py +7 -0
- core/prompt/__main__.py +147 -0
- core/prompt/content.py +320 -0
- core/prompt/context_manager.py +164 -0
- core/prompt/renderer.py +236 -0
- core/prompt/response_parser.py +274 -0
- core/prompt/templates.py +357 -0
- core/prompt/validate_parity.py +162 -0
- core/prompt/variables.py +339 -0
- core/rag/__init__.py +22 -0
- core/rag/__main__.py +136 -0
- core/rag/bm25_index.py +268 -0
- core/rag/chunker.py +273 -0
- core/rag/embedder.py +151 -0
- core/rag/indexer.py +292 -0
- core/rag/loader.py +89 -0
- core/rag/retriever.py +82 -0
- core/skeleton/__init__.py +11 -0
- core/skeleton/__main__.py +934 -0
- core/skeleton/anchor_fix.py +250 -0
- core/skeleton/classify.py +331 -0
- core/skeleton/cmd_anchor_fix.py +43 -0
- core/skeleton/cmd_diff_doc.py +44 -0
- core/skeleton/cmd_lock.py +87 -0
- core/skeleton/cmd_merge_delta.py +41 -0
- core/skeleton/community.py +233 -0
- core/skeleton/dependency_graph.py +306 -0
- core/skeleton/diff_doc.py +248 -0
- core/skeleton/dispatch.py +273 -0
- core/skeleton/dispatch_render.py +319 -0
- core/skeleton/dispatch_source.py +111 -0
- core/skeleton/extract.py +218 -0
- core/skeleton/extract_methods.py +298 -0
- core/skeleton/file_list.py +239 -0
- core/skeleton/impact.py +278 -0
- core/skeleton/jar_download.py +177 -0
- core/skeleton/jar_resolver.py +186 -0
- core/skeleton/loader.py +162 -0
- core/skeleton/merge.py +278 -0
- core/skeleton/merge_delta.py +229 -0
- core/skeleton/metadata.py +96 -0
- core/skeleton/metadata_builders.py +264 -0
- core/skeleton/module_dag.py +330 -0
- core/skeleton/parsers/__init__.py +71 -0
- core/skeleton/parsers/jqassistant.py +300 -0
- core/skeleton/parsers/jqassistant_cypher.py +225 -0
- core/skeleton/parsers/regex.py +171 -0
- core/skeleton/parsers/treesitter.py +324 -0
- core/skeleton/parsers/treesitter_java.py +284 -0
- core/skeleton/parsers/treesitter_multi.py +289 -0
- core/skeleton/pom_parser.py +299 -0
- core/skeleton/post_merge.py +295 -0
- core/skeleton/post_merge_llm.py +82 -0
- core/skeleton/query.py +195 -0
- core/skeleton/shard_context.py +177 -0
- core/skeleton/split.py +180 -0
- core/skeleton/split_cache.py +107 -0
- core/skeleton/split_feedback.py +174 -0
- core/skeleton/split_plan.py +219 -0
- core/skeleton/split_plan_helpers.py +305 -0
- core/skeleton/split_plan_llm.py +274 -0
- core/utils.py +135 -0
- core/validators/__init__.py +65 -0
- core/validators/__main__.py +215 -0
- core/validators/consistency.py +203 -0
- core/validators/coverage.py +171 -0
- core/validators/duplicates.py +76 -0
- core/validators/engine.py +224 -0
- core/validators/links.py +76 -0
- core/validators/sampling.py +169 -0
- core/validators/structure.py +144 -0
- engine/__init__.py +7 -0
- engine/assembler.py +231 -0
- engine/confirm.py +65 -0
- engine/dedup.py +106 -0
- engine/main.py +211 -0
- engine/pipeline/__init__.py +163 -0
- engine/pipeline/recovery.py +250 -0
- engine/pipeline/steps/__init__.py +23 -0
- engine/pipeline/steps/audit.py +220 -0
- engine/pipeline/steps/audit_apply.py +195 -0
- engine/pipeline/steps/audit_helpers.py +155 -0
- engine/pipeline/steps/classify_llm.py +236 -0
- engine/pipeline/steps/classify_prompt.py +223 -0
- engine/pipeline/steps/finalize.py +160 -0
- engine/pipeline/steps/generate.py +169 -0
- engine/pipeline/steps/generate_batch.py +197 -0
- engine/pipeline/steps/generate_recovery.py +170 -0
- engine/pipeline/steps/llm_plan_split.py +253 -0
- engine/pipeline/steps/lock.py +64 -0
- engine/pipeline/steps/preflight.py +237 -0
- engine/pipeline/steps/preflight_adjust.py +147 -0
- engine/pipeline/steps/pregenerate.py +130 -0
- engine/pipeline/steps/quality.py +81 -0
- engine/pipeline/steps/skeleton.py +149 -0
- engine/pipeline/steps/source.py +163 -0
- engine/pipeline/steps/sync.py +117 -0
- engine/pipeline/steps/sync_finalize.py +237 -0
- engine/pipeline/steps/sync_update.py +341 -0
- engine/pipelines.py +91 -0
- engine/runner.py +335 -0
- engine/strategies/__init__.py +86 -0
- engine/strategies/api.py +128 -0
- engine/strategies/delegated.py +50 -0
- engine/strategies/dryrun.py +25 -0
- engine/two_phase.py +143 -0
- mcp_server/__init__.py +73 -0
- mcp_server/__main__.py +5 -0
- mcp_server/tools/__init__.py +1 -0
- mcp_server/tools/config.py +63 -0
- mcp_server/tools/discovery.py +276 -0
- mcp_server/tools/generation.py +184 -0
- mcp_server/tools/planning.py +144 -0
- mcp_server/tools/source.py +175 -0
- mcp_server/tools/validation.py +140 -0
- mcp_server/tools/workflow.py +166 -0
- mcp_server/workflow_loader.py +204 -0
- presets/generic/audit_dimensions.md +132 -0
- presets/generic/doc_types.yaml +152 -0
- presets/generic/preset.yaml +115 -0
- presets/java-spring/audit_dimensions.md +228 -0
- presets/java-spring/audit_dimensions.yaml +203 -0
- presets/java-spring/doc_types.yaml +269 -0
- presets/java-spring/hooks.py +122 -0
- presets/java-spring/preset.yaml +341 -0
- presets/java-spring/templates/README.md +34 -0
- presets/java-spring/templates/audit-system.md +15 -0
- presets/java-spring/templates/subagent-aop.md +105 -0
- presets/java-spring/templates/subagent-api.md +63 -0
- presets/java-spring/templates/subagent-architecture.md +111 -0
- presets/java-spring/templates/subagent-async-events.md +107 -0
- presets/java-spring/templates/subagent-audit-api-contracts.md +40 -0
- presets/java-spring/templates/subagent-audit-architecture.md +38 -0
- presets/java-spring/templates/subagent-audit-business.md +40 -0
- presets/java-spring/templates/subagent-audit-data-models.md +40 -0
- presets/java-spring/templates/subagent-business.md +129 -0
- presets/java-spring/templates/subagent-caching.md +75 -0
- presets/java-spring/templates/subagent-database-access.md +114 -0
- presets/java-spring/templates/subagent-enum.md +75 -0
- presets/java-spring/templates/subagent-error-handling.md +91 -0
- presets/java-spring/templates/subagent-external-integrations.md +80 -0
- presets/java-spring/templates/subagent-index.md +122 -0
- presets/java-spring/templates/subagent-messaging.md +97 -0
- presets/java-spring/templates/subagent-model.md +88 -0
- presets/java-spring/templates/subagent-observability.md +91 -0
- presets/java-spring/templates/subagent-scheduled.md +81 -0
- presets/java-spring/templates/subagent-security.md +102 -0
- presets/java-spring/templates/subagent-structure.md +101 -0
- presets/java-spring/templates/subagent-sync-section.md +34 -0
- presets/java-spring/templates/subagent-utils.md +73 -0
- presets/java-spring/templates/sync-system.md +8 -0
- presets/java-spring/workflow-extensions.md +112 -0
- skills/__init__.py +1 -0
- skills/_shared/README.md +30 -0
- skills/_shared/doc-coverage-shared.md +134 -0
- skills/_shared/doc-quality-standard.md +1058 -0
- skills/_shared/doc-subagent-rules.md +762 -0
- skills/_shared/windows-compat.md +89 -0
- skills/kb-audit/SKILL.md +52 -0
- skills/kb-audit/rules.md +88 -0
- skills/kb-audit/steps/step-01-prepare.md +75 -0
- skills/kb-audit/steps/step-02-audit.md +96 -0
- skills/kb-audit/steps/step-03-verify.md +65 -0
- skills/kb-audit/steps/step-04-report.md +64 -0
- skills/kb-init/SKILL.md +146 -0
- skills/kb-init/rules.md +187 -0
- skills/kb-init/steps/step-01-scope.md +62 -0
- skills/kb-init/steps/step-02-source.md +410 -0
- skills/kb-init/steps/step-03-generate.md +307 -0
- skills/kb-init/steps/step-04-quality.md +92 -0
- skills/kb-init/steps/step-05-finalize.md +132 -0
- skills/kb-init/templates/core/execution-modes.md +29 -0
- skills/kb-init/templates/core/output-only.md +4 -0
- skills/kb-init/templates/core/readwrite.md +33 -0
- skills/kb-search/SKILL.md +138 -0
- skills/kb-search/rules.md +64 -0
- skills/kb-sync/SKILL.md +43 -0
- skills/kb-sync/rules.md +70 -0
- skills/kb-sync/scripts/rebuild_module.py +91 -0
- skills/kb-sync/scripts/scan_repos.py +687 -0
- skills/kb-sync/steps/step-01-detect.md +72 -0
- skills/kb-sync/steps/step-02-update.md +71 -0
- skills/kb-sync/steps/step-03-verify.md +47 -0
- skills/kb-sync/steps/step-04-finalize.md +52 -0
- source_kb-0.2.2.dist-info/METADATA +194 -0
- source_kb-0.2.2.dist-info/RECORD +228 -0
- source_kb-0.2.2.dist-info/WHEEL +5 -0
- source_kb-0.2.2.dist-info/entry_points.txt +3 -0
- source_kb-0.2.2.dist-info/licenses/LICENSE +21 -0
- source_kb-0.2.2.dist-info/top_level.txt +6 -0
core/prompt/variables.py
ADDED
|
@@ -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()
|