shotgun-sh 0.5.2.dev2__tar.gz → 0.5.2.dev3__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.
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/.gitignore +1 -1
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/PKG-INFO +1 -1
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/pyproject.toml +1 -1
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/common.py +4 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/tools/__init__.py +3 -0
- shotgun_sh-0.5.2.dev3/src/shotgun/agents/tools/markdown_tools/__init__.py +33 -0
- shotgun_sh-0.5.2.dev3/src/shotgun/agents/tools/markdown_tools/insert_section.py +195 -0
- shotgun_sh-0.5.2.dev3/src/shotgun/agents/tools/markdown_tools/models.py +33 -0
- shotgun_sh-0.5.2.dev3/src/shotgun/agents/tools/markdown_tools/replace_section.py +186 -0
- shotgun_sh-0.5.2.dev3/src/shotgun/agents/tools/markdown_tools/utils.py +157 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/LICENSE +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/README.md +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/hatch_build.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/agent_manager.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/config/README.md +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/config/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/config/constants.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/config/manager.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/config/models.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/config/provider.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/config/streaming_test.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/context_analyzer/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/context_analyzer/analyzer.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/context_analyzer/constants.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/context_analyzer/formatter.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/context_analyzer/models.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/conversation/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/conversation/filters.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/conversation/history/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/conversation/history/chunking.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/conversation/history/compaction.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/conversation/history/constants.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/conversation/history/context_extraction.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/conversation/history/file_content_deduplication.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/conversation/history/history_building.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/conversation/history/history_processors.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/conversation/history/message_utils.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/conversation/history/token_counting/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/conversation/history/token_counting/anthropic.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/conversation/history/token_counting/base.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/conversation/history/token_counting/openai.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/conversation/history/token_counting/sentencepiece_counter.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/conversation/history/token_counting/tokenizer_cache.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/conversation/history/token_counting/utils.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/conversation/history/token_estimation.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/conversation/manager.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/conversation/models.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/error/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/error/models.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/export.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/llm.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/messages.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/models.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/plan.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/research.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/router/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/router/models.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/router/router.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/router/tools/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/router/tools/delegation_tools.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/router/tools/plan_tools.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/runner.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/specify.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/tasks.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/tools/codebase/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/tools/codebase/codebase_shell.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/tools/codebase/directory_lister.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/tools/codebase/file_read.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/tools/codebase/models.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/tools/codebase/query_graph.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/tools/codebase/retrieve_code.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/tools/file_management.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/tools/registry.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/tools/web_search/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/tools/web_search/anthropic.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/tools/web_search/gemini.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/tools/web_search/openai.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/tools/web_search/utils.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/agents/usage_manager.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/api_endpoints.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/build_constants.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/clear.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/codebase/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/codebase/commands.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/codebase/models.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/compact.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/config.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/context.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/error_handler.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/export.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/feedback.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/models.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/plan.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/research.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/spec/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/spec/backup.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/spec/commands.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/spec/models.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/spec/pull_service.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/specify.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/tasks.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/update.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/cli/utils.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/benchmarks/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/benchmarks/benchmark_runner.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/benchmarks/exporters.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/benchmarks/formatters/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/benchmarks/formatters/base.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/benchmarks/formatters/json_formatter.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/benchmarks/formatters/markdown.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/benchmarks/models.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/call_resolution.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/change_detector.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/code_retrieval.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/cypher_models.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/errors.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/extractors/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/extractors/base.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/extractors/factory.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/extractors/go/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/extractors/go/extractor.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/extractors/javascript/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/extractors/javascript/extractor.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/extractors/protocol.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/extractors/python/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/extractors/python/extractor.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/extractors/rust/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/extractors/rust/extractor.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/extractors/types.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/extractors/typescript/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/extractors/typescript/extractor.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/gitignore.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/ingestor.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/kuzu_compat.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/language_config.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/manager.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/metrics_collector.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/metrics_types.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/nl_query.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/parallel_executor.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/parser_loader.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/work_distributor.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/core/worker.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/indexing_state.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/models.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/codebase/service.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/exceptions.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/llm_proxy/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/llm_proxy/client.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/llm_proxy/clients.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/llm_proxy/constants.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/llm_proxy/models.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/logging_config.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/main.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/posthog_telemetry.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/agents/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/agents/export.j2 +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/agents/partials/codebase_understanding.j2 +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/agents/partials/content_formatting.j2 +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/agents/partials/interactive_mode.j2 +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/agents/partials/router_delegation_mode.j2 +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/agents/plan.j2 +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/agents/research.j2 +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/agents/router.j2 +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/agents/specify.j2 +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/agents/state/codebase/codebase_graphs_available.j2 +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/agents/state/system_state.j2 +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/agents/tasks.j2 +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/codebase/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/codebase/cypher_query_patterns.j2 +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/codebase/cypher_system.j2 +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/codebase/enhanced_query_context.j2 +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/codebase/partials/cypher_rules.j2 +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/codebase/partials/graph_schema.j2 +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/codebase/partials/temporal_context.j2 +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/history/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/history/chunk_summarization.j2 +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/history/combine_summaries.j2 +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/history/incremental_summarization.j2 +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/history/summarization.j2 +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/loader.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/prompts/tools/web_search.j2 +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/py.typed +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/sdk/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/sdk/codebase.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/sdk/exceptions.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/sdk/models.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/sdk/services.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/sentry_telemetry.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/settings.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/shotgun_web/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/shotgun_web/client.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/shotgun_web/constants.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/shotgun_web/exceptions.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/shotgun_web/models.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/shotgun_web/shared_specs/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/shotgun_web/shared_specs/file_scanner.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/shotgun_web/shared_specs/hasher.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/shotgun_web/shared_specs/models.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/shotgun_web/shared_specs/upload_pipeline.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/shotgun_web/shared_specs/utils.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/shotgun_web/specs_client.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/shotgun_web/supabase_client.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/telemetry.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/app.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/commands/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/components/context_indicator.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/components/mode_indicator.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/components/prompt_input.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/components/spinner.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/components/splash.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/components/status_bar.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/components/vertical_tail.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/containers.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/dependencies.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/filtered_codebase_service.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/layout.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/protocols.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/chat/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/chat/chat.tcss +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/chat/chat_screen.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/chat/codebase_index_prompt_screen.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/chat/codebase_index_selection.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/chat/help_text.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/chat/prompt_history.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/chat.tcss +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/chat_screen/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/chat_screen/command_providers.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/chat_screen/hint_message.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/chat_screen/history/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/chat_screen/history/agent_response.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/chat_screen/history/chat_history.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/chat_screen/history/formatters.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/chat_screen/history/partial_response.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/chat_screen/history/user_question.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/chat_screen/messages.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/confirmation_dialog.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/database_locked_dialog.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/database_timeout_dialog.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/directory_setup.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/feedback.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/github_issue.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/kuzu_error_dialog.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/model_picker.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/pipx_migration.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/provider_config.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/shared_specs/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/shared_specs/create_spec_dialog.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/shared_specs/models.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/shared_specs/share_specs_dialog.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/shared_specs/upload_progress_screen.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/shotgun_auth.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/spec_pull.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/splash.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/screens/welcome.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/services/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/services/conversation_service.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/state/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/state/processing_state.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/styles.tcss +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/utils/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/utils/mode_progress.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/widgets/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/widgets/approval_widget.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/widgets/cascade_confirmation_widget.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/widgets/plan_panel.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/widgets/step_checkpoint_widget.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/tui/widgets/widget_coordinator.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/utils/__init__.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/utils/datetime_utils.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/utils/env_utils.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/utils/file_system_utils.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/utils/marketing.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/utils/source_detection.py +0 -0
- {shotgun_sh-0.5.2.dev2 → shotgun_sh-0.5.2.dev3}/src/shotgun/utils/update_checker.py +0 -0
|
@@ -38,8 +38,10 @@ from .tools import (
|
|
|
38
38
|
codebase_shell,
|
|
39
39
|
directory_lister,
|
|
40
40
|
file_read,
|
|
41
|
+
insert_markdown_section,
|
|
41
42
|
query_graph,
|
|
42
43
|
read_file,
|
|
44
|
+
replace_markdown_section,
|
|
43
45
|
retrieve_code,
|
|
44
46
|
write_file,
|
|
45
47
|
)
|
|
@@ -204,6 +206,8 @@ async def create_base_agent(
|
|
|
204
206
|
agent.tool(write_file)
|
|
205
207
|
agent.tool(append_file)
|
|
206
208
|
agent.tool(read_file)
|
|
209
|
+
agent.tool(replace_markdown_section)
|
|
210
|
+
agent.tool(insert_markdown_section)
|
|
207
211
|
|
|
208
212
|
# Register codebase understanding tools (conditional)
|
|
209
213
|
if load_codebase_understanding_tools:
|
|
@@ -8,6 +8,7 @@ from .codebase import (
|
|
|
8
8
|
retrieve_code,
|
|
9
9
|
)
|
|
10
10
|
from .file_management import append_file, read_file, write_file
|
|
11
|
+
from .markdown_tools import insert_markdown_section, replace_markdown_section
|
|
11
12
|
from .web_search import (
|
|
12
13
|
anthropic_web_search_tool,
|
|
13
14
|
gemini_web_search_tool,
|
|
@@ -23,6 +24,8 @@ __all__ = [
|
|
|
23
24
|
"read_file",
|
|
24
25
|
"write_file",
|
|
25
26
|
"append_file",
|
|
27
|
+
"replace_markdown_section",
|
|
28
|
+
"insert_markdown_section",
|
|
26
29
|
# Codebase understanding tools
|
|
27
30
|
"query_graph",
|
|
28
31
|
"retrieve_code",
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Markdown manipulation tools for Pydantic AI agents."""
|
|
2
|
+
|
|
3
|
+
from .insert_section import insert_markdown_section
|
|
4
|
+
from .models import CloseMatch, HeadingList, HeadingMatch, MarkdownHeading
|
|
5
|
+
from .replace_section import replace_markdown_section
|
|
6
|
+
from .utils import (
|
|
7
|
+
detect_line_ending,
|
|
8
|
+
extract_headings,
|
|
9
|
+
find_close_matches,
|
|
10
|
+
find_matching_heading,
|
|
11
|
+
find_section_bounds,
|
|
12
|
+
get_heading_level,
|
|
13
|
+
normalize_section_content,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
# Tools
|
|
18
|
+
"replace_markdown_section",
|
|
19
|
+
"insert_markdown_section",
|
|
20
|
+
# Models
|
|
21
|
+
"MarkdownHeading",
|
|
22
|
+
"HeadingList",
|
|
23
|
+
"HeadingMatch",
|
|
24
|
+
"CloseMatch",
|
|
25
|
+
# Utilities
|
|
26
|
+
"get_heading_level",
|
|
27
|
+
"extract_headings",
|
|
28
|
+
"find_matching_heading",
|
|
29
|
+
"find_close_matches",
|
|
30
|
+
"find_section_bounds",
|
|
31
|
+
"detect_line_ending",
|
|
32
|
+
"normalize_section_content",
|
|
33
|
+
]
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""Tool for inserting content into markdown sections."""
|
|
2
|
+
|
|
3
|
+
import aiofiles
|
|
4
|
+
import aiofiles.os
|
|
5
|
+
from pydantic_ai import RunContext
|
|
6
|
+
|
|
7
|
+
from shotgun.agents.models import AgentDeps, FileOperationType
|
|
8
|
+
from shotgun.agents.tools.file_management import _validate_agent_scoped_path
|
|
9
|
+
from shotgun.agents.tools.registry import ToolCategory, register_tool
|
|
10
|
+
from shotgun.logging_config import get_logger
|
|
11
|
+
|
|
12
|
+
from .utils import (
|
|
13
|
+
detect_line_ending,
|
|
14
|
+
extract_headings,
|
|
15
|
+
find_close_matches,
|
|
16
|
+
find_matching_heading,
|
|
17
|
+
find_section_bounds,
|
|
18
|
+
normalize_section_content,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
logger = get_logger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@register_tool(
|
|
25
|
+
category=ToolCategory.ARTIFACT_MANAGEMENT,
|
|
26
|
+
display_text="Inserting content",
|
|
27
|
+
key_arg="filename",
|
|
28
|
+
)
|
|
29
|
+
async def insert_markdown_section(
|
|
30
|
+
ctx: RunContext[AgentDeps],
|
|
31
|
+
filename: str,
|
|
32
|
+
after_heading: str,
|
|
33
|
+
content: str,
|
|
34
|
+
new_heading: str | None = None,
|
|
35
|
+
) -> str:
|
|
36
|
+
"""Insert content at the end of a Markdown section.
|
|
37
|
+
|
|
38
|
+
PREFER THIS TOOL over rewriting the entire file - it is faster, less costly,
|
|
39
|
+
and less error-prone. Use this to append content to an existing section.
|
|
40
|
+
|
|
41
|
+
Uses fuzzy matching on headings so minor typos are tolerated.
|
|
42
|
+
Inserts content just before the next heading at the same or higher level.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
ctx: Run context with agent dependencies
|
|
46
|
+
filename: Path to the Markdown file (relative to .shotgun directory)
|
|
47
|
+
after_heading: The heading to insert after (e.g., '## Requirements'). Fuzzy matched.
|
|
48
|
+
content: The content to insert at the end of the section
|
|
49
|
+
new_heading: Optional heading for the inserted content (creates a subsection)
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Success message or error message
|
|
53
|
+
"""
|
|
54
|
+
logger.debug("Inserting content into section '%s' in: %s", after_heading, filename)
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
# Validate path with agent scoping
|
|
58
|
+
file_path = _validate_agent_scoped_path(filename, ctx.deps.agent_mode)
|
|
59
|
+
|
|
60
|
+
# Check if file exists
|
|
61
|
+
if not await aiofiles.os.path.exists(file_path):
|
|
62
|
+
return f"Error: File '{filename}' not found"
|
|
63
|
+
|
|
64
|
+
# Read file content (newline="" preserves original line endings)
|
|
65
|
+
async with aiofiles.open(file_path, encoding="utf-8", newline="") as f:
|
|
66
|
+
file_content = await f.read()
|
|
67
|
+
|
|
68
|
+
# Detect line ending style
|
|
69
|
+
line_ending = detect_line_ending(file_content)
|
|
70
|
+
lines = file_content.split("\n")
|
|
71
|
+
|
|
72
|
+
# Remove \r from lines if CRLF
|
|
73
|
+
if line_ending == "\r\n":
|
|
74
|
+
lines = [line.rstrip("\r") for line in lines]
|
|
75
|
+
|
|
76
|
+
# Extract headings
|
|
77
|
+
headings = extract_headings(file_content)
|
|
78
|
+
|
|
79
|
+
if not headings:
|
|
80
|
+
return f"Error: No headings found in '{filename}'. Cannot insert into files without headings."
|
|
81
|
+
|
|
82
|
+
# Find matching heading
|
|
83
|
+
match_result = find_matching_heading(headings, after_heading)
|
|
84
|
+
|
|
85
|
+
if match_result is None:
|
|
86
|
+
# No match found - provide helpful error with available headings
|
|
87
|
+
available = [h.text for h in headings]
|
|
88
|
+
close = find_close_matches(headings, after_heading)
|
|
89
|
+
|
|
90
|
+
if close and close[0].confidence >= 0.6:
|
|
91
|
+
# There are close matches but below threshold
|
|
92
|
+
close_display = ", ".join(
|
|
93
|
+
f"'{m.heading_text}' ({int(m.confidence * 100)}%)" for m in close
|
|
94
|
+
)
|
|
95
|
+
return (
|
|
96
|
+
f"No section matching '{after_heading}' found in {filename}. "
|
|
97
|
+
f"Did you mean: {close_display}"
|
|
98
|
+
)
|
|
99
|
+
else:
|
|
100
|
+
# List available headings
|
|
101
|
+
available_display = ", ".join(available[:5])
|
|
102
|
+
if len(available) > 5:
|
|
103
|
+
available_display += f" (+{len(available) - 5} more)"
|
|
104
|
+
return (
|
|
105
|
+
f"No section matching '{after_heading}' found in {filename}. "
|
|
106
|
+
f"Available headings: {available_display}"
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
matched = match_result.heading
|
|
110
|
+
confidence = match_result.confidence
|
|
111
|
+
|
|
112
|
+
# Check for ambiguous matches (multiple close matches)
|
|
113
|
+
if confidence < 1.0:
|
|
114
|
+
close = find_close_matches(
|
|
115
|
+
headings, after_heading, threshold=confidence - 0.1
|
|
116
|
+
)
|
|
117
|
+
if len(close) > 1 and close[1].confidence >= confidence - 0.05:
|
|
118
|
+
# Second match is very close to first - ambiguous
|
|
119
|
+
close_display = ", ".join(
|
|
120
|
+
f"'{m.heading_text}' ({int(m.confidence * 100)}%)"
|
|
121
|
+
for m in close[:3]
|
|
122
|
+
)
|
|
123
|
+
return (
|
|
124
|
+
f"Multiple sections closely match '{after_heading}' in {filename}: "
|
|
125
|
+
f"{close_display}. Please be more specific."
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# Find section boundaries
|
|
129
|
+
_start_line, end_line = find_section_bounds(
|
|
130
|
+
lines, matched.line_number, matched.level
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# Build insert content
|
|
134
|
+
normalized_content = normalize_section_content(content)
|
|
135
|
+
insert_content_lines = normalized_content.split("\n")
|
|
136
|
+
# Remove empty last line from split (since we added \n)
|
|
137
|
+
if insert_content_lines and insert_content_lines[-1] == "":
|
|
138
|
+
insert_content_lines.pop()
|
|
139
|
+
|
|
140
|
+
# Build the insert lines
|
|
141
|
+
insert_lines: list[str] = [""] # Blank line separator before new content
|
|
142
|
+
|
|
143
|
+
if new_heading:
|
|
144
|
+
insert_lines.append(new_heading)
|
|
145
|
+
insert_lines.append("") # Blank line after heading
|
|
146
|
+
|
|
147
|
+
insert_lines.extend(insert_content_lines)
|
|
148
|
+
|
|
149
|
+
# Add trailing blank line if not at EOF
|
|
150
|
+
if end_line < len(lines):
|
|
151
|
+
insert_lines.append("")
|
|
152
|
+
|
|
153
|
+
# Insert before section end (before next heading or EOF)
|
|
154
|
+
new_lines = lines[:end_line] + insert_lines + lines[end_line:]
|
|
155
|
+
|
|
156
|
+
# Join with detected line ending
|
|
157
|
+
new_content = line_ending.join(new_lines)
|
|
158
|
+
|
|
159
|
+
# Write file (newline="" preserves our chosen line endings)
|
|
160
|
+
async with aiofiles.open(file_path, "w", encoding="utf-8", newline="") as f:
|
|
161
|
+
await f.write(new_content)
|
|
162
|
+
|
|
163
|
+
# Track the file operation
|
|
164
|
+
ctx.deps.file_tracker.add_operation(file_path, FileOperationType.UPDATED)
|
|
165
|
+
|
|
166
|
+
logger.debug(
|
|
167
|
+
"Successfully inserted content into section '%s' in %s",
|
|
168
|
+
matched.text,
|
|
169
|
+
filename,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
lines_added = len(insert_lines)
|
|
173
|
+
confidence_display = f"{int(confidence * 100)}%"
|
|
174
|
+
|
|
175
|
+
if new_heading:
|
|
176
|
+
return (
|
|
177
|
+
f"Successfully inserted '{new_heading}' into '{matched.text}' in {filename} "
|
|
178
|
+
f"(matched with {confidence_display} confidence, {lines_added} lines added)"
|
|
179
|
+
)
|
|
180
|
+
else:
|
|
181
|
+
return (
|
|
182
|
+
f"Successfully inserted content into '{matched.text}' in {filename} "
|
|
183
|
+
f"(matched with {confidence_display} confidence, {lines_added} lines added)"
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
except ValueError as e:
|
|
187
|
+
# Path validation errors
|
|
188
|
+
error_msg = f"Error inserting into '{filename}': {e}"
|
|
189
|
+
logger.error("Section insertion failed: %s", error_msg)
|
|
190
|
+
return error_msg
|
|
191
|
+
|
|
192
|
+
except Exception as e:
|
|
193
|
+
error_msg = f"Error inserting into '{filename}': {e}"
|
|
194
|
+
logger.error("Section insertion failed: %s", error_msg)
|
|
195
|
+
return error_msg
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Pydantic models for markdown tools."""
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MarkdownHeading(BaseModel):
|
|
7
|
+
"""Represents a heading found in a Markdown file."""
|
|
8
|
+
|
|
9
|
+
line_number: int
|
|
10
|
+
text: str
|
|
11
|
+
level: int
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def normalized_text(self) -> str:
|
|
15
|
+
"""Return heading text without # prefix, stripped and lowercased."""
|
|
16
|
+
return self.text.lstrip("#").strip().lower()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
HeadingList = list[MarkdownHeading]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class HeadingMatch(BaseModel):
|
|
23
|
+
"""Result of a successful heading match."""
|
|
24
|
+
|
|
25
|
+
heading: MarkdownHeading
|
|
26
|
+
confidence: float
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class CloseMatch(BaseModel):
|
|
30
|
+
"""A close match result for error messages."""
|
|
31
|
+
|
|
32
|
+
heading_text: str
|
|
33
|
+
confidence: float
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"""Tool for replacing markdown sections."""
|
|
2
|
+
|
|
3
|
+
import aiofiles
|
|
4
|
+
import aiofiles.os
|
|
5
|
+
from pydantic_ai import RunContext
|
|
6
|
+
|
|
7
|
+
from shotgun.agents.models import AgentDeps, FileOperationType
|
|
8
|
+
from shotgun.agents.tools.file_management import _validate_agent_scoped_path
|
|
9
|
+
from shotgun.agents.tools.registry import ToolCategory, register_tool
|
|
10
|
+
from shotgun.logging_config import get_logger
|
|
11
|
+
|
|
12
|
+
from .utils import (
|
|
13
|
+
detect_line_ending,
|
|
14
|
+
extract_headings,
|
|
15
|
+
find_close_matches,
|
|
16
|
+
find_matching_heading,
|
|
17
|
+
find_section_bounds,
|
|
18
|
+
normalize_section_content,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
logger = get_logger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@register_tool(
|
|
25
|
+
category=ToolCategory.ARTIFACT_MANAGEMENT,
|
|
26
|
+
display_text="Replacing section",
|
|
27
|
+
key_arg="filename",
|
|
28
|
+
)
|
|
29
|
+
async def replace_markdown_section(
|
|
30
|
+
ctx: RunContext[AgentDeps],
|
|
31
|
+
filename: str,
|
|
32
|
+
section_heading: str,
|
|
33
|
+
new_contents: str,
|
|
34
|
+
new_heading: str | None = None,
|
|
35
|
+
) -> str:
|
|
36
|
+
"""Replace an entire section in a Markdown file.
|
|
37
|
+
|
|
38
|
+
PREFER THIS TOOL over rewriting the entire file - it is faster, less costly,
|
|
39
|
+
and less error-prone.
|
|
40
|
+
|
|
41
|
+
Uses fuzzy matching on headings so minor typos are tolerated.
|
|
42
|
+
Replaces from the target heading down to (but not including) the next
|
|
43
|
+
heading at the same or higher level.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
ctx: Run context with agent dependencies
|
|
47
|
+
filename: Path to the Markdown file (relative to .shotgun directory)
|
|
48
|
+
section_heading: The heading to find (e.g., '## Requirements'). Fuzzy matched.
|
|
49
|
+
new_contents: The new content for the section body (not including the heading)
|
|
50
|
+
new_heading: Optional new heading text to replace the old one
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Success message or error message
|
|
54
|
+
"""
|
|
55
|
+
logger.debug("Replacing section '%s' in: %s", section_heading, filename)
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
# Validate path with agent scoping
|
|
59
|
+
file_path = _validate_agent_scoped_path(filename, ctx.deps.agent_mode)
|
|
60
|
+
|
|
61
|
+
# Check if file exists
|
|
62
|
+
if not await aiofiles.os.path.exists(file_path):
|
|
63
|
+
return f"Error: File '{filename}' not found"
|
|
64
|
+
|
|
65
|
+
# Read file content (newline="" preserves original line endings)
|
|
66
|
+
async with aiofiles.open(file_path, encoding="utf-8", newline="") as f:
|
|
67
|
+
content = await f.read()
|
|
68
|
+
|
|
69
|
+
# Detect line ending style
|
|
70
|
+
line_ending = detect_line_ending(content)
|
|
71
|
+
lines = content.split("\n")
|
|
72
|
+
|
|
73
|
+
# Remove \r from lines if CRLF
|
|
74
|
+
if line_ending == "\r\n":
|
|
75
|
+
lines = [line.rstrip("\r") for line in lines]
|
|
76
|
+
|
|
77
|
+
# Extract headings
|
|
78
|
+
headings = extract_headings(content)
|
|
79
|
+
|
|
80
|
+
if not headings:
|
|
81
|
+
return f"Error: No headings found in '{filename}'. Cannot replace sections in files without headings."
|
|
82
|
+
|
|
83
|
+
# Find matching heading
|
|
84
|
+
match_result = find_matching_heading(headings, section_heading)
|
|
85
|
+
|
|
86
|
+
if match_result is None:
|
|
87
|
+
# No match found - provide helpful error with available headings
|
|
88
|
+
available = [h.text for h in headings]
|
|
89
|
+
close = find_close_matches(headings, section_heading)
|
|
90
|
+
|
|
91
|
+
if close and close[0].confidence >= 0.6:
|
|
92
|
+
# There are close matches but below threshold
|
|
93
|
+
close_display = ", ".join(
|
|
94
|
+
f"'{m.heading_text}' ({int(m.confidence * 100)}%)" for m in close
|
|
95
|
+
)
|
|
96
|
+
return (
|
|
97
|
+
f"No section matching '{section_heading}' found in {filename}. "
|
|
98
|
+
f"Did you mean: {close_display}"
|
|
99
|
+
)
|
|
100
|
+
else:
|
|
101
|
+
# List available headings
|
|
102
|
+
available_display = ", ".join(available[:5])
|
|
103
|
+
if len(available) > 5:
|
|
104
|
+
available_display += f" (+{len(available) - 5} more)"
|
|
105
|
+
return (
|
|
106
|
+
f"No section matching '{section_heading}' found in {filename}. "
|
|
107
|
+
f"Available headings: {available_display}"
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
matched = match_result.heading
|
|
111
|
+
confidence = match_result.confidence
|
|
112
|
+
|
|
113
|
+
# Check for ambiguous matches (multiple close matches)
|
|
114
|
+
if confidence < 1.0:
|
|
115
|
+
close = find_close_matches(
|
|
116
|
+
headings, section_heading, threshold=confidence - 0.1
|
|
117
|
+
)
|
|
118
|
+
if len(close) > 1 and close[1].confidence >= confidence - 0.05:
|
|
119
|
+
# Second match is very close to first - ambiguous
|
|
120
|
+
close_display = ", ".join(
|
|
121
|
+
f"'{m.heading_text}' ({int(m.confidence * 100)}%)"
|
|
122
|
+
for m in close[:3]
|
|
123
|
+
)
|
|
124
|
+
return (
|
|
125
|
+
f"Multiple sections closely match '{section_heading}' in {filename}: "
|
|
126
|
+
f"{close_display}. Please be more specific."
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# Find section boundaries
|
|
130
|
+
start_line, end_line = find_section_bounds(
|
|
131
|
+
lines, matched.line_number, matched.level
|
|
132
|
+
)
|
|
133
|
+
old_section_lines = end_line - start_line
|
|
134
|
+
|
|
135
|
+
# Build new section
|
|
136
|
+
final_heading = new_heading if new_heading else matched.text
|
|
137
|
+
normalized_content = normalize_section_content(new_contents)
|
|
138
|
+
|
|
139
|
+
# Split new content into lines
|
|
140
|
+
new_content_lines = normalized_content.split("\n")
|
|
141
|
+
# Remove empty last line from split (since we added \n)
|
|
142
|
+
if new_content_lines and new_content_lines[-1] == "":
|
|
143
|
+
new_content_lines.pop()
|
|
144
|
+
|
|
145
|
+
# Build the new section: heading + blank line + content
|
|
146
|
+
new_section_lines = [final_heading, ""]
|
|
147
|
+
new_section_lines.extend(new_content_lines)
|
|
148
|
+
|
|
149
|
+
# Add trailing blank line if not at EOF
|
|
150
|
+
if end_line < len(lines):
|
|
151
|
+
new_section_lines.append("")
|
|
152
|
+
|
|
153
|
+
# Replace section
|
|
154
|
+
new_lines = lines[:start_line] + new_section_lines + lines[end_line:]
|
|
155
|
+
|
|
156
|
+
# Join with detected line ending
|
|
157
|
+
new_content = line_ending.join(new_lines)
|
|
158
|
+
|
|
159
|
+
# Write file (newline="" preserves our chosen line endings)
|
|
160
|
+
async with aiofiles.open(file_path, "w", encoding="utf-8", newline="") as f:
|
|
161
|
+
await f.write(new_content)
|
|
162
|
+
|
|
163
|
+
# Track the file operation
|
|
164
|
+
ctx.deps.file_tracker.add_operation(file_path, FileOperationType.UPDATED)
|
|
165
|
+
|
|
166
|
+
logger.debug("Successfully replaced section '%s' in %s", matched.text, filename)
|
|
167
|
+
|
|
168
|
+
new_section_line_count = len(new_section_lines)
|
|
169
|
+
confidence_display = f"{int(confidence * 100)}%"
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
f"Successfully replaced section '{matched.text}' in {filename} "
|
|
173
|
+
f"(matched with {confidence_display} confidence, "
|
|
174
|
+
f"{old_section_lines} lines -> {new_section_line_count} lines)"
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
except ValueError as e:
|
|
178
|
+
# Path validation errors
|
|
179
|
+
error_msg = f"Error replacing section in '{filename}': {e}"
|
|
180
|
+
logger.error("Section replacement failed: %s", error_msg)
|
|
181
|
+
return error_msg
|
|
182
|
+
|
|
183
|
+
except Exception as e:
|
|
184
|
+
error_msg = f"Error replacing section in '{filename}': {e}"
|
|
185
|
+
logger.error("Section replacement failed: %s", error_msg)
|
|
186
|
+
return error_msg
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""Utility functions for markdown parsing and manipulation."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from difflib import SequenceMatcher
|
|
5
|
+
|
|
6
|
+
from .models import CloseMatch, HeadingList, HeadingMatch, MarkdownHeading
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_heading_level(line: str) -> int | None:
|
|
10
|
+
"""Get the heading level (1-6) from a line, or None if not a heading.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
line: A line of text to check
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
The heading level (1-6) or None if not a heading
|
|
17
|
+
"""
|
|
18
|
+
match = re.match(r"^(#{1,6})\s+", line)
|
|
19
|
+
return len(match.group(1)) if match else None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def extract_headings(content: str) -> HeadingList:
|
|
23
|
+
"""Extract all headings from markdown content.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
content: The markdown content to parse
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
List of MarkdownHeading objects
|
|
30
|
+
"""
|
|
31
|
+
headings: HeadingList = []
|
|
32
|
+
for i, line in enumerate(content.splitlines()):
|
|
33
|
+
level = get_heading_level(line)
|
|
34
|
+
if level is not None:
|
|
35
|
+
headings.append(MarkdownHeading(line_number=i, text=line, level=level))
|
|
36
|
+
return headings
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def find_matching_heading(
|
|
40
|
+
headings: HeadingList,
|
|
41
|
+
target: str,
|
|
42
|
+
threshold: float = 0.8,
|
|
43
|
+
) -> HeadingMatch | None:
|
|
44
|
+
"""Find the best matching heading above the similarity threshold.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
headings: List of MarkdownHeading objects
|
|
48
|
+
target: The target heading to match (e.g., "## Requirements")
|
|
49
|
+
threshold: Minimum similarity ratio (0.0-1.0)
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
HeadingMatch with the matched heading and confidence, or None if no match
|
|
53
|
+
"""
|
|
54
|
+
best_heading: MarkdownHeading | None = None
|
|
55
|
+
best_ratio = 0.0
|
|
56
|
+
|
|
57
|
+
# Normalize target: strip leading #s and whitespace, lowercase
|
|
58
|
+
norm_target = target.lstrip("#").strip().lower()
|
|
59
|
+
|
|
60
|
+
for heading in headings:
|
|
61
|
+
ratio = SequenceMatcher(None, heading.normalized_text, norm_target).ratio()
|
|
62
|
+
|
|
63
|
+
if ratio > best_ratio and ratio >= threshold:
|
|
64
|
+
best_ratio = ratio
|
|
65
|
+
best_heading = heading
|
|
66
|
+
|
|
67
|
+
if best_heading is not None:
|
|
68
|
+
return HeadingMatch(heading=best_heading, confidence=best_ratio)
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def find_close_matches(
|
|
73
|
+
headings: HeadingList,
|
|
74
|
+
target: str,
|
|
75
|
+
threshold: float = 0.6,
|
|
76
|
+
max_matches: int = 3,
|
|
77
|
+
) -> list[CloseMatch]:
|
|
78
|
+
"""Find headings that are close matches to the target.
|
|
79
|
+
|
|
80
|
+
Used for error messages when no exact match is found.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
headings: List of MarkdownHeading objects
|
|
84
|
+
target: The target heading to match
|
|
85
|
+
threshold: Minimum similarity ratio for inclusion
|
|
86
|
+
max_matches: Maximum number of matches to return
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
List of CloseMatch objects, sorted by confidence descending
|
|
90
|
+
"""
|
|
91
|
+
norm_target = target.lstrip("#").strip().lower()
|
|
92
|
+
matches: list[CloseMatch] = []
|
|
93
|
+
|
|
94
|
+
for heading in headings:
|
|
95
|
+
ratio = SequenceMatcher(None, heading.normalized_text, norm_target).ratio()
|
|
96
|
+
if ratio >= threshold:
|
|
97
|
+
matches.append(CloseMatch(heading_text=heading.text, confidence=ratio))
|
|
98
|
+
|
|
99
|
+
# Sort by confidence descending
|
|
100
|
+
matches.sort(key=lambda x: x.confidence, reverse=True)
|
|
101
|
+
return matches[:max_matches]
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def find_section_bounds(
|
|
105
|
+
lines: list[str],
|
|
106
|
+
heading_line_num: int,
|
|
107
|
+
heading_level: int,
|
|
108
|
+
) -> tuple[int, int]:
|
|
109
|
+
"""Find the boundaries of a section.
|
|
110
|
+
|
|
111
|
+
The section includes everything from the heading to the next heading
|
|
112
|
+
at the same or higher level (exclusive), or end of file.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
lines: All lines of the file
|
|
116
|
+
heading_line_num: Line number of the section heading
|
|
117
|
+
heading_level: Level of the section heading (1-6)
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Tuple of (start_line, end_line) where end_line is exclusive
|
|
121
|
+
"""
|
|
122
|
+
start = heading_line_num
|
|
123
|
+
end = len(lines) # Default to EOF
|
|
124
|
+
|
|
125
|
+
for i in range(heading_line_num + 1, len(lines)):
|
|
126
|
+
level = get_heading_level(lines[i])
|
|
127
|
+
if level is not None and level <= heading_level:
|
|
128
|
+
end = i
|
|
129
|
+
break
|
|
130
|
+
|
|
131
|
+
return (start, end)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def detect_line_ending(content: str) -> str:
|
|
135
|
+
"""Detect the line ending style used in the content.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
content: The file content
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
The line ending string ('\\r\\n' or '\\n')
|
|
142
|
+
"""
|
|
143
|
+
if "\r\n" in content:
|
|
144
|
+
return "\r\n"
|
|
145
|
+
return "\n"
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def normalize_section_content(content: str) -> str:
|
|
149
|
+
"""Normalize content to have no leading whitespace and single trailing newline.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
content: The content to normalize
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
Normalized content
|
|
156
|
+
"""
|
|
157
|
+
return content.strip() + "\n"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|