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