precis-cli 0.1.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- app/__init__.py +16 -0
- app/api/__init__.py +32 -0
- app/api/dependencies.py +185 -0
- app/api/main.py +301 -0
- app/api/middleware/__init__.py +34 -0
- app/api/middleware/exception_handler.py +126 -0
- app/api/middleware/request_logging.py +97 -0
- app/api/middleware/token_auth.py +215 -0
- app/api/models/__init__.py +151 -0
- app/api/models/connection_rules.py +98 -0
- app/api/models/files.py +99 -0
- app/api/models/full_validation.py +554 -0
- app/api/models/project.py +117 -0
- app/api/models/projects.py +80 -0
- app/api/models/schema.py +121 -0
- app/api/models/v2_responses.py +46 -0
- app/api/models/validation.py +303 -0
- app/api/models/workspace.py +118 -0
- app/api/routers/__init__.py +56 -0
- app/api/routers/ai/__init__.py +36 -0
- app/api/routers/ai/chat.py +243 -0
- app/api/routers/ai/generate.py +156 -0
- app/api/routers/ai/hardware.py +199 -0
- app/api/routers/ai/jobs.py +544 -0
- app/api/routers/ai/migrate.py +471 -0
- app/api/routers/ai/models.py +352 -0
- app/api/routers/ai/ollama.py +108 -0
- app/api/routers/ai/providers.py +491 -0
- app/api/routers/ai/router.py +44 -0
- app/api/routers/ai/stream.py +281 -0
- app/api/routers/ai/utils.py +182 -0
- app/api/routers/core/__init__.py +34 -0
- app/api/routers/core/connection_rules.py +188 -0
- app/api/routers/core/data_sources.py +374 -0
- app/api/routers/core/regex.py +333 -0
- app/api/routers/core/reporting.py +263 -0
- app/api/routers/files/__init__.py +24 -0
- app/api/routers/files/ops.py +179 -0
- app/api/routers/files/transfer.py +101 -0
- app/api/routers/preview/__init__.py +35 -0
- app/api/routers/preview/content_mode.py +264 -0
- app/api/routers/preview/header_row.py +154 -0
- app/api/routers/preview/models.py +81 -0
- app/api/routers/preview/path_mode.py +440 -0
- app/api/routers/preview/router.py +30 -0
- app/api/routers/project/__init__.py +61 -0
- app/api/routers/project/base.py +111 -0
- app/api/routers/project/constraint.py +345 -0
- app/api/routers/project/full_config.py +411 -0
- app/api/routers/project/full_config_writer.py +327 -0
- app/api/routers/project/helpers.py +167 -0
- app/api/routers/project/inspection_fix.py +330 -0
- app/api/routers/project/manifest.py +605 -0
- app/api/routers/project/models.py +177 -0
- app/api/routers/project/pattern.py +268 -0
- app/api/routers/project/regex.py +358 -0
- app/api/routers/project/scanner.py +157 -0
- app/api/routers/project/schema.py +567 -0
- app/api/routers/project/schema_helpers.py +149 -0
- app/api/routers/project/settings.py +399 -0
- app/api/routers/project/template.py +325 -0
- app/api/routers/project/validation.py +227 -0
- app/api/routers/project/view.py +177 -0
- app/api/routers/project/workspaces.py +167 -0
- app/api/routers/projects/__init__.py +25 -0
- app/api/routers/projects/create.py +121 -0
- app/api/routers/projects/open.py +103 -0
- app/api/routers/projects/scan.py +100 -0
- app/api/routers/validation/__init__.py +39 -0
- app/api/routers/validation/common.py +263 -0
- app/api/routers/validation/content_mode.py +401 -0
- app/api/routers/validation/history.py +235 -0
- app/api/routers/validation/inline_mode.py +196 -0
- app/api/routers/validation/path_mode.py +314 -0
- app/api/routers/validation/router.py +51 -0
- app/api/services/full_validation_response_builder.py +412 -0
- app/api/services/io_error_messages.py +41 -0
- app/api/services/preview_service.py +248 -0
- app/cli/__init__.py +18 -0
- app/cli/__main__.py +30 -0
- app/cli/shared_services/__init__.py +42 -0
- app/cli/shared_services/config_ops.py +581 -0
- app/cli/shared_services/generation_ops.py +160 -0
- app/cli/shared_services/project_ops.py +288 -0
- app/cli/shell/__init__.py +48 -0
- app/cli/shell/commands/__init__.py +63 -0
- app/cli/shell/commands/ai/__init__.py +260 -0
- app/cli/shell/commands/ai/chat.py +331 -0
- app/cli/shell/commands/ai/delete.py +165 -0
- app/cli/shell/commands/ai/diff.py +70 -0
- app/cli/shell/commands/ai/display.py +428 -0
- app/cli/shell/commands/ai/executor.py +225 -0
- app/cli/shell/commands/ai/executor_utils.py +271 -0
- app/cli/shell/commands/ai/generate.py +285 -0
- app/cli/shell/commands/ai/interaction.py +262 -0
- app/cli/shell/commands/ai/migrate.py +281 -0
- app/cli/shell/commands/ai/resolver.py +224 -0
- app/cli/shell/commands/ai/status.py +101 -0
- app/cli/shell/commands/ai/switch.py +158 -0
- app/cli/shell/commands/ai/utils.py +52 -0
- app/cli/shell/commands/base.py +343 -0
- app/cli/shell/commands/config/__init__.py +131 -0
- app/cli/shell/commands/config/base.py +80 -0
- app/cli/shell/commands/config/check.py +246 -0
- app/cli/shell/commands/config/edit.py +148 -0
- app/cli/shell/commands/config/get.py +111 -0
- app/cli/shell/commands/config/init.py +123 -0
- app/cli/shell/commands/config/inspect.py +164 -0
- app/cli/shell/commands/config/list.py +104 -0
- app/cli/shell/commands/config/set.py +119 -0
- app/cli/shell/commands/config/show.py +150 -0
- app/cli/shell/commands/exit.py +67 -0
- app/cli/shell/commands/help.py +104 -0
- app/cli/shell/commands/infer_schema.py +156 -0
- app/cli/shell/commands/open.py +201 -0
- app/cli/shell/commands/project.py +215 -0
- app/cli/shell/commands/provider.py +619 -0
- app/cli/shell/commands/system.py +170 -0
- app/cli/shell/commands/validate.py +455 -0
- app/cli/shell/completer.py +231 -0
- app/cli/shell/config_storage.py +191 -0
- app/cli/shell/exceptions.py +90 -0
- app/cli/shell/formatter.py +371 -0
- app/cli/shell/interactive_menu.py +348 -0
- app/cli/shell/main.py +286 -0
- app/cli/shell/parser.py +266 -0
- app/cli/start.py +180 -0
- app/cli_main.py +52 -0
- app/mcp_server.py +311 -0
- app/shared/core/__init__.py +18 -0
- app/shared/core/app_version.py +59 -0
- app/shared/core/config/__init__.py +119 -0
- app/shared/core/config/server.py +193 -0
- app/shared/core/data_source/__init__.py +106 -0
- app/shared/core/data_source/loader.py +348 -0
- app/shared/core/data_source/loaders/__init__.py +213 -0
- app/shared/core/data_source/loaders/base.py +215 -0
- app/shared/core/data_source/loaders/converter.py +337 -0
- app/shared/core/data_source/loaders/csv_loader.py +252 -0
- app/shared/core/data_source/loaders/excel_loader.py +666 -0
- app/shared/core/data_source/loaders/extractor.py +268 -0
- app/shared/core/data_source/loaders/json_loader.py +354 -0
- app/shared/core/data_source/loaders/registry.py +88 -0
- app/shared/core/data_source/loaders/sql_loader.py +313 -0
- app/shared/core/data_source/loaders/strategies/__init__.py +100 -0
- app/shared/core/data_source/loaders/strategies/array_parser.py +277 -0
- app/shared/core/data_source/loaders/strategies/lines_parser.py +355 -0
- app/shared/core/data_source/loaders/strategies/object_parser.py +278 -0
- app/shared/core/data_source/schema_info.py +50 -0
- app/shared/core/data_source/specs/__init__.py +128 -0
- app/shared/core/data_source/specs/base.py +258 -0
- app/shared/core/data_source/specs/csv_source.py +137 -0
- app/shared/core/data_source/specs/excel_source.py +141 -0
- app/shared/core/data_source/specs/file_base.py +252 -0
- app/shared/core/data_source/specs/json_source.py +226 -0
- app/shared/core/data_source/specs/sql_source.py +161 -0
- app/shared/core/encoding.py +36 -0
- app/shared/core/io/__init__.py +24 -0
- app/shared/core/io/yaml.py +379 -0
- app/shared/core/manifest_schema/__init__.py +52 -0
- app/shared/core/manifest_schema/types.py +99 -0
- app/shared/core/manifest_schema/version.py +175 -0
- app/shared/core/patterns/__init__.py +24 -0
- app/shared/core/patterns/loader.py +134 -0
- app/shared/core/patterns/writer.py +244 -0
- app/shared/core/project/__init__.py +47 -0
- app/shared/core/project/constraint/builders/__init__.py +50 -0
- app/shared/core/project/constraint/builders/base.py +100 -0
- app/shared/core/project/constraint/builders/composite.py +77 -0
- app/shared/core/project/constraint/builders/conditional.py +67 -0
- app/shared/core/project/constraint/builders/foreign_key.py +53 -0
- app/shared/core/project/constraint/builders/registry.py +64 -0
- app/shared/core/project/constraint/builders/scripted.py +51 -0
- app/shared/core/project/constraint/builders/single_column.py +86 -0
- app/shared/core/project/constraint/builders/unique.py +53 -0
- app/shared/core/project/constraint/factory.py +170 -0
- app/shared/core/project/constraint/reader.py +214 -0
- app/shared/core/project/constraint/registry.py +233 -0
- app/shared/core/project/constraint/types/__init__.py +63 -0
- app/shared/core/project/constraint/types/constraint_file.py +261 -0
- app/shared/core/project/constraint/types/refs.py +460 -0
- app/shared/core/project/constraint/types.py +28 -0
- app/shared/core/project/constraint/writer.py +181 -0
- app/shared/core/project/loader/__init__.py +56 -0
- app/shared/core/project/loader/loader.py +30 -0
- app/shared/core/project/loader/loader_parts/config_inspector.py +137 -0
- app/shared/core/project/loader/loader_parts/embedded_constraints.py +224 -0
- app/shared/core/project/loader/loader_parts/file_loaders.py +58 -0
- app/shared/core/project/loader/loader_parts/inspection_ids.py +85 -0
- app/shared/core/project/loader/loader_parts/inspector_helpers.py +312 -0
- app/shared/core/project/loader/loader_parts/inspector_id_checks.py +274 -0
- app/shared/core/project/loader/loader_parts/inspector_reference_checks.py +690 -0
- app/shared/core/project/loader/loader_parts/inspector_uniqueness_checks.py +286 -0
- app/shared/core/project/loader/loader_parts/loading_error_messages.py +298 -0
- app/shared/core/project/loader/loader_parts/main.py +432 -0
- app/shared/core/project/loader/loader_parts/path_validation.py +117 -0
- app/shared/core/project/loader/loader_parts/runtime.py +125 -0
- app/shared/core/project/loader/types.py +246 -0
- app/shared/core/project/manifest/coverage.py +391 -0
- app/shared/core/project/manifest/reader.py +260 -0
- app/shared/core/project/manifest/types.py +91 -0
- app/shared/core/project/manifest/types_parts/__init__.py +60 -0
- app/shared/core/project/manifest/types_parts/constants.py +40 -0
- app/shared/core/project/manifest/types_parts/data_source.py +68 -0
- app/shared/core/project/manifest/types_parts/info.py +59 -0
- app/shared/core/project/manifest/types_parts/manifest.py +294 -0
- app/shared/core/project/manifest/types_parts/refs.py +153 -0
- app/shared/core/project/manifest/types_parts/settings.py +92 -0
- app/shared/core/project/manifest/types_parts/settings_file_processing.py +64 -0
- app/shared/core/project/manifest/types_parts/settings_script_security.py +78 -0
- app/shared/core/project/manifest/types_parts/settings_validation.py +83 -0
- app/shared/core/project/manifest/types_parts/template.py +66 -0
- app/shared/core/project/manifest/writer.py +262 -0
- app/shared/core/project/manual_data/__init__.py +27 -0
- app/shared/core/project/manual_data/types.py +75 -0
- app/shared/core/project/regex/reader.py +197 -0
- app/shared/core/project/regex/types.py +405 -0
- app/shared/core/project/regex/writer.py +123 -0
- app/shared/core/project/schema/reader.py +170 -0
- app/shared/core/project/schema/types.py +47 -0
- app/shared/core/project/schema/types_parts/__init__.py +36 -0
- app/shared/core/project/schema/types_parts/column.py +174 -0
- app/shared/core/project/schema/types_parts/column_utils.py +72 -0
- app/shared/core/project/schema/types_parts/constraint.py +165 -0
- app/shared/core/project/schema/types_parts/schema_id.py +66 -0
- app/shared/core/project/schema/types_parts/source.py +255 -0
- app/shared/core/project/schema/types_parts/source_options.py +347 -0
- app/shared/core/project/schema/types_parts/table.py +230 -0
- app/shared/core/project/schema/writer.py +139 -0
- app/shared/core/project/schema_ref_check.py +95 -0
- app/shared/core/project/template/__init__.py +27 -0
- app/shared/core/project/template/expander.py +263 -0
- app/shared/core/project/template/reader.py +120 -0
- app/shared/core/project/template/types.py +114 -0
- app/shared/core/project/transform/reader.py +76 -0
- app/shared/core/project/transform/types.py +116 -0
- app/shared/core/project/transform/writer.py +84 -0
- app/shared/core/pydantic_messages.py +59 -0
- app/shared/core/reporter/__init__.py +46 -0
- app/shared/core/reporter/reporter.py +220 -0
- app/shared/core/reporter/reporters/__init__.py +65 -0
- app/shared/core/reporter/reporters/base.py +188 -0
- app/shared/core/reporter/reporters/dingtalk_app_reporter.py +274 -0
- app/shared/core/reporter/reporters/email_reporter.py +271 -0
- app/shared/core/reporter/reporters/feishu_app_reporter.py +467 -0
- app/shared/core/reporter/reporters/local_file_reporter.py +208 -0
- app/shared/core/reporter/reporters/wecom_app_reporter.py +268 -0
- app/shared/core/utils/__init__.py +18 -0
- app/shared/core/utils/path_utils.py +60 -0
- app/shared/core/utils/regex_extract.py +113 -0
- app/shared/domain/__init__.py +114 -0
- app/shared/domain/constraints/__init__.py +76 -0
- app/shared/domain/constraints/allowed_values.py +211 -0
- app/shared/domain/constraints/base.py +141 -0
- app/shared/domain/constraints/charset.py +337 -0
- app/shared/domain/constraints/composite.py +174 -0
- app/shared/domain/constraints/condition_registry.py +130 -0
- app/shared/domain/constraints/conditional.py +629 -0
- app/shared/domain/constraints/date_logic.py +731 -0
- app/shared/domain/constraints/foreign_key.py +261 -0
- app/shared/domain/constraints/key_normalization.py +56 -0
- app/shared/domain/constraints/not_null.py +185 -0
- app/shared/domain/constraints/range.py +360 -0
- app/shared/domain/constraints/regex.py +218 -0
- app/shared/domain/constraints/scripted.py +276 -0
- app/shared/domain/constraints/unique.py +233 -0
- app/shared/domain/data_engine.py +384 -0
- app/shared/domain/data_types.py +113 -0
- app/shared/domain/data_types_parts/__init__.py +67 -0
- app/shared/domain/data_types_parts/base.py +204 -0
- app/shared/domain/data_types_parts/composite.py +250 -0
- app/shared/domain/data_types_parts/expression.py +202 -0
- app/shared/domain/data_types_parts/extracted.py +106 -0
- app/shared/domain/data_types_parts/json_types.py +234 -0
- app/shared/domain/data_types_parts/scalars.py +719 -0
- app/shared/domain/data_types_parts/sequence.py +129 -0
- app/shared/domain/dataset_schema.py +48 -0
- app/shared/domain/eval_sandbox.py +63 -0
- app/shared/domain/expression_system.py +366 -0
- app/shared/domain/regex_flags.py +53 -0
- app/shared/domain/schema/builder.py +223 -0
- app/shared/domain/schema/models.py +339 -0
- app/shared/domain/transforms/__init__.py +28 -0
- app/shared/domain/transforms/aggregate.py +141 -0
- app/shared/domain/transforms/base.py +171 -0
- app/shared/domain/transforms/cast_type.py +120 -0
- app/shared/domain/transforms/concat.py +103 -0
- app/shared/domain/transforms/conditional_assign.py +114 -0
- app/shared/domain/transforms/date_format.py +81 -0
- app/shared/domain/transforms/digits.py +77 -0
- app/shared/domain/transforms/drop_duplicates.py +94 -0
- app/shared/domain/transforms/fill_na.py +94 -0
- app/shared/domain/transforms/filter_rows.py +97 -0
- app/shared/domain/transforms/lookup.py +78 -0
- app/shared/domain/transforms/lower_case.py +70 -0
- app/shared/domain/transforms/map_value.py +90 -0
- app/shared/domain/transforms/math_expr.py +112 -0
- app/shared/domain/transforms/modulo.py +82 -0
- app/shared/domain/transforms/regex_extract.py +103 -0
- app/shared/domain/transforms/registry.py +95 -0
- app/shared/domain/transforms/replace.py +88 -0
- app/shared/domain/transforms/sort_rows.py +94 -0
- app/shared/domain/transforms/string_split.py +84 -0
- app/shared/domain/transforms/strip.py +73 -0
- app/shared/domain/transforms/substring.py +92 -0
- app/shared/domain/transforms/upper_case.py +70 -0
- app/shared/domain/transforms/weighted_sum.py +104 -0
- app/shared/domain/validation_constraints.py +69 -0
- app/shared/services/__init__.py +62 -0
- app/shared/services/ai/__init__.py +48 -0
- app/shared/services/ai/agent/__init__.py +40 -0
- app/shared/services/ai/agent/chat_tools/__init__.py +47 -0
- app/shared/services/ai/agent/chat_tools/apply_actions.py +592 -0
- app/shared/services/ai/agent/chat_tools/ask_user.py +237 -0
- app/shared/services/ai/agent/chat_tools/read_canvas.py +140 -0
- app/shared/services/ai/agent/chat_tools/read_project.py +160 -0
- app/shared/services/ai/agent/chat_tools/read_table.py +301 -0
- app/shared/services/ai/agent/chat_tools/schemas.py +105 -0
- app/shared/services/ai/agent/chat_tools/validate_table.py +131 -0
- app/shared/services/ai/agent/executor.py +554 -0
- app/shared/services/ai/agent/memory.py +256 -0
- app/shared/services/ai/agent/planner.py +282 -0
- app/shared/services/ai/agent/tool_registry.py +296 -0
- app/shared/services/ai/agent/tools/__init__.py +32 -0
- app/shared/services/ai/agent/tools/config_generate.py +133 -0
- app/shared/services/ai/agent/tools/config_refine.py +109 -0
- app/shared/services/ai/agent/tools/config_validate.py +403 -0
- app/shared/services/ai/agent/tools/merge_results.py +136 -0
- app/shared/services/ai/agent/tools/plan_chunks.py +79 -0
- app/shared/services/ai/agent/tools/script_parse.py +359 -0
- app/shared/services/ai/agent/types.py +138 -0
- app/shared/services/ai/chat_agent_runner.py +596 -0
- app/shared/services/ai/chat_orchestrator.py +725 -0
- app/shared/services/ai/failure_messages.py +65 -0
- app/shared/services/ai/job_storage.py +274 -0
- app/shared/services/ai/migrate_service.py +550 -0
- app/shared/services/ai/streaming/__init__.py +65 -0
- app/shared/services/ai/streaming/event_journal.py +197 -0
- app/shared/services/ai/streaming/orchestrator.py +262 -0
- app/shared/services/ai/streaming/pending_interaction_store.py +227 -0
- app/shared/services/ai/streaming/sse_response.py +148 -0
- app/shared/services/ai/streaming/types.py +73 -0
- app/shared/services/ai/types.py +213 -0
- app/shared/services/ai/utils.py +322 -0
- app/shared/services/diff/config_diff.py +320 -0
- app/shared/services/hardware.py +237 -0
- app/shared/services/llm/__init__.py +62 -0
- app/shared/services/llm/actions/__init__.py +42 -0
- app/shared/services/llm/actions/_canvas_validator.py +147 -0
- app/shared/services/llm/actions/_constraint_validator.py +401 -0
- app/shared/services/llm/actions/_regex_validator.py +85 -0
- app/shared/services/llm/actions/_schema_validator.py +82 -0
- app/shared/services/llm/actions/_settings_validator.py +76 -0
- app/shared/services/llm/actions/_transform_validator.py +78 -0
- app/shared/services/llm/actions/action_handlers.py +400 -0
- app/shared/services/llm/actions/action_parser.py +63 -0
- app/shared/services/llm/actions/action_processor.py +468 -0
- app/shared/services/llm/actions/action_validator.py +311 -0
- app/shared/services/llm/actions/diff_compute.py +195 -0
- app/shared/services/llm/actions/regex_handlers.py +284 -0
- app/shared/services/llm/actions/registry.py +336 -0
- app/shared/services/llm/actions/schema_handlers.py +335 -0
- app/shared/services/llm/actions/settings_handlers.py +180 -0
- app/shared/services/llm/actions/specs.py +259 -0
- app/shared/services/llm/actions/transform_handlers.py +279 -0
- app/shared/services/llm/actions/validation_types.py +147 -0
- app/shared/services/llm/cache/__init__.py +18 -0
- app/shared/services/llm/cache/response_cache.py +80 -0
- app/shared/services/llm/chat/__init__.py +35 -0
- app/shared/services/llm/chat/chat_system_prompt.py +561 -0
- app/shared/services/llm/chat/response_parser.py +296 -0
- app/shared/services/llm/config/__init__.py +45 -0
- app/shared/services/llm/config/crypto.py +135 -0
- app/shared/services/llm/config/loader.py +258 -0
- app/shared/services/llm/config/models.py +202 -0
- app/shared/services/llm/config/presets.py +128 -0
- app/shared/services/llm/config_generator.py +163 -0
- app/shared/services/llm/constraints/__init__.py +41 -0
- app/shared/services/llm/constraints/constraint_builder.py +177 -0
- app/shared/services/llm/constraints/constraint_deletion.py +84 -0
- app/shared/services/llm/constraints/constraint_id.py +174 -0
- app/shared/services/llm/constraints/frontend_instructions.py +333 -0
- app/shared/services/llm/constraints/inline_batch.py +279 -0
- app/shared/services/llm/discovery/__init__.py +35 -0
- app/shared/services/llm/discovery/scanner.py +181 -0
- app/shared/services/llm/generation/__init__.py +42 -0
- app/shared/services/llm/generation/agent_wiring.py +156 -0
- app/shared/services/llm/generation/config_builder.py +386 -0
- app/shared/services/llm/generation/errors.py +36 -0
- app/shared/services/llm/generation/existing_config.py +99 -0
- app/shared/services/llm/generation/profiler.py +279 -0
- app/shared/services/llm/generation/prompt_builder.py +199 -0
- app/shared/services/llm/generation/response_parser.py +144 -0
- app/shared/services/llm/generation/service.py +622 -0
- app/shared/services/llm/models.py +104 -0
- app/shared/services/llm/providers/__init__.py +48 -0
- app/shared/services/llm/providers/base.py +310 -0
- app/shared/services/llm/providers/cached_provider.py +90 -0
- app/shared/services/llm/providers/ollama.py +498 -0
- app/shared/services/llm/providers/openai.py +334 -0
- app/shared/services/llm/providers/registry.py +108 -0
- app/shared/services/llm/schema_resolver.py +141 -0
- app/shared/services/llm/suggestion_utils.py +219 -0
- app/shared/services/llm/validate_executor.py +163 -0
- app/shared/services/llm/yaml_io.py +406 -0
- app/shared/services/preview/__init__.py +18 -0
- app/shared/services/preview/loader.py +145 -0
- app/shared/services/preview/path_validation.py +138 -0
- app/shared/services/project_loader.py +57 -0
- app/shared/services/schema_inference.py +260 -0
- app/shared/services/schema_runtime_builder.py +120 -0
- app/shared/services/validation/__init__.py +52 -0
- app/shared/services/validation/chunked_loader.py +509 -0
- app/shared/services/validation/dag/__init__.py +35 -0
- app/shared/services/validation/dag/builder.py +141 -0
- app/shared/services/validation/dag/executor.py +225 -0
- app/shared/services/validation/dag/sorter.py +77 -0
- app/shared/services/validation/data_loader.py +231 -0
- app/shared/services/validation/engine.py +446 -0
- app/shared/services/validation/executor.py +1030 -0
- app/shared/services/validation/extractors.py +266 -0
- app/shared/services/validation/history.py +209 -0
- app/shared/services/validation/json_payload.py +136 -0
- app/shared/services/validation/loader.py +152 -0
- app/shared/services/validation/memory_monitor.py +179 -0
- app/shared/services/validation/postprocess.py +208 -0
- app/shared/services/validation/progress.py +64 -0
- app/shared/services/validation/report_export.py +222 -0
- app/shared/services/validation/resolver.py +180 -0
- app/shared/services/validation/service.py +418 -0
- app/shared/services/validation/types.py +238 -0
- app/shared/services/validation/validators/__init__.py +36 -0
- app/shared/services/validation/validators/adapter.py +182 -0
- app/shared/services/validation/validators/base.py +332 -0
- app/shared/services/validation/validators/composite.py +233 -0
- app/shared/services/validation/validators/date_logic.py +276 -0
- app/start_server.py +133 -0
- precis_cli-0.1.3.dist-info/METADATA +180 -0
- precis_cli-0.1.3.dist-info/RECORD +442 -0
- precis_cli-0.1.3.dist-info/WHEEL +5 -0
- precis_cli-0.1.3.dist-info/entry_points.txt +4 -0
- precis_cli-0.1.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2026 Precis Team
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
"""
|
|
17
|
+
@fileoverview 动作预验证器模块
|
|
18
|
+
|
|
19
|
+
功能概述:
|
|
20
|
+
- 在 AI 生成的动作执行前进行业务逻辑验证
|
|
21
|
+
- 提前发现表/字段不存在、约束类型不支持、参数缺失等问题
|
|
22
|
+
- 避免执行时失败,提升用户体验
|
|
23
|
+
- 支持错误分级:errors(阻止执行)和 warnings(提醒但不阻止)
|
|
24
|
+
|
|
25
|
+
架构设计:
|
|
26
|
+
- 与项目结构联动:加载 schemas 目录下的 YAML 文件构建表/字段索引
|
|
27
|
+
- 约束类型白名单:VALID_CONSTRAINT_TYPES 定义支持的约束类型
|
|
28
|
+
- 参数完整性检查:CONSTRAINT_REQUIRED_PARAMS 定义各类型必填参数
|
|
29
|
+
- 返回结构化结果:ValidationResult 包含 errors、warnings、valid_actions
|
|
30
|
+
|
|
31
|
+
输入示例:
|
|
32
|
+
actions = [
|
|
33
|
+
{
|
|
34
|
+
"action": "create_constraint",
|
|
35
|
+
"tableName": "users",
|
|
36
|
+
"constraintType": "Unique",
|
|
37
|
+
"columnNames": ["email"]
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
validator = ActionValidator("/path/to/project")
|
|
41
|
+
result = validator.validate(actions)
|
|
42
|
+
|
|
43
|
+
输出示例:
|
|
44
|
+
ValidationResult(
|
|
45
|
+
errors=[ValidationError(action_index=0, action_type="create_constraint",
|
|
46
|
+
error_type="column_not_found", message="字段 email 不存在")],
|
|
47
|
+
warnings=[],
|
|
48
|
+
valid_actions=[],
|
|
49
|
+
invalid_action_indices={0}
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
实现说明:
|
|
53
|
+
- 数据类 (ValidationError/ValidationResult) 与格式化函数已提取到 validation_types.py
|
|
54
|
+
- 各动作类型验证逻辑已拆分到 _constraint_validator / _schema_validator /
|
|
55
|
+
_regex_validator / _transform_validator / _settings_validator
|
|
56
|
+
- 本模块保留 ActionValidator 主类,作为门面委托给上述子模块
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
from __future__ import annotations
|
|
60
|
+
|
|
61
|
+
import logging
|
|
62
|
+
from pathlib import Path
|
|
63
|
+
from typing import Any
|
|
64
|
+
|
|
65
|
+
from app.shared.services.llm.actions._canvas_validator import validate_canvas_action
|
|
66
|
+
from app.shared.services.llm.actions._constraint_validator import (
|
|
67
|
+
validate_constraint_action,
|
|
68
|
+
validate_foreign_key_reference,
|
|
69
|
+
validate_type_compatibility,
|
|
70
|
+
validate_validate_action,
|
|
71
|
+
)
|
|
72
|
+
from app.shared.services.llm.actions._regex_validator import validate_regex_action
|
|
73
|
+
from app.shared.services.llm.actions._schema_validator import validate_schema_action
|
|
74
|
+
from app.shared.services.llm.actions._settings_validator import validate_settings_action
|
|
75
|
+
from app.shared.services.llm.actions._transform_validator import validate_transform_action
|
|
76
|
+
from app.shared.services.llm.actions.registry import (
|
|
77
|
+
ALL_CONSTRAINT_TYPES,
|
|
78
|
+
REGEX_ACTION_TYPES,
|
|
79
|
+
SCHEMA_ACTION_TYPES,
|
|
80
|
+
SETTINGS_CATEGORIES,
|
|
81
|
+
TRANSFORM_ACTION_TYPES,
|
|
82
|
+
)
|
|
83
|
+
from app.shared.services.llm.actions.registry import CONSTRAINT_REQUIRED_PARAMS as CONSTRAINT_REQUIRED_PARAMS_REGISTRY
|
|
84
|
+
from app.shared.services.llm.actions.specs import SpecParseError, parse_action_spec
|
|
85
|
+
from app.shared.services.llm.actions.validation_types import (
|
|
86
|
+
ValidationError,
|
|
87
|
+
ValidationResult,
|
|
88
|
+
format_validation_result,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# 重新导出以保持向后兼容
|
|
92
|
+
__all__ = [
|
|
93
|
+
"ActionValidator",
|
|
94
|
+
"ValidationError",
|
|
95
|
+
"ValidationResult",
|
|
96
|
+
"format_validation_result",
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
logger = logging.getLogger(__name__)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class ActionValidator:
|
|
103
|
+
"""
|
|
104
|
+
@classdesc 动作预验证器
|
|
105
|
+
|
|
106
|
+
验证 AI 生成的动作在执行前的业务逻辑可行性。
|
|
107
|
+
加载项目 schema,检查表/字段存在性、约束类型有效性、参数完整性等。
|
|
108
|
+
|
|
109
|
+
Attributes:
|
|
110
|
+
project_path: 项目路径
|
|
111
|
+
_project_schema: 缓存的项目结构信息
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
# 以下白名单均从动作注册表(单一事实源)派生,禁止本地硬编码。
|
|
115
|
+
# 保留类属性形式以兼容现有引用(如 _constraint_validator 经回调注入使用)。
|
|
116
|
+
VALID_CONSTRAINT_TYPES = set(ALL_CONSTRAINT_TYPES)
|
|
117
|
+
VALID_SCHEMA_TYPES = set(SCHEMA_ACTION_TYPES)
|
|
118
|
+
VALID_REGEX_TYPES = set(REGEX_ACTION_TYPES)
|
|
119
|
+
VALID_TRANSFORM_TYPES = set(TRANSFORM_ACTION_TYPES)
|
|
120
|
+
VALID_SETTINGS_CATEGORIES = set(SETTINGS_CATEGORIES)
|
|
121
|
+
|
|
122
|
+
# 需要特定参数的约束类型(从注册表派生)
|
|
123
|
+
CONSTRAINT_REQUIRED_PARAMS = dict(CONSTRAINT_REQUIRED_PARAMS_REGISTRY)
|
|
124
|
+
|
|
125
|
+
def __init__(self, project_path: str):
|
|
126
|
+
self.project_path = Path(project_path)
|
|
127
|
+
self._project_schema: dict[str, Any] | None = None
|
|
128
|
+
|
|
129
|
+
def _load_project_schema(self) -> dict[str, Any]:
|
|
130
|
+
"""加载项目结构信息
|
|
131
|
+
|
|
132
|
+
读取 schemas 目录下的 YAML 文件,构建表和字段索引。
|
|
133
|
+
结果会被缓存。
|
|
134
|
+
"""
|
|
135
|
+
if self._project_schema is not None:
|
|
136
|
+
return self._project_schema
|
|
137
|
+
|
|
138
|
+
schema: dict[str, Any] = {
|
|
139
|
+
"tables": {},
|
|
140
|
+
"table_name_to_id": {},
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
schemas_dir = self.project_path / "schemas"
|
|
144
|
+
if not schemas_dir.exists():
|
|
145
|
+
logger.warning(f"Schemas 目录不存在: {schemas_dir}")
|
|
146
|
+
self._project_schema = schema
|
|
147
|
+
return schema
|
|
148
|
+
|
|
149
|
+
try:
|
|
150
|
+
import yaml
|
|
151
|
+
|
|
152
|
+
for schema_file in schemas_dir.glob("*.yaml"):
|
|
153
|
+
try:
|
|
154
|
+
with open(schema_file, encoding="utf-8") as f:
|
|
155
|
+
data = yaml.safe_load(f) or {}
|
|
156
|
+
|
|
157
|
+
table_id = data.get("id", "")
|
|
158
|
+
table_name = data.get("name", "")
|
|
159
|
+
columns = data.get("columns", [])
|
|
160
|
+
|
|
161
|
+
if table_id:
|
|
162
|
+
column_info = {}
|
|
163
|
+
for col in columns:
|
|
164
|
+
col_id = col.get("id", col.get("name", ""))
|
|
165
|
+
col_name = col.get("name", col_id)
|
|
166
|
+
col_type = col.get("type", "string")
|
|
167
|
+
if col_id:
|
|
168
|
+
column_info[col_id] = {
|
|
169
|
+
"name": col_name,
|
|
170
|
+
"type": col_type,
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
schema["tables"][table_id] = {
|
|
174
|
+
"name": table_name,
|
|
175
|
+
"columns": column_info,
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if table_name:
|
|
179
|
+
if table_name not in schema["table_name_to_id"]:
|
|
180
|
+
schema["table_name_to_id"][table_name] = []
|
|
181
|
+
schema["table_name_to_id"][table_name].append(table_id)
|
|
182
|
+
|
|
183
|
+
except Exception as e:
|
|
184
|
+
logger.debug(f"读取 schema 文件失败 {schema_file}: {e}")
|
|
185
|
+
|
|
186
|
+
except Exception as e:
|
|
187
|
+
logger.error(f"加载项目 schema 失败: {e}")
|
|
188
|
+
|
|
189
|
+
self._project_schema = schema
|
|
190
|
+
return schema
|
|
191
|
+
|
|
192
|
+
def _fk_check(self, spec: dict[str, Any], index: int, action_type: str) -> list[ValidationError]:
|
|
193
|
+
"""外键引用检查(注入到约束验证的回调中)"""
|
|
194
|
+
return validate_foreign_key_reference(spec, index, action_type, self._load_project_schema())
|
|
195
|
+
|
|
196
|
+
def _type_compat_check(
|
|
197
|
+
self, column_info: dict[str, Any], constraint_type: str, index: int, action_type: str
|
|
198
|
+
) -> list[ValidationError]:
|
|
199
|
+
"""类型兼容性检查(注入到约束验证的回调中)"""
|
|
200
|
+
return validate_type_compatibility(column_info, constraint_type, index, action_type)
|
|
201
|
+
|
|
202
|
+
def validate(self, actions: list[dict[str, Any]]) -> ValidationResult:
|
|
203
|
+
"""验证动作列表
|
|
204
|
+
|
|
205
|
+
遍历所有动作,根据动作类型调用对应的验证方法。
|
|
206
|
+
"""
|
|
207
|
+
result = ValidationResult()
|
|
208
|
+
schema = self._load_project_schema()
|
|
209
|
+
|
|
210
|
+
for index, action in enumerate(actions):
|
|
211
|
+
action_type = action.get("actionType", "")
|
|
212
|
+
|
|
213
|
+
# 结构校验前置(Pydantic):枚举、必填、Range min<=max 等不依赖项目状态的规则。
|
|
214
|
+
# 失败则跳过该动作的上下文校验(数据已非法,上下文校验无意义),直接标记无效。
|
|
215
|
+
# 这是 specs.py 与各 validator 的分工:specs 管结构,validator 管上下文(表/列/FK 存在性)。
|
|
216
|
+
try:
|
|
217
|
+
parse_action_spec(action)
|
|
218
|
+
except SpecParseError as e:
|
|
219
|
+
result.errors.append(
|
|
220
|
+
ValidationError(
|
|
221
|
+
action_index=index,
|
|
222
|
+
action_type=action_type,
|
|
223
|
+
error_type="spec_structure_invalid",
|
|
224
|
+
message=e.message,
|
|
225
|
+
suggestion="请检查 spec 字段结构(类型枚举、必填字段、参数关系)",
|
|
226
|
+
)
|
|
227
|
+
)
|
|
228
|
+
result.invalid_action_indices.add(index)
|
|
229
|
+
continue
|
|
230
|
+
|
|
231
|
+
if action_type in [
|
|
232
|
+
"ADD_CONSTRAINT_NODE",
|
|
233
|
+
"UPDATE_CONSTRAINT_NODE",
|
|
234
|
+
"DELETE_CONSTRAINT_NODE",
|
|
235
|
+
]:
|
|
236
|
+
errors = validate_constraint_action(
|
|
237
|
+
action,
|
|
238
|
+
schema,
|
|
239
|
+
index,
|
|
240
|
+
self.CONSTRAINT_REQUIRED_PARAMS,
|
|
241
|
+
self._type_compat_check,
|
|
242
|
+
self._fk_check,
|
|
243
|
+
)
|
|
244
|
+
result.errors.extend(errors)
|
|
245
|
+
if errors:
|
|
246
|
+
result.invalid_action_indices.add(index)
|
|
247
|
+
else:
|
|
248
|
+
result.valid_actions.append(action)
|
|
249
|
+
|
|
250
|
+
elif action_type == "VALIDATE_PROJECT":
|
|
251
|
+
errors = validate_validate_action(action, schema, index)
|
|
252
|
+
result.errors.extend(errors)
|
|
253
|
+
if errors:
|
|
254
|
+
result.invalid_action_indices.add(index)
|
|
255
|
+
else:
|
|
256
|
+
result.valid_actions.append(action)
|
|
257
|
+
|
|
258
|
+
elif action_type in self.VALID_SCHEMA_TYPES:
|
|
259
|
+
errors = validate_schema_action(action, index)
|
|
260
|
+
result.errors.extend(errors)
|
|
261
|
+
if errors:
|
|
262
|
+
result.invalid_action_indices.add(index)
|
|
263
|
+
else:
|
|
264
|
+
result.valid_actions.append(action)
|
|
265
|
+
|
|
266
|
+
elif action_type in self.VALID_REGEX_TYPES:
|
|
267
|
+
errors = validate_regex_action(action, index)
|
|
268
|
+
result.errors.extend(errors)
|
|
269
|
+
if errors:
|
|
270
|
+
result.invalid_action_indices.add(index)
|
|
271
|
+
else:
|
|
272
|
+
result.valid_actions.append(action)
|
|
273
|
+
|
|
274
|
+
elif action_type in self.VALID_TRANSFORM_TYPES:
|
|
275
|
+
errors = validate_transform_action(action, index)
|
|
276
|
+
result.errors.extend(errors)
|
|
277
|
+
if errors:
|
|
278
|
+
result.invalid_action_indices.add(index)
|
|
279
|
+
else:
|
|
280
|
+
result.valid_actions.append(action)
|
|
281
|
+
|
|
282
|
+
elif action_type == "UPDATE_SETTINGS":
|
|
283
|
+
errors = validate_settings_action(action, index)
|
|
284
|
+
result.errors.extend(errors)
|
|
285
|
+
if errors:
|
|
286
|
+
result.invalid_action_indices.add(index)
|
|
287
|
+
else:
|
|
288
|
+
result.valid_actions.append(action)
|
|
289
|
+
|
|
290
|
+
elif action_type == "ADD_TO_CANVAS":
|
|
291
|
+
# ADD_TO_CANVAS 不写盘,但仍需校验目标资源真实存在(避免显示不存在的资源)
|
|
292
|
+
errors = validate_canvas_action(action, index, str(self.project_path))
|
|
293
|
+
result.errors.extend(errors)
|
|
294
|
+
if errors:
|
|
295
|
+
result.invalid_action_indices.add(index)
|
|
296
|
+
else:
|
|
297
|
+
result.valid_actions.append(action)
|
|
298
|
+
|
|
299
|
+
else:
|
|
300
|
+
result.warnings.append(
|
|
301
|
+
ValidationError(
|
|
302
|
+
action_index=index,
|
|
303
|
+
action_type=action_type,
|
|
304
|
+
error_type="unknown_action_type",
|
|
305
|
+
message=f"未知的动作类型: {action_type}",
|
|
306
|
+
suggestion="系统可能不支持此操作,请检查 AI 响应",
|
|
307
|
+
)
|
|
308
|
+
)
|
|
309
|
+
result.valid_actions.append(action)
|
|
310
|
+
|
|
311
|
+
return result
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2026 Precis Team
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
"""@fileoverview apply_actions dry-run diff 计算
|
|
17
|
+
|
|
18
|
+
shadow-copy 配置目录到临时目录 → process_actions(临时目录) → unified diff。
|
|
19
|
+
dry-run 不触碰真实项目文件。
|
|
20
|
+
|
|
21
|
+
核心函数:
|
|
22
|
+
- compute_action_diff: 主入口,返回 DiffResult
|
|
23
|
+
- _shadow_copy: 拷贝配置目录到临时位置
|
|
24
|
+
- _build_file_diff: 生成单个文件的 unified diff
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import difflib
|
|
30
|
+
import logging
|
|
31
|
+
import os
|
|
32
|
+
import shutil
|
|
33
|
+
import tempfile
|
|
34
|
+
from dataclasses import dataclass, field
|
|
35
|
+
from typing import Any
|
|
36
|
+
|
|
37
|
+
from app.shared.services.llm.actions.action_processor import (
|
|
38
|
+
_collect_affected_files,
|
|
39
|
+
_snapshot_resource_files,
|
|
40
|
+
process_actions,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
logger = logging.getLogger(__name__)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class FileDiff:
|
|
48
|
+
"""单个文件的 diff 信息。"""
|
|
49
|
+
|
|
50
|
+
path: str
|
|
51
|
+
status: str # modified|created|deleted
|
|
52
|
+
diff: str
|
|
53
|
+
before_preview: str = ""
|
|
54
|
+
after_preview: str = ""
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass
|
|
58
|
+
class DiffResult:
|
|
59
|
+
"""dry-run diff 计算结果。"""
|
|
60
|
+
|
|
61
|
+
files: list[FileDiff] = field(default_factory=list)
|
|
62
|
+
summary: dict[str, int] = field(default_factory=lambda: {"modified": 0, "created": 0, "deleted": 0})
|
|
63
|
+
frontend_instructions: list[Any] = field(default_factory=list)
|
|
64
|
+
success: bool = True
|
|
65
|
+
error: str | None = None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def compute_action_diff(actions: list[dict[str, Any]], workspace_path: str) -> DiffResult:
|
|
69
|
+
"""计算 actions 对配置文件的预期变更(dry-run)。
|
|
70
|
+
|
|
71
|
+
参数:
|
|
72
|
+
actions: LLM 生成的 actions 列表
|
|
73
|
+
workspace_path: 项目配置目录路径
|
|
74
|
+
|
|
75
|
+
返回:
|
|
76
|
+
DiffResult: 含 file diffs、summary、frontend_instructions
|
|
77
|
+
"""
|
|
78
|
+
result = DiffResult()
|
|
79
|
+
|
|
80
|
+
affected = _collect_affected_files(actions, workspace_path)
|
|
81
|
+
workspace_abs = os.path.abspath(workspace_path)
|
|
82
|
+
|
|
83
|
+
before_contents: dict[str, str | None] = {}
|
|
84
|
+
rel_paths: list[str] = []
|
|
85
|
+
|
|
86
|
+
for abs_p in affected:
|
|
87
|
+
rel = os.path.relpath(abs_p, workspace_abs)
|
|
88
|
+
rel_paths.append(rel)
|
|
89
|
+
try:
|
|
90
|
+
with open(abs_p, encoding="utf-8") as f:
|
|
91
|
+
before_contents[rel] = f.read()
|
|
92
|
+
except FileNotFoundError:
|
|
93
|
+
before_contents[rel] = None
|
|
94
|
+
except OSError:
|
|
95
|
+
before_contents[rel] = ""
|
|
96
|
+
|
|
97
|
+
tmp_root = tempfile.mkdtemp(prefix="precis_dryrun_")
|
|
98
|
+
try:
|
|
99
|
+
_shadow_copy(workspace_path, tmp_root)
|
|
100
|
+
proc = process_actions(actions, tmp_root)
|
|
101
|
+
|
|
102
|
+
if not proc.get("success"):
|
|
103
|
+
result.success = False
|
|
104
|
+
msgs = [r.get("message", "") for r in proc.get("results", []) if not r.get("success")]
|
|
105
|
+
result.error = "; ".join(msgs) if msgs else "process_actions 失败"
|
|
106
|
+
return result
|
|
107
|
+
|
|
108
|
+
# dry-run 后用资源文件快照对比检测新建文件(比按 spec 收集可靠——
|
|
109
|
+
# spec 可能不含 constraintFile,而 handler 内部派生的路径不在 _collect_affected_files 结果中)
|
|
110
|
+
pre_snapshot = _snapshot_resource_files(workspace_path)
|
|
111
|
+
post_snapshot = _snapshot_resource_files(tmp_root)
|
|
112
|
+
created_in_tmp = post_snapshot - pre_snapshot
|
|
113
|
+
for abs_p in created_in_tmp:
|
|
114
|
+
rel = os.path.relpath(abs_p, tmp_root)
|
|
115
|
+
if rel not in before_contents:
|
|
116
|
+
rel_paths.append(rel)
|
|
117
|
+
# 新建文件执行前不存在
|
|
118
|
+
before_contents[rel] = None
|
|
119
|
+
|
|
120
|
+
for rel in rel_paths:
|
|
121
|
+
tmp_p = os.path.join(tmp_root, rel)
|
|
122
|
+
try:
|
|
123
|
+
with open(tmp_p, encoding="utf-8") as f:
|
|
124
|
+
after = f.read()
|
|
125
|
+
except FileNotFoundError:
|
|
126
|
+
after = None
|
|
127
|
+
|
|
128
|
+
before = before_contents.get(rel)
|
|
129
|
+
fd = _build_file_diff(rel, before, after)
|
|
130
|
+
if fd is not None: # None = 无变化,跳过(避免确认框噪音)
|
|
131
|
+
result.files.append(fd)
|
|
132
|
+
|
|
133
|
+
for fd in result.files:
|
|
134
|
+
result.summary[fd.status] = result.summary.get(fd.status, 0) + 1
|
|
135
|
+
|
|
136
|
+
for r in proc.get("results", []):
|
|
137
|
+
fi = r.get("frontendInstructions")
|
|
138
|
+
if fi:
|
|
139
|
+
result.frontend_instructions.append(fi)
|
|
140
|
+
|
|
141
|
+
return result
|
|
142
|
+
finally:
|
|
143
|
+
shutil.rmtree(tmp_root, ignore_errors=True)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _shadow_copy(src_workspace: str, dst: str) -> None:
|
|
147
|
+
"""将项目配置目录浅拷贝到临时目录(跳过 .precis 和 __pycache__)。"""
|
|
148
|
+
os.makedirs(dst, exist_ok=True)
|
|
149
|
+
ignore = shutil.ignore_patterns(".precis", "__pycache__")
|
|
150
|
+
for entry in os.listdir(src_workspace):
|
|
151
|
+
if entry in (".precis", "__pycache__"):
|
|
152
|
+
continue
|
|
153
|
+
s = os.path.join(src_workspace, entry)
|
|
154
|
+
d = os.path.join(dst, entry)
|
|
155
|
+
if os.path.isdir(s):
|
|
156
|
+
shutil.copytree(s, d, ignore=ignore, dirs_exist_ok=True)
|
|
157
|
+
else:
|
|
158
|
+
shutil.copy2(s, d)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _build_file_diff(rel: str, before: str | None, after: str | None) -> FileDiff | None:
|
|
162
|
+
"""对比 before/after 生成 FileDiff。
|
|
163
|
+
|
|
164
|
+
返回 None 表示该文件无变化(before==after),调用方应跳过,
|
|
165
|
+
避免在确认框中显示"修改 → 无文本差异"误导用户。
|
|
166
|
+
"""
|
|
167
|
+
if before is None and after is not None:
|
|
168
|
+
return FileDiff(
|
|
169
|
+
path=rel, status="created", diff=f"--- /dev/null\n+++ {rel}\n{after}", after_preview=after[:500]
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
if after is None and before is not None:
|
|
173
|
+
return FileDiff(path=rel, status="deleted", diff=f"--- {rel}\n+++ /dev/null\n", before_preview=before[:500])
|
|
174
|
+
|
|
175
|
+
# before 和 after 都存在(或都不存在):计算真实 diff
|
|
176
|
+
if before == after:
|
|
177
|
+
# 内容完全一致——无变化,不生成 FileDiff(避免确认框出现"无文本差异"噪音)
|
|
178
|
+
return None
|
|
179
|
+
|
|
180
|
+
diff = "".join(
|
|
181
|
+
difflib.unified_diff(
|
|
182
|
+
(before or "").splitlines(keepends=True),
|
|
183
|
+
(after or "").splitlines(keepends=True),
|
|
184
|
+
fromfile=f"a/{rel}",
|
|
185
|
+
tofile=f"b/{rel}",
|
|
186
|
+
)
|
|
187
|
+
)
|
|
188
|
+
# diff 为空但内容不同(如仅末尾换行差异)——仍算 modified 但标注
|
|
189
|
+
return FileDiff(
|
|
190
|
+
path=rel,
|
|
191
|
+
status="modified",
|
|
192
|
+
diff=diff or "(无文本差异)",
|
|
193
|
+
before_preview=(before or "")[:500],
|
|
194
|
+
after_preview=(after or "")[:500],
|
|
195
|
+
)
|