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.
- {source_kb-0.2.26/source_kb.egg-info → source_kb-0.2.28}/PKG-INFO +1 -1
- source_kb-0.2.28/cli/commands/cross_graph.py +27 -0
- source_kb-0.2.28/cli/commands/dispatch_preview.py +28 -0
- source_kb-0.2.28/cli/commands/graph.py +27 -0
- source_kb-0.2.28/cli/commands/impact.py +32 -0
- source_kb-0.2.28/cli/commands/inject_version.py +22 -0
- source_kb-0.2.28/cli/commands/sync_prompt.py +20 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/__init__.py +2 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/docs/__init__.py +2 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/docs/shared.py +1 -1
- {source_kb-0.2.26 → source_kb-0.2.28}/core/monitor/__init__.py +2 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/paths.py +30 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/prompt/__init__.py +2 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/prompt/__main__.py +40 -28
- {source_kb-0.2.26 → source_kb-0.2.28}/core/prompt/content.py +30 -4
- {source_kb-0.2.26 → source_kb-0.2.28}/core/prompt/renderer.py +55 -3
- {source_kb-0.2.26 → source_kb-0.2.28}/core/rag/__init__.py +2 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/rag/__main__.py +27 -22
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/__init__.py +2 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/__main__.py +34 -243
- source_kb-0.2.28/core/skeleton/_dispatch.py +348 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/cmd_anchor_fix.py +4 -6
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/cmd_cross_graph.py +11 -10
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/cmd_diff_doc.py +7 -11
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/cmd_dispatch_preview.py +20 -19
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/cmd_extract.py +31 -24
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/cmd_file_list.py +24 -20
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/cmd_graph.py +3 -8
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/cmd_impact.py +23 -17
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/cmd_inject_version.py +1 -4
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/cmd_lock.py +5 -7
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/cmd_merge_delta.py +8 -12
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/cmd_report.py +1 -6
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/cmd_split_apply.py +2 -5
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/cmd_split_files.py +10 -8
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/cmd_stale_files.py +8 -5
- source_kb-0.2.28/core/skeleton/cmd_sync_prompt.py +151 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/parsers/treesitter_multi.py +2 -8
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/section_ops.py +1 -1
- {source_kb-0.2.26 → source_kb-0.2.28}/core/utils.py +45 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/validators/__main__.py +74 -37
- source_kb-0.2.28/presets/java-spring/common-rules.md +12 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/doc_types.yaml +4 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-aop.md +1 -12
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-api.md +1 -4
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-architecture.md +1 -4
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-async-events.md +1 -12
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-business.md +1 -4
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-caching.md +1 -12
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-data-flow.md +1 -12
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-database-access.md +1 -12
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-deployment.md +1 -12
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-enum.md +1 -12
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-error-handling.md +1 -12
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-extension-points.md +1 -12
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-external-integrations.md +1 -12
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-messaging.md +1 -12
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-model.md +1 -4
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-observability.md +1 -4
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-public-api.md +1 -12
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-rate-limiting.md +1 -12
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-routing.md +1 -12
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-runbook.md +1 -12
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-scheduled.md +1 -12
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-scheduling.md +1 -12
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-security.md +1 -12
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-starters.md +1 -12
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-structure.md +1 -4
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-utils.md +1 -4
- {source_kb-0.2.26 → source_kb-0.2.28}/pyproject.toml +1 -1
- {source_kb-0.2.26 → source_kb-0.2.28/source_kb.egg-info}/PKG-INFO +1 -1
- {source_kb-0.2.26 → source_kb-0.2.28}/source_kb.egg-info/SOURCES.txt +9 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/LICENSE +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/MANIFEST.in +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/README.en.md +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/README.md +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/cli/__init__.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/cli/__main__.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/__init__.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/anchor_fix.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/audit.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/check_triggers.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/detect.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/diff_doc.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/dispatch.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/extract.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/file_list.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/history_cmd.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/index.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/install_skills.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/jar_resolve.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/lock.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/merge.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/merge_delta.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/metadata.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/module_dag.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/post_merge.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/query.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/query_preset.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/record_feedback.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/render.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/report.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/scan_repos.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/score.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/setup.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/split.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/stale_files.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/status.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/cli/commands/validate.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/cli/global_assets.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/bootstrap.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/config.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/docs/section_updater.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/docs/trigger_detect.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/git.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/history.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/interfaces.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/monitor/progress.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/monitor/prompt_store.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/preset.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/preset_accessors.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/preset_classify.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/preset_hooks.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/preset_profile.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/prompt/context_manager.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/prompt/response_parser.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/prompt/templates.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/prompt/validate_parity.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/prompt/variables.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/rag/bm25_index.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/rag/chunker.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/rag/embedder.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/rag/indexer.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/rag/loader.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/rag/retriever.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/scan_repos.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/anchor_fix.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/classify.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/community.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/cross_module_graph.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/dependency_graph.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/diff_doc.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/dispatch.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/dispatch_render.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/dispatch_source.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/extract.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/extract_methods.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/file_list.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/graph.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/impact.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/impact_cross_module.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/jar_download.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/jar_resolver.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/loader.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/merge.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/merge_delta.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/metadata.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/metadata_builders.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/module_dag.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/parsers/__init__.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/parsers/jqassistant.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/parsers/jqassistant_cypher.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/parsers/regex.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/parsers/treesitter.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/parsers/treesitter_java.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/pom_parser.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/post_merge.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/post_merge_llm.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/query.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/shard_context.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/split.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/split_cache.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/split_feedback.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/split_plan.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/split_plan_helpers.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/skeleton/split_plan_llm.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/validators/__init__.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/validators/consistency.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/validators/coverage.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/validators/density.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/validators/duplicates.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/validators/engine.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/validators/links.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/validators/sampling.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/validators/score.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/core/validators/structure.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/examples/README.md +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/examples/__init__.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/examples/monorepo-backend/kb-project.yaml +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/examples/multi-module-ecommerce/kb-project.yaml +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/examples/spring-petclinic/README.md +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/examples/spring-petclinic/kb-project.yaml +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/generic/audit_dimensions.md +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/generic/doc_types.yaml +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/generic/preset.yaml +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/audit_dimensions.md +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/audit_dimensions.yaml +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/hooks.py +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/preset.yaml +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/README.md +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/audit-system.md +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-audit-api-contracts.md +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-audit-architecture.md +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-audit-business.md +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-audit-data-models.md +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-index.md +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/subagent-sync-section.md +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/templates/sync-system.md +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/presets/java-spring/workflow-extensions.md +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/setup.cfg +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/source_kb.egg-info/dependency_links.txt +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/source_kb.egg-info/entry_points.txt +0 -0
- {source_kb-0.2.26 → source_kb-0.2.28}/source_kb.egg-info/requires.txt +0 -0
- {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.
|
|
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)
|
|
@@ -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
|
|
|
@@ -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"
|
|
@@ -14,78 +14,81 @@ from pathlib import Path
|
|
|
14
14
|
sys.stdout.reconfigure(encoding="utf-8")
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
def cmd_render(
|
|
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
|
-
|
|
24
|
-
kb_config =
|
|
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
|
-
|
|
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(
|
|
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 '{
|
|
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
|
|
52
|
-
for item in
|
|
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
|
|
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
|
|
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=
|
|
71
|
-
kb_name=
|
|
72
|
-
module_name=
|
|
73
|
-
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
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
print(f"Rendered to: {
|
|
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
|
-
|
|
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"{
|
|
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(
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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(
|
|
99
|
-
|
|
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(
|
|
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 =
|
|
25
|
-
|
|
24
|
+
config_path = config if config else find_config()
|
|
25
|
+
cfg = load_config(config_path)
|
|
26
26
|
|
|
27
|
-
results = retrieve(
|
|
27
|
+
results = retrieve(query, cfg, kb_name=kb)
|
|
28
28
|
if not results:
|
|
29
|
-
print(f"No results for '{
|
|
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(
|
|
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 =
|
|
55
|
-
|
|
56
|
-
kb_cfg =
|
|
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,
|
|
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(
|
|
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 =
|
|
82
|
-
|
|
83
|
-
kb_cfg =
|
|
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
|
-
|
|
88
|
-
if
|
|
89
|
-
docs = load_documents(knowledge_dir, module=
|
|
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=
|
|
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,
|
|
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
|
-
|
|
129
|
-
|
|
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__":
|