precis-cli 0.1.5__tar.gz → 0.1.7__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.
Files changed (454) hide show
  1. {precis_cli-0.1.5 → precis_cli-0.1.7}/MANIFEST.in +0 -1
  2. {precis_cli-0.1.5 → precis_cli-0.1.7}/PKG-INFO +1 -1
  3. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/ai/providers.py +8 -3
  4. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shared_services/config_ops.py +52 -19
  5. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shared_services/generation_ops.py +41 -12
  6. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shared_services/project_ops.py +22 -3
  7. precis_cli-0.1.7/app/cli/shell/commands/ai/agent_interaction.py +210 -0
  8. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shell/commands/ai/chat.py +2 -2
  9. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shell/commands/ai/executor.py +12 -0
  10. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shell/commands/ai/generate.py +2 -1
  11. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shell/commands/ai/migrate.py +10 -2
  12. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shell/commands/ai/switch.py +6 -5
  13. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shell/commands/config/check.py +8 -3
  14. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shell/commands/config/edit.py +9 -3
  15. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shell/commands/config/get.py +5 -5
  16. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shell/commands/config/set.py +9 -2
  17. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shell/commands/config/show.py +7 -3
  18. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shell/commands/provider.py +1 -1
  19. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shell/commands/validate.py +68 -7
  20. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shell/formatter.py +16 -8
  21. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shell/interactive_menu.py +41 -0
  22. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shell/main.py +13 -6
  23. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/start.py +20 -0
  24. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/mcp_server.py +31 -13
  25. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/data_source/loaders/excel_loader.py +99 -15
  26. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/loader/loader_parts/main.py +27 -11
  27. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/utils/path_utils.py +12 -0
  28. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/ai/chat_agent_runner.py +10 -7
  29. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/ai/chat_orchestrator.py +14 -1
  30. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/actions/_constraint_validator.py +62 -1
  31. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/actions/action_handlers.py +33 -16
  32. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/actions/registry.py +4 -0
  33. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/chat/chat_system_prompt.py +16 -7
  34. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/config/loader.py +10 -23
  35. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/config/models.py +2 -0
  36. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/config/presets.py +6 -4
  37. precis_cli-0.1.7/app/shared/services/llm/constraints/constraint_builder.py +421 -0
  38. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/constraints/inline_batch.py +53 -22
  39. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/generation/config_builder.py +40 -3
  40. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/generation/prompt_builder.py +2 -1
  41. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/providers/__init__.py +3 -1
  42. precis_cli-0.1.7/app/shared/services/llm/providers/fake.py +184 -0
  43. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/providers/registry.py +3 -0
  44. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/schema_inference.py +37 -17
  45. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/validation/chunked_loader.py +25 -9
  46. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/validation/executor.py +3 -0
  47. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/validation/report_export.py +17 -2
  48. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/validation/resolver.py +26 -1
  49. {precis_cli-0.1.5 → precis_cli-0.1.7}/precis_cli.egg-info/PKG-INFO +1 -1
  50. {precis_cli-0.1.5 → precis_cli-0.1.7}/precis_cli.egg-info/SOURCES.txt +2 -0
  51. {precis_cli-0.1.5 → precis_cli-0.1.7}/pyproject.toml +1 -4
  52. precis_cli-0.1.5/app/shared/services/llm/constraints/constraint_builder.py +0 -177
  53. {precis_cli-0.1.5 → precis_cli-0.1.7}/PYPI_README.md +0 -0
  54. {precis_cli-0.1.5 → precis_cli-0.1.7}/README.md +0 -0
  55. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/__init__.py +0 -0
  56. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/__init__.py +0 -0
  57. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/dependencies.py +0 -0
  58. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/main.py +0 -0
  59. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/middleware/__init__.py +0 -0
  60. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/middleware/exception_handler.py +0 -0
  61. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/middleware/request_logging.py +0 -0
  62. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/middleware/token_auth.py +0 -0
  63. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/models/__init__.py +0 -0
  64. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/models/connection_rules.py +0 -0
  65. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/models/files.py +0 -0
  66. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/models/full_validation.py +0 -0
  67. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/models/project.py +0 -0
  68. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/models/projects.py +0 -0
  69. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/models/schema.py +0 -0
  70. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/models/v2_responses.py +0 -0
  71. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/models/validation.py +0 -0
  72. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/models/workspace.py +0 -0
  73. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/__init__.py +0 -0
  74. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/ai/__init__.py +0 -0
  75. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/ai/chat.py +0 -0
  76. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/ai/generate.py +0 -0
  77. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/ai/hardware.py +0 -0
  78. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/ai/jobs.py +0 -0
  79. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/ai/migrate.py +0 -0
  80. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/ai/models.py +0 -0
  81. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/ai/ollama.py +0 -0
  82. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/ai/router.py +0 -0
  83. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/ai/stream.py +0 -0
  84. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/ai/utils.py +0 -0
  85. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/core/__init__.py +0 -0
  86. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/core/connection_rules.py +0 -0
  87. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/core/data_sources.py +0 -0
  88. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/core/regex.py +0 -0
  89. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/core/reporting.py +0 -0
  90. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/files/__init__.py +0 -0
  91. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/files/ops.py +0 -0
  92. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/files/transfer.py +0 -0
  93. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/preview/__init__.py +0 -0
  94. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/preview/content_mode.py +0 -0
  95. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/preview/header_row.py +0 -0
  96. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/preview/models.py +0 -0
  97. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/preview/path_mode.py +0 -0
  98. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/preview/router.py +0 -0
  99. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/project/__init__.py +0 -0
  100. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/project/base.py +0 -0
  101. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/project/constraint.py +0 -0
  102. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/project/full_config.py +0 -0
  103. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/project/full_config_writer.py +0 -0
  104. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/project/helpers.py +0 -0
  105. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/project/inspection_fix.py +0 -0
  106. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/project/manifest.py +0 -0
  107. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/project/models.py +0 -0
  108. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/project/pattern.py +0 -0
  109. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/project/regex.py +0 -0
  110. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/project/scanner.py +0 -0
  111. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/project/schema.py +0 -0
  112. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/project/schema_helpers.py +0 -0
  113. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/project/settings.py +0 -0
  114. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/project/template.py +0 -0
  115. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/project/validation.py +0 -0
  116. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/project/view.py +0 -0
  117. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/project/workspaces.py +0 -0
  118. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/projects/__init__.py +0 -0
  119. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/projects/create.py +0 -0
  120. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/projects/open.py +0 -0
  121. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/projects/scan.py +0 -0
  122. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/validation/__init__.py +0 -0
  123. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/validation/common.py +0 -0
  124. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/validation/content_mode.py +0 -0
  125. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/validation/history.py +0 -0
  126. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/validation/inline_mode.py +0 -0
  127. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/validation/path_mode.py +0 -0
  128. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/routers/validation/router.py +0 -0
  129. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/services/full_validation_response_builder.py +0 -0
  130. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/services/io_error_messages.py +0 -0
  131. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/api/services/preview_service.py +0 -0
  132. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/__init__.py +0 -0
  133. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/__main__.py +0 -0
  134. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shared_services/__init__.py +0 -0
  135. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shell/__init__.py +0 -0
  136. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shell/commands/__init__.py +0 -0
  137. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shell/commands/ai/__init__.py +0 -0
  138. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shell/commands/ai/delete.py +0 -0
  139. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shell/commands/ai/diff.py +0 -0
  140. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shell/commands/ai/display.py +0 -0
  141. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shell/commands/ai/executor_utils.py +0 -0
  142. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shell/commands/ai/interaction.py +0 -0
  143. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shell/commands/ai/resolver.py +0 -0
  144. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shell/commands/ai/status.py +0 -0
  145. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shell/commands/ai/utils.py +0 -0
  146. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shell/commands/base.py +0 -0
  147. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shell/commands/config/__init__.py +0 -0
  148. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shell/commands/config/base.py +0 -0
  149. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shell/commands/config/init.py +0 -0
  150. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shell/commands/config/inspect.py +0 -0
  151. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shell/commands/config/list.py +0 -0
  152. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shell/commands/exit.py +0 -0
  153. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shell/commands/help.py +0 -0
  154. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shell/commands/infer_schema.py +0 -0
  155. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shell/commands/open.py +0 -0
  156. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shell/commands/project.py +0 -0
  157. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shell/commands/system.py +0 -0
  158. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shell/completer.py +0 -0
  159. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shell/config_storage.py +0 -0
  160. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shell/exceptions.py +0 -0
  161. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli/shell/parser.py +0 -0
  162. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/cli_main.py +0 -0
  163. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/__init__.py +0 -0
  164. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/app_version.py +0 -0
  165. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/config/__init__.py +0 -0
  166. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/config/server.py +0 -0
  167. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/data_source/__init__.py +0 -0
  168. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/data_source/loader.py +0 -0
  169. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/data_source/loaders/__init__.py +0 -0
  170. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/data_source/loaders/base.py +0 -0
  171. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/data_source/loaders/converter.py +0 -0
  172. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/data_source/loaders/csv_loader.py +0 -0
  173. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/data_source/loaders/extractor.py +0 -0
  174. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/data_source/loaders/json_loader.py +0 -0
  175. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/data_source/loaders/registry.py +0 -0
  176. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/data_source/loaders/sql_loader.py +0 -0
  177. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/data_source/loaders/strategies/__init__.py +0 -0
  178. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/data_source/loaders/strategies/array_parser.py +0 -0
  179. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/data_source/loaders/strategies/lines_parser.py +0 -0
  180. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/data_source/loaders/strategies/object_parser.py +0 -0
  181. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/data_source/schema_info.py +0 -0
  182. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/data_source/specs/__init__.py +0 -0
  183. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/data_source/specs/base.py +0 -0
  184. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/data_source/specs/csv_source.py +0 -0
  185. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/data_source/specs/excel_source.py +0 -0
  186. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/data_source/specs/file_base.py +0 -0
  187. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/data_source/specs/json_source.py +0 -0
  188. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/data_source/specs/sql_source.py +0 -0
  189. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/encoding.py +0 -0
  190. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/io/__init__.py +0 -0
  191. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/io/yaml.py +0 -0
  192. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/manifest_schema/__init__.py +0 -0
  193. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/manifest_schema/types.py +0 -0
  194. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/manifest_schema/version.py +0 -0
  195. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/patterns/__init__.py +0 -0
  196. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/patterns/loader.py +0 -0
  197. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/patterns/writer.py +0 -0
  198. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/__init__.py +0 -0
  199. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/constraint/builders/__init__.py +0 -0
  200. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/constraint/builders/base.py +0 -0
  201. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/constraint/builders/composite.py +0 -0
  202. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/constraint/builders/conditional.py +0 -0
  203. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/constraint/builders/foreign_key.py +0 -0
  204. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/constraint/builders/registry.py +0 -0
  205. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/constraint/builders/scripted.py +0 -0
  206. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/constraint/builders/single_column.py +0 -0
  207. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/constraint/builders/unique.py +0 -0
  208. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/constraint/factory.py +0 -0
  209. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/constraint/reader.py +0 -0
  210. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/constraint/registry.py +0 -0
  211. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/constraint/types/__init__.py +0 -0
  212. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/constraint/types/constraint_file.py +0 -0
  213. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/constraint/types/refs.py +0 -0
  214. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/constraint/types.py +0 -0
  215. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/constraint/writer.py +0 -0
  216. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/loader/__init__.py +0 -0
  217. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/loader/loader.py +0 -0
  218. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/loader/loader_parts/config_inspector.py +0 -0
  219. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/loader/loader_parts/embedded_constraints.py +0 -0
  220. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/loader/loader_parts/file_loaders.py +0 -0
  221. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/loader/loader_parts/inspection_ids.py +0 -0
  222. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/loader/loader_parts/inspector_helpers.py +0 -0
  223. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/loader/loader_parts/inspector_id_checks.py +0 -0
  224. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/loader/loader_parts/inspector_reference_checks.py +0 -0
  225. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/loader/loader_parts/inspector_uniqueness_checks.py +0 -0
  226. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/loader/loader_parts/loading_error_messages.py +0 -0
  227. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/loader/loader_parts/path_validation.py +0 -0
  228. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/loader/loader_parts/runtime.py +0 -0
  229. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/loader/types.py +0 -0
  230. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/manifest/coverage.py +0 -0
  231. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/manifest/reader.py +0 -0
  232. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/manifest/types.py +0 -0
  233. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/manifest/types_parts/__init__.py +0 -0
  234. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/manifest/types_parts/constants.py +0 -0
  235. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/manifest/types_parts/data_source.py +0 -0
  236. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/manifest/types_parts/info.py +0 -0
  237. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/manifest/types_parts/manifest.py +0 -0
  238. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/manifest/types_parts/refs.py +0 -0
  239. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/manifest/types_parts/settings.py +0 -0
  240. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/manifest/types_parts/settings_file_processing.py +0 -0
  241. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/manifest/types_parts/settings_script_security.py +0 -0
  242. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/manifest/types_parts/settings_validation.py +0 -0
  243. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/manifest/types_parts/template.py +0 -0
  244. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/manifest/writer.py +0 -0
  245. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/manual_data/__init__.py +0 -0
  246. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/manual_data/types.py +0 -0
  247. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/regex/reader.py +0 -0
  248. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/regex/types.py +0 -0
  249. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/regex/writer.py +0 -0
  250. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/schema/reader.py +0 -0
  251. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/schema/types.py +0 -0
  252. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/schema/types_parts/__init__.py +0 -0
  253. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/schema/types_parts/column.py +0 -0
  254. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/schema/types_parts/column_utils.py +0 -0
  255. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/schema/types_parts/constraint.py +0 -0
  256. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/schema/types_parts/schema_id.py +0 -0
  257. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/schema/types_parts/source.py +0 -0
  258. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/schema/types_parts/source_options.py +0 -0
  259. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/schema/types_parts/table.py +0 -0
  260. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/schema/writer.py +0 -0
  261. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/schema_ref_check.py +0 -0
  262. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/template/__init__.py +0 -0
  263. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/template/expander.py +0 -0
  264. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/template/reader.py +0 -0
  265. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/template/types.py +0 -0
  266. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/transform/reader.py +0 -0
  267. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/transform/types.py +0 -0
  268. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/project/transform/writer.py +0 -0
  269. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/pydantic_messages.py +0 -0
  270. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/reporter/__init__.py +0 -0
  271. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/reporter/reporter.py +0 -0
  272. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/reporter/reporters/__init__.py +0 -0
  273. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/reporter/reporters/base.py +0 -0
  274. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/reporter/reporters/dingtalk_app_reporter.py +0 -0
  275. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/reporter/reporters/email_reporter.py +0 -0
  276. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/reporter/reporters/feishu_app_reporter.py +0 -0
  277. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/reporter/reporters/local_file_reporter.py +0 -0
  278. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/reporter/reporters/wecom_app_reporter.py +0 -0
  279. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/utils/__init__.py +0 -0
  280. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/core/utils/regex_extract.py +0 -0
  281. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/__init__.py +0 -0
  282. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/constraints/__init__.py +0 -0
  283. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/constraints/allowed_values.py +0 -0
  284. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/constraints/base.py +0 -0
  285. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/constraints/charset.py +0 -0
  286. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/constraints/composite.py +0 -0
  287. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/constraints/condition_registry.py +0 -0
  288. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/constraints/conditional.py +0 -0
  289. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/constraints/date_logic.py +0 -0
  290. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/constraints/foreign_key.py +0 -0
  291. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/constraints/key_normalization.py +0 -0
  292. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/constraints/not_null.py +0 -0
  293. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/constraints/range.py +0 -0
  294. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/constraints/regex.py +0 -0
  295. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/constraints/scripted.py +0 -0
  296. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/constraints/unique.py +0 -0
  297. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/data_engine.py +0 -0
  298. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/data_types.py +0 -0
  299. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/data_types_parts/__init__.py +0 -0
  300. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/data_types_parts/base.py +0 -0
  301. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/data_types_parts/composite.py +0 -0
  302. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/data_types_parts/expression.py +0 -0
  303. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/data_types_parts/extracted.py +0 -0
  304. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/data_types_parts/json_types.py +0 -0
  305. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/data_types_parts/scalars.py +0 -0
  306. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/data_types_parts/sequence.py +0 -0
  307. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/dataset_schema.py +0 -0
  308. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/eval_sandbox.py +0 -0
  309. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/expression_system.py +0 -0
  310. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/regex_flags.py +0 -0
  311. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/schema/builder.py +0 -0
  312. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/schema/models.py +0 -0
  313. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/transforms/__init__.py +0 -0
  314. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/transforms/aggregate.py +0 -0
  315. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/transforms/base.py +0 -0
  316. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/transforms/cast_type.py +0 -0
  317. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/transforms/concat.py +0 -0
  318. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/transforms/conditional_assign.py +0 -0
  319. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/transforms/date_format.py +0 -0
  320. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/transforms/digits.py +0 -0
  321. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/transforms/drop_duplicates.py +0 -0
  322. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/transforms/fill_na.py +0 -0
  323. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/transforms/filter_rows.py +0 -0
  324. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/transforms/lookup.py +0 -0
  325. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/transforms/lower_case.py +0 -0
  326. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/transforms/map_value.py +0 -0
  327. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/transforms/math_expr.py +0 -0
  328. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/transforms/modulo.py +0 -0
  329. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/transforms/regex_extract.py +0 -0
  330. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/transforms/registry.py +0 -0
  331. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/transforms/replace.py +0 -0
  332. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/transforms/sort_rows.py +0 -0
  333. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/transforms/string_split.py +0 -0
  334. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/transforms/strip.py +0 -0
  335. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/transforms/substring.py +0 -0
  336. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/transforms/upper_case.py +0 -0
  337. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/transforms/weighted_sum.py +0 -0
  338. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/domain/validation_constraints.py +0 -0
  339. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/__init__.py +0 -0
  340. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/ai/__init__.py +0 -0
  341. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/ai/agent/__init__.py +0 -0
  342. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/ai/agent/chat_tools/__init__.py +0 -0
  343. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/ai/agent/chat_tools/apply_actions.py +0 -0
  344. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/ai/agent/chat_tools/ask_user.py +0 -0
  345. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/ai/agent/chat_tools/read_canvas.py +0 -0
  346. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/ai/agent/chat_tools/read_project.py +0 -0
  347. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/ai/agent/chat_tools/read_table.py +0 -0
  348. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/ai/agent/chat_tools/schemas.py +0 -0
  349. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/ai/agent/chat_tools/validate_table.py +0 -0
  350. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/ai/agent/executor.py +0 -0
  351. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/ai/agent/memory.py +0 -0
  352. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/ai/agent/planner.py +0 -0
  353. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/ai/agent/tool_registry.py +0 -0
  354. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/ai/agent/tools/__init__.py +0 -0
  355. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/ai/agent/tools/config_generate.py +0 -0
  356. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/ai/agent/tools/config_refine.py +0 -0
  357. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/ai/agent/tools/config_validate.py +0 -0
  358. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/ai/agent/tools/merge_results.py +0 -0
  359. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/ai/agent/tools/plan_chunks.py +0 -0
  360. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/ai/agent/tools/script_parse.py +0 -0
  361. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/ai/agent/types.py +0 -0
  362. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/ai/failure_messages.py +0 -0
  363. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/ai/job_storage.py +0 -0
  364. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/ai/migrate_service.py +0 -0
  365. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/ai/streaming/__init__.py +0 -0
  366. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/ai/streaming/event_journal.py +0 -0
  367. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/ai/streaming/orchestrator.py +0 -0
  368. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/ai/streaming/pending_interaction_store.py +0 -0
  369. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/ai/streaming/sse_response.py +0 -0
  370. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/ai/streaming/types.py +0 -0
  371. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/ai/types.py +0 -0
  372. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/ai/utils.py +0 -0
  373. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/diff/config_diff.py +0 -0
  374. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/hardware.py +0 -0
  375. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/__init__.py +0 -0
  376. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/actions/__init__.py +0 -0
  377. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/actions/_canvas_validator.py +0 -0
  378. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/actions/_regex_validator.py +0 -0
  379. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/actions/_schema_validator.py +0 -0
  380. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/actions/_settings_validator.py +0 -0
  381. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/actions/_transform_validator.py +0 -0
  382. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/actions/action_parser.py +0 -0
  383. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/actions/action_processor.py +0 -0
  384. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/actions/action_validator.py +0 -0
  385. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/actions/diff_compute.py +0 -0
  386. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/actions/regex_handlers.py +0 -0
  387. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/actions/schema_handlers.py +0 -0
  388. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/actions/settings_handlers.py +0 -0
  389. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/actions/specs.py +0 -0
  390. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/actions/transform_handlers.py +0 -0
  391. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/actions/validation_types.py +0 -0
  392. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/cache/__init__.py +0 -0
  393. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/cache/response_cache.py +0 -0
  394. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/chat/__init__.py +0 -0
  395. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/chat/response_parser.py +0 -0
  396. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/config/__init__.py +0 -0
  397. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/config/crypto.py +0 -0
  398. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/config_generator.py +0 -0
  399. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/constraints/__init__.py +0 -0
  400. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/constraints/constraint_deletion.py +0 -0
  401. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/constraints/constraint_id.py +0 -0
  402. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/constraints/frontend_instructions.py +0 -0
  403. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/discovery/__init__.py +0 -0
  404. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/discovery/scanner.py +0 -0
  405. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/generation/__init__.py +0 -0
  406. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/generation/agent_wiring.py +0 -0
  407. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/generation/errors.py +0 -0
  408. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/generation/existing_config.py +0 -0
  409. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/generation/profiler.py +0 -0
  410. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/generation/response_parser.py +0 -0
  411. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/generation/service.py +0 -0
  412. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/models.py +0 -0
  413. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/providers/base.py +0 -0
  414. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/providers/cached_provider.py +0 -0
  415. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/providers/ollama.py +0 -0
  416. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/providers/openai.py +0 -0
  417. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/schema_resolver.py +0 -0
  418. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/suggestion_utils.py +0 -0
  419. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/validate_executor.py +0 -0
  420. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/llm/yaml_io.py +0 -0
  421. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/preview/__init__.py +0 -0
  422. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/preview/loader.py +0 -0
  423. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/preview/path_validation.py +0 -0
  424. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/project_loader.py +0 -0
  425. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/schema_runtime_builder.py +0 -0
  426. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/validation/__init__.py +0 -0
  427. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/validation/dag/__init__.py +0 -0
  428. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/validation/dag/builder.py +0 -0
  429. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/validation/dag/executor.py +0 -0
  430. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/validation/dag/sorter.py +0 -0
  431. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/validation/data_loader.py +0 -0
  432. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/validation/engine.py +0 -0
  433. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/validation/extractors.py +0 -0
  434. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/validation/history.py +0 -0
  435. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/validation/json_payload.py +0 -0
  436. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/validation/loader.py +0 -0
  437. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/validation/memory_monitor.py +0 -0
  438. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/validation/postprocess.py +0 -0
  439. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/validation/progress.py +0 -0
  440. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/validation/service.py +0 -0
  441. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/validation/types.py +0 -0
  442. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/validation/validators/__init__.py +0 -0
  443. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/validation/validators/adapter.py +0 -0
  444. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/validation/validators/base.py +0 -0
  445. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/validation/validators/composite.py +0 -0
  446. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/shared/services/validation/validators/date_logic.py +0 -0
  447. {precis_cli-0.1.5 → precis_cli-0.1.7}/app/start_server.py +0 -0
  448. {precis_cli-0.1.5 → precis_cli-0.1.7}/precis_cli.egg-info/dependency_links.txt +0 -0
  449. {precis_cli-0.1.5 → precis_cli-0.1.7}/precis_cli.egg-info/entry_points.txt +0 -0
  450. {precis_cli-0.1.5 → precis_cli-0.1.7}/precis_cli.egg-info/requires.txt +0 -0
  451. {precis_cli-0.1.5 → precis_cli-0.1.7}/precis_cli.egg-info/top_level.txt +0 -0
  452. {precis_cli-0.1.5 → precis_cli-0.1.7}/setup.cfg +0 -0
  453. {precis_cli-0.1.5 → precis_cli-0.1.7}/tests/test_local_file_reporter.py +0 -0
  454. {precis_cli-0.1.5 → precis_cli-0.1.7}/tests/test_schema_builder.py +0 -0
@@ -1,3 +1,2 @@
1
1
  include README.md
2
2
  include pyproject.toml
3
- recursive-include app *.yaml
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: precis-cli
3
- Version: 0.1.5
3
+ Version: 0.1.7
4
4
  Summary: Precis - 可配置数据校验工具 CLI(校验 CSV/Excel/JSON 数据质量)
5
5
  Author: Precis Team
6
6
  License: Apache-2.0
@@ -58,8 +58,10 @@ def _is_configured(p: AIProvider) -> bool:
58
58
  """判断 Provider 是否已配置。
59
59
 
60
60
  loader.load() 已完成 env-var 覆盖,直接检查 api_key 即可。
61
- 本地 Provider(Ollama)不需要 API Key。
61
+ 本地 Provider(Ollama)不需要 API Key;fake 是确定性测试 Provider,永不依赖 key。
62
62
  """
63
+ if p.type == ProviderType.FAKE:
64
+ return True
63
65
  if p.deployment == DeploymentType.LOCAL:
64
66
  return bool(p.base_url)
65
67
  return bool(p.api_key and p.api_key.strip())
@@ -141,7 +143,7 @@ providers:
141
143
  type: openai
142
144
  base_url: https://api.deepseek.com
143
145
  api_key: ${DEEPSEEK_API_KEY}
144
- model: deepseek-v4-pro
146
+ model: deepseek-flash
145
147
 
146
148
  # 本地 Ollama(无需 API Key)
147
149
  - id: ollama-local
@@ -391,7 +393,10 @@ async def create_provider(req: CreateProviderRequest) -> ProviderResponse:
391
393
  try:
392
394
  provider_type = ProviderType(req.type)
393
395
  except ValueError:
394
- raise HTTPException(400, detail=f"不支持的 AI 服务类型「{req.type}」,目前仅支持 openai、ollama 两种接入方式")
396
+ raise HTTPException(
397
+ 400,
398
+ detail=f"不支持的 AI 服务类型「{req.type}」,目前支持 openai、ollama 两种接入方式(fake 仅用于测试演练)",
399
+ )
395
400
 
396
401
  _validate_base_url(req.base_url) # SSRF 防护
397
402
 
@@ -41,6 +41,7 @@
41
41
  from __future__ import annotations
42
42
 
43
43
  import logging
44
+ import math
44
45
  import os
45
46
  import tempfile
46
47
  from collections.abc import Iterator
@@ -207,6 +208,10 @@ def resolve_config_file(project_path: str, filename: str | None) -> tuple[str |
207
208
  def get_by_dotpath(data: dict, key_path: str) -> tuple[bool, Any]:
208
209
  """按点号路径从字典中查找值。
209
210
 
211
+ YAML 键可为非字符串(如 ``2024:`` 解析为 int、``true:`` 解析为 bool)——
212
+ 路径段除精确匹配外也按字符串形态匹配非字符串键,防止"键存在却查不到"
213
+ 进而被 set 侧写出同形态影子键。
214
+
210
215
  Args:
211
216
  data: 配置字典
212
217
  key_path: 点号路径,如 "project.name"
@@ -217,8 +222,16 @@ def get_by_dotpath(data: dict, key_path: str) -> tuple[bool, Any]:
217
222
  keys = key_path.split(".")
218
223
  value: Any = data
219
224
  for key in keys:
220
- if isinstance(value, dict) and key in value:
221
- value = value[key]
225
+ if isinstance(value, dict):
226
+ if key in value:
227
+ value = value[key]
228
+ continue
229
+ for k, v in value.items():
230
+ if not isinstance(k, str) and str(k) == key:
231
+ value = v
232
+ break
233
+ else:
234
+ return False, None
222
235
  else:
223
236
  return False, None
224
237
  return True, value
@@ -227,7 +240,8 @@ def get_by_dotpath(data: dict, key_path: str) -> tuple[bool, Any]:
227
240
  def set_by_dotpath(data: dict, key_path: str, value: Any) -> dict:
228
241
  """按点号路径写入值,返回新 data(不改原 dict)。
229
242
 
230
- 中间层级不存在时自动创建空字典。
243
+ 中间层级不存在时自动创建空字典;路径段与既有非字符串键(如 int ``2024``)
244
+ 字符串形态相同时复用原键,防止写出同形态影子键并存。
231
245
 
232
246
  Args:
233
247
  data: 原配置字典(不会被修改)
@@ -244,10 +258,21 @@ def set_by_dotpath(data: dict, key_path: str, value: Any) -> dict:
244
258
  keys = key_path.split(".")
245
259
  current = new_data
246
260
  for key in keys[:-1]:
247
- if key not in current or not isinstance(current[key], dict):
248
- current[key] = {}
249
- current = current[key]
250
- current[keys[-1]] = value
261
+ existing = current.get(key)
262
+ if isinstance(existing, dict):
263
+ current = existing
264
+ continue
265
+ # 同形态非字符串键复用(写原键而非另建字符串影子键)
266
+ non_str_key = next((k for k in current if not isinstance(k, str) and str(k) == key), None)
267
+ if non_str_key is not None and isinstance(current[non_str_key], dict):
268
+ current = current[non_str_key]
269
+ continue
270
+ target = non_str_key if non_str_key is not None else key
271
+ current[target] = {}
272
+ current = current[target]
273
+ leaf = keys[-1]
274
+ leaf_key = next((k for k in current if not isinstance(k, str) and str(k) == leaf), leaf)
275
+ current[leaf_key] = value
251
276
  return new_data
252
277
 
253
278
 
@@ -267,8 +292,9 @@ def parse_config_value(value_str: str) -> tuple[bool, Any, str]:
267
292
  value_str: 原始字符串值
268
293
 
269
294
  Returns:
270
- (成功, 转换后的值, 错误信息);本实现始终成功,错误信息为空字符串。
271
- 保留 tuple 三元组以匹配接口契约,便于未来扩展严格模式。
295
+ (成功, 转换后的值, 错误信息);毒化字面量(inf/nan/下划线整数等
296
+ 无法被下游安全消费的数值形态)返回 (False, None, 错误信息),
297
+ 调用方应拒绝落盘。
272
298
  """
273
299
  # 尝试布尔值
274
300
  if value_str.lower() == "true":
@@ -278,17 +304,24 @@ def parse_config_value(value_str: str) -> tuple[bool, Any, str]:
278
304
  if value_str.lower() == "null" or value_str.lower() == "none":
279
305
  return True, None, ""
280
306
 
281
- # 尝试整数
282
- try:
283
- return True, int(value_str), ""
284
- except ValueError:
285
- pass
307
+ # 尝试整数:拒绝 Python 下划线字面量('1_0' 静默变 10 会毒化下游数值读取)
308
+ if "_" not in value_str:
309
+ try:
310
+ return True, int(value_str), ""
311
+ except ValueError:
312
+ pass
286
313
 
287
- # 尝试浮点数
288
- try:
289
- return True, float(value_str), ""
290
- except ValueError:
291
- pass
314
+ # 尝试浮点数:拒绝非有限值(inf/nan/溢出 1e999)与下划线字面量——
315
+ # timeout_seconds: .inf 落盘后 validate 侧 int(inf) 抛 OverflowError,项目从此无法校验
316
+ if "_" not in value_str:
317
+ try:
318
+ parsed_float = float(value_str)
319
+ except ValueError:
320
+ parsed_float = None
321
+ if parsed_float is not None:
322
+ if not math.isfinite(parsed_float):
323
+ return False, None, f"不接受非有限数值: {value_str}(inf/nan 落盘会使项目无法校验)"
324
+ return True, parsed_float, ""
292
325
 
293
326
  # 去除引号
294
327
  if (value_str.startswith('"') and value_str.endswith('"')) or (
@@ -36,6 +36,7 @@ from __future__ import annotations
36
36
  import glob
37
37
  import logging
38
38
  import os
39
+ import re
39
40
  from pathlib import Path
40
41
  from typing import Any
41
42
 
@@ -46,6 +47,25 @@ logger = logging.getLogger(__name__)
46
47
  # 支持的文件扩展名(generate/migrate 共用)
47
48
  SUPPORTED_EXTENSIONS = (".xlsx", ".xls", ".csv", ".json", ".jsonl")
48
49
 
50
+ # 实体 id 安全白名单:字母/数字/下划线/连字符(H10——LLM 生成的 id 未消毒即拼盘
51
+ # 路径时,`../../evil` 类路径成分可越项目写文件/建目录,写盘前须拒绝)
52
+ _SAFE_ENTITY_ID_RE = re.compile(r"[A-Za-z0-9_-]+")
53
+
54
+
55
+ def _filter_safe_entity_ids(entities: dict[str, Any], kind: str) -> dict[str, Any]:
56
+ """过滤掉含路径成分或非法字符的实体 id,记录错误日志(H10)。
57
+
58
+ Returns:
59
+ 仅保留安全 id 的实体字典;非法 id 整体跳过(不写盘、不进 manifest 引用)
60
+ """
61
+ safe: dict[str, Any] = {}
62
+ for eid, body in entities.items():
63
+ if isinstance(eid, str) and _SAFE_ENTITY_ID_RE.fullmatch(eid):
64
+ safe[eid] = body
65
+ else:
66
+ logger.error("跳过非法实体 id(含路径成分或非法字符,kind=%s): %r", kind, eid)
67
+ return safe
68
+
49
69
 
50
70
  def scan_data_files(patterns: list[str], project_path: str) -> list[str]:
51
71
  """展开数据文件路径(支持通配符、相对路径、data/ 目录扫描)。
@@ -91,8 +111,9 @@ def scan_data_files(patterns: list[str], project_path: str) -> list[str]:
91
111
  def apply_generated_config(result: dict[str, Any], project_path: str) -> list[str]:
92
112
  """将生成的配置写入项目目录。
93
113
 
94
- 会保留现有 project.precis.yaml 中的 transforms/manual_data 等引用,
95
- 覆盖写入 schemas、constraints、regex_nodes。
114
+ 保留策略为并集(R6):生成 manifest 未携带的既有顶层键(project/settings/
115
+ templates/data_sources 等)一律保留,防止空/局部 manifest 清空既有配置;
116
+ schemas、constraints、regex_nodes 引用则始终按生成结果覆盖写入。
96
117
 
97
118
  Args:
98
119
  result: ConfigGenerationService / ConfigMigrationService 返回的配置字典
@@ -111,16 +132,24 @@ def apply_generated_config(result: dict[str, Any], project_path: str) -> list[st
111
132
  except Exception:
112
133
  logger.warning("读取现有 manifest 失败,将覆盖写入", exc_info=True)
113
134
 
114
- manifest = result.get("manifest") or {"version": 2, "project": {"id": "", "name": ""}}
115
-
116
- # 保留现有 manifest 中生成未覆盖的引用
117
- for key in ("transforms", "manual_data"):
118
- if key in existing_manifest and key not in manifest:
119
- manifest[key] = existing_manifest[key]
120
-
121
- schemas = result.get("schemas", {})
122
- constraints = result.get("constraints", {})
123
- regex_nodes = result.get("regex_nodes", {})
135
+ generated_manifest = result.get("manifest")
136
+ if generated_manifest:
137
+ manifest = dict(generated_manifest)
138
+ else:
139
+ # LLM 未返回 manifest:以现有 manifest 为底仅补最小骨架,
140
+ # 不再制造空 project 兜底清空既有 project.id/settings(R6)
141
+ manifest = dict(existing_manifest)
142
+ manifest.setdefault("version", 2)
143
+ manifest.setdefault("project", {"id": "", "name": ""})
144
+
145
+ # 保留策略并集(R6):生成 manifest 未携带的既有键一律保留
146
+ for key, value in existing_manifest.items():
147
+ if key not in manifest:
148
+ manifest[key] = value
149
+
150
+ schemas = _filter_safe_entity_ids(result.get("schemas", {}) or {}, "schema")
151
+ constraints = _filter_safe_entity_ids(result.get("constraints", {}) or {}, "constraint")
152
+ regex_nodes = _filter_safe_entity_ids(result.get("regex_nodes", {}) or {}, "regex")
124
153
 
125
154
  # 确保目录存在
126
155
  (Path(project_path) / "schemas").mkdir(exist_ok=True)
@@ -51,10 +51,25 @@ HISTORY_FILE = os.path.expanduser("~/.precis_project_history")
51
51
  MAX_HISTORY = 10
52
52
 
53
53
 
54
+ def _backup_corrupt_history() -> None:
55
+ """把损坏的历史文件改名备份(.corrupt.bak),失败仅记日志。
56
+
57
+ 原实现在 JSON 损坏后静默按空历史继续——下次保存直接覆盖,
58
+ 用户历史不可恢复;备份保留现场供手工抢救。
59
+ """
60
+ try:
61
+ backup = f"{HISTORY_FILE}.corrupt.bak"
62
+ os.replace(HISTORY_FILE, backup)
63
+ logger.warning("项目历史文件损坏,已备份到 %s 并从空历史继续", backup)
64
+ except OSError:
65
+ logger.error("备份损坏的项目历史文件失败: %s", HISTORY_FILE, exc_info=True)
66
+
67
+
54
68
  def load_history() -> list[dict]:
55
69
  """加载项目打开历史。
56
70
 
57
71
  从 HISTORY_FILE 读取历史记录。文件不存在、格式损坏或权限不足时返回空列表。
72
+ JSON 损坏时先把原文件备份为 ``*.corrupt.bak`` 再按空历史继续(不静默清零)。
58
73
 
59
74
  Returns:
60
75
  历史记录列表,每个元素为包含 path 和 last_opened 的字典
@@ -78,7 +93,8 @@ def load_history() -> list[dict]:
78
93
  return valid
79
94
  return []
80
95
  except json.JSONDecodeError:
81
- # JSON 格式损坏,返回空列表
96
+ # JSON 格式损坏:备份后按空历史继续
97
+ _backup_corrupt_history()
82
98
  return []
83
99
  except PermissionError:
84
100
  # 无权限读取,返回空列表
@@ -91,7 +107,8 @@ def load_history() -> list[dict]:
91
107
  def _save_history(history: list[dict]) -> None:
92
108
  """保存项目打开历史。
93
109
 
94
- 将历史记录写入 HISTORY_FILE。保存失败仅记录日志,不阻断主流程。
110
+ 原子写(临时文件 + os.replace):进程中断不会留下半截 JSON——
111
+ 半截文件在下次 load 即被判损坏。保存失败仅记录日志,不阻断主流程。
95
112
 
96
113
  Args:
97
114
  history: 历史记录列表
@@ -99,8 +116,10 @@ def _save_history(history: list[dict]) -> None:
99
116
  try:
100
117
  # 确保目录存在(用户主目录)
101
118
  os.makedirs(os.path.dirname(HISTORY_FILE), exist_ok=True)
102
- with open(HISTORY_FILE, "w", encoding="utf-8") as f:
119
+ tmp_file = f"{HISTORY_FILE}.tmp"
120
+ with open(tmp_file, "w", encoding="utf-8") as f:
103
121
  json.dump(history, f, ensure_ascii=False, indent=2)
122
+ os.replace(tmp_file, HISTORY_FILE)
104
123
  except (PermissionError, OSError):
105
124
  # 保存失败时记录错误日志,但不阻断主流程
106
125
  logger.error("保存项目历史记录失败", exc_info=True)
@@ -0,0 +1,210 @@
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 Agent 模式终端交互适配器(CLI)
17
+
18
+ 为 CLI `ai chat`(默认 agent 模式)提供 apply_actions 两阶段确认与 ask_user
19
+ 交互问答的终端实现,解开非流式环境的 fail-closed 写盘/提问闸门。
20
+
21
+ 机制与 GUI 流式路径同源:回调在事件循环协程内触发(on_apply_pending /
22
+ on_user_input_requested),此处打印摘要后在独立 daemon 线程阻塞读终端输入,
23
+ 经 pending interaction store 线程安全 resolve 唤醒挂起的 await_decision() /
24
+ await_response()。超时(5 分钟)由 controller 兜底自动 reject/skip。
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import asyncio
30
+ import logging
31
+ import threading
32
+ from typing import Any
33
+
34
+ from app.cli.shell.commands.ai.executor_utils import SpinnerController
35
+ from app.cli.shell.formatter import Formatter
36
+ from app.shared.services.ai.agent.chat_tools.apply_actions import ApplyCallbacks
37
+ from app.shared.services.ai.agent.chat_tools.ask_user import AskCallbacks
38
+
39
+ logger = logging.getLogger(__name__)
40
+
41
+ # 终端 diff 展示上限:超长 diff 只显示前 N 行,避免刷屏(完整内容在 dry-run 文件里)
42
+ _MAX_DIFF_LINES = 30
43
+
44
+
45
+ def build_agent_interaction(spinner: SpinnerController | None) -> tuple[ApplyCallbacks, AskCallbacks]:
46
+ """构建 CLI 终端的 apply/ask 回调集合。
47
+
48
+ 参数:
49
+ spinner: CLI 交互模式的 spinner 控制器(None 表示无动画,静默模式)
50
+
51
+ 返回:
52
+ (ApplyCallbacks, AskCallbacks),注入 ChatOptions 后经 ChatAgentRunner 生效
53
+ """
54
+
55
+ def on_apply_pending(payload: dict[str, Any]) -> None:
56
+ # 回调在事件循环协程内同步触发,捕获运行中的 loop 供工作线程 resolve
57
+ loop = asyncio.get_running_loop()
58
+ apply_id = payload.get("apply_id", "")
59
+ if spinner:
60
+ spinner.pause()
61
+ _print_apply_summary(payload)
62
+
63
+ def _worker() -> None:
64
+ decision = _read_apply_decision()
65
+ try:
66
+ future = asyncio.run_coroutine_threadsafe(_resolve_apply(apply_id, decision), loop)
67
+ future.result(timeout=10)
68
+ except Exception:
69
+ logger.exception("[agent-confirm] 终端确认 resolve 失败: apply_id=%s", apply_id)
70
+ finally:
71
+ if spinner:
72
+ spinner.resume()
73
+
74
+ threading.Thread(target=_worker, daemon=True, name="agent-apply-confirm").start()
75
+
76
+ def on_user_input_requested(payload: dict[str, Any]) -> None:
77
+ loop = asyncio.get_running_loop()
78
+ ask_id = payload.get("ask_id", "")
79
+ if spinner:
80
+ spinner.pause()
81
+ _print_ask_question(payload)
82
+
83
+ def _worker() -> None:
84
+ response = _read_ask_response(payload)
85
+ try:
86
+ future = asyncio.run_coroutine_threadsafe(_resolve_ask(ask_id, response), loop)
87
+ future.result(timeout=10)
88
+ except Exception:
89
+ logger.exception("[agent-ask] 终端问答 resolve 失败: ask_id=%s", ask_id)
90
+ finally:
91
+ if spinner:
92
+ spinner.resume()
93
+
94
+ threading.Thread(target=_worker, daemon=True, name="agent-ask-input").start()
95
+
96
+ apply_callbacks = ApplyCallbacks(on_apply_pending=on_apply_pending)
97
+ ask_callbacks = AskCallbacks(on_user_input_requested=on_user_input_requested)
98
+ return apply_callbacks, ask_callbacks
99
+
100
+
101
+ async def _resolve_apply(apply_id: str, decision: str) -> None:
102
+ """按 apply_id 从全局 store 取 controller 并写入决策(幂等)。"""
103
+ # 延迟导入与 apply_actions._run_two_phase 一致(避免 streaming 包初始化链循环导入)
104
+ from app.shared.services.ai.streaming.pending_interaction_store import (
105
+ ConfirmController,
106
+ get_global_pending_interaction_store,
107
+ )
108
+
109
+ controller = get_global_pending_interaction_store().get(apply_id)
110
+ if isinstance(controller, ConfirmController):
111
+ await controller.resolve(decision)
112
+
113
+
114
+ async def _resolve_ask(ask_id: str, response: dict[str, Any]) -> None:
115
+ from app.shared.services.ai.streaming.pending_interaction_store import (
116
+ InteractionController,
117
+ get_global_pending_interaction_store,
118
+ )
119
+
120
+ controller = get_global_pending_interaction_store().get(ask_id)
121
+ if isinstance(controller, InteractionController):
122
+ await controller.resolve(response)
123
+
124
+
125
+ def _print_apply_summary(payload: dict[str, Any]) -> None:
126
+ """打印写盘计划摘要:文件清单 + 状态 + 截断 diff + 汇总行。"""
127
+ files = payload.get("files") or []
128
+ print(Formatter.warning(f"\nAI 请求写入 {len(files)} 个文件:"))
129
+ for f in files:
130
+ status = f.get("status", "?")
131
+ path = f.get("path", "?")
132
+ print(f" [{status}] {path}")
133
+ diff = (f.get("diff") or "").strip()
134
+ if diff:
135
+ diff_lines = diff.splitlines()
136
+ shown = diff_lines[:_MAX_DIFF_LINES]
137
+ for line in shown:
138
+ print(f" {line}")
139
+ if len(diff_lines) > _MAX_DIFF_LINES:
140
+ print(f" ...(共 {len(diff_lines)} 行,已截断)")
141
+ summary = payload.get("summary")
142
+ if summary:
143
+ print(Formatter.info(f" 摘要: {summary}"))
144
+
145
+
146
+ def _read_apply_decision() -> str:
147
+ """读取 y/n 决策;空输入与非 y 输入一律 reject(写盘默认保守)。"""
148
+ try:
149
+ raw = input(Formatter.warning("确认写入以上变更?[y/N]: ")).strip().lower()
150
+ except EOFError:
151
+ return "reject"
152
+ return "confirm" if raw in ("y", "yes", "是") else "reject"
153
+
154
+
155
+ def _print_ask_question(payload: dict[str, Any]) -> None:
156
+ print(Formatter.warning(f"\nAI 提问: {payload.get('prompt', '')}"))
157
+ options = payload.get("options")
158
+ if isinstance(options, list) and options:
159
+ for idx, opt in enumerate(options, start=1):
160
+ print(f" {idx}. {opt}")
161
+ if payload.get("optional"):
162
+ print(Formatter.info(" (直接回车跳过此问题)"))
163
+
164
+
165
+ def _read_ask_response(payload: dict[str, Any]) -> dict[str, Any]:
166
+ """按 question_type 读取终端回答,构造 AskResponseBody 形态。
167
+
168
+ 形态与前端 AskUserCard 一致:{answer: str|num|bool|str[]} / {skipped: true, reason}。
169
+ """
170
+ question_type = payload.get("question_type", "free_text")
171
+ optional = bool(payload.get("optional"))
172
+ try:
173
+ if question_type == "confirm":
174
+ raw = input("[y/N]: ").strip().lower()
175
+ return {"answer": raw in ("y", "yes", "是")}
176
+ if question_type == "choice":
177
+ options = payload.get("options") or []
178
+ raw = input(("(多选,逗号分隔) " if payload.get("multiple") else "") + "选项编号: ").strip()
179
+ if not raw and optional:
180
+ return {"skipped": True, "reason": "user_skipped"}
181
+ indexes = [part.strip() for part in raw.split(",") if part.strip()]
182
+ picked: list[str] = []
183
+ for part in indexes:
184
+ if part.isdigit() and 1 <= int(part) <= len(options):
185
+ picked.append(str(options[int(part) - 1]))
186
+ else:
187
+ picked.append(part)
188
+ if payload.get("multiple"):
189
+ return {"answer": picked}
190
+ return {"answer": picked[0] if picked else ""}
191
+ if question_type == "value":
192
+ value_type = payload.get("value_type", "string")
193
+ raw = input(f"输入值({value_type}): ").strip()
194
+ if not raw and optional:
195
+ return {"skipped": True, "reason": "user_skipped"}
196
+ if value_type in ("integer", "float"):
197
+ try:
198
+ return {"answer": float(raw) if value_type == "float" else int(raw)}
199
+ except ValueError:
200
+ return {"answer": raw}
201
+ if value_type == "boolean":
202
+ return {"answer": raw.lower() in ("y", "yes", "true", "1", "是")}
203
+ return {"answer": raw}
204
+ # free_text
205
+ raw = input("回答: ").strip()
206
+ if not raw and optional:
207
+ return {"skipped": True, "reason": "user_skipped"}
208
+ return {"answer": raw}
209
+ except EOFError:
210
+ return {"skipped": True, "reason": "eof"}
@@ -258,8 +258,6 @@ AI 助手帮助
258
258
  qq - 直接退出整个程序(全局可用)
259
259
 
260
260
  启动选项:
261
- --stream - 启用流式输出
262
- --no-stream - 禁用流式输出(默认,显示 loading spinner)
263
261
  --no-agent-mode - 关闭 Agent 深度模式(降级为旧 JSON actions 路径)
264
262
 
265
263
  你可以用自然语言描述你的需求,例如:
@@ -277,6 +275,8 @@ AI 助手帮助
277
275
  - ForeignKey: 外键约束 (如: "关联到 orders 表的 user_id 列")
278
276
  - Conditional: 条件约束 (如: "如果 type 等于 A 则值为 B")
279
277
  - DateLogic: 日期逻辑约束 (如: "日期晚于 2023-01-01")
278
+ - Charset: 字符集约束 (如: "该列只能包含中文")
279
+ - Composite: 复合约束 (如: "该列必须非空且唯一")
280
280
  """
281
281
  print(Formatter.info(help_text))
282
282
 
@@ -125,6 +125,15 @@ def execute_ai_chat(
125
125
  context_window = resolve_context_window(provider_config)
126
126
  max_history_tokens, _output_budget = compute_token_budgets(context_window)
127
127
 
128
+ # Agent 模式终端交互:注入 apply/ask 回调解开 fail-closed 写盘/提问闸门
129
+ # (非交互或 legacy JSON 路径不需要——legacy 走 confirm_callback 确认门)
130
+ agent_apply_callbacks = None
131
+ agent_ask_callbacks = None
132
+ if interactive and agent_mode:
133
+ from app.cli.shell.commands.ai.agent_interaction import build_agent_interaction
134
+
135
+ agent_apply_callbacks, agent_ask_callbacks = build_agent_interaction(spinner)
136
+
128
137
  # 配置对话选项
129
138
  options = ChatOptions(
130
139
  history=history or [],
@@ -137,6 +146,9 @@ def execute_ai_chat(
137
146
  agent_mode=agent_mode,
138
147
  max_agent_iterations=5,
139
148
  canvas_nodes=[],
149
+ apply_callbacks=agent_apply_callbacks,
150
+ ask_callbacks=agent_ask_callbacks,
151
+ dry_run_enabled=agent_apply_callbacks is not None,
140
152
  )
141
153
 
142
154
  try:
@@ -53,6 +53,7 @@ from app.cli.shared_services.generation_ops import (
53
53
  )
54
54
  from app.cli.shell.commands.base import Command, CommandResult, ProjectContext
55
55
  from app.cli.shell.formatter import Formatter
56
+ from app.shared.core.utils.path_utils import display_relpath
56
57
  from app.shared.services.llm.generation import (
57
58
  ConfigGenerationService,
58
59
  GenerationOptions,
@@ -200,7 +201,7 @@ class AIGenerateCommand(Command):
200
201
  print(Formatter.info(f"迭代次数: {max_iterations}"))
201
202
  print(Formatter.info("数据文件:"))
202
203
  for p in file_paths:
203
- print(f" - {os.path.relpath(p, project_path)}")
204
+ print(f" - {display_relpath(p, project_path)}")
204
205
 
205
206
  def progress_callback(stage: str, progress: float, extra: dict[str, Any] | None = None) -> None:
206
207
  """终端进度回调。"""
@@ -51,6 +51,7 @@ from app.cli.shared_services.generation_ops import (
51
51
  )
52
52
  from app.cli.shell.commands.base import Command, CommandResult, ProjectContext
53
53
  from app.cli.shell.formatter import Formatter
54
+ from app.shared.core.utils.path_utils import display_relpath
54
55
  from app.shared.services.ai.migrate_service import ConfigMigrationService
55
56
  from app.shared.services.llm.generation import (
56
57
  GenerationOptions,
@@ -72,6 +73,10 @@ def _infer_language(file_path: str) -> str:
72
73
  return "natural_language"
73
74
 
74
75
 
76
+ # --language 可选值(与 usage 声明一致)
77
+ _SUPPORTED_LANGUAGES = ("python", "sql", "excel_formula", "natural_language")
78
+
79
+
75
80
  class AIMigrateCommand(Command):
76
81
  """AI 配置迁移命令。
77
82
 
@@ -171,6 +176,9 @@ class AIMigrateCommand(Command):
171
176
 
172
177
  if language is None:
173
178
  language = _infer_language(script_path)
179
+ elif language not in _SUPPORTED_LANGUAGES:
180
+ # 枚举校验:非法值前置拒绝(usage 已声明可选值),不透传下游晦涩报错
181
+ return CommandResult.error(f"--language 仅支持 {'|'.join(_SUPPORTED_LANGUAGES)},收到: {language}")
174
182
 
175
183
  try:
176
184
  with open(script_path, encoding="utf-8") as f:
@@ -202,10 +210,10 @@ class AIMigrateCommand(Command):
202
210
 
203
211
  print(Formatter.header("\nAI 配置迁移"))
204
212
  print(Formatter.info(f"项目: {project_name}"))
205
- print(Formatter.info(f"脚本: {os.path.relpath(script_path, project_path)} ({language})"))
213
+ print(Formatter.info(f"脚本: {display_relpath(script_path, project_path)} ({language})"))
206
214
  print(Formatter.info("数据文件:"))
207
215
  for p in file_paths:
208
- print(f" - {os.path.relpath(p, project_path)}")
216
+ print(f" - {display_relpath(p, project_path)}")
209
217
 
210
218
  def progress_callback(stage: str, progress: float, extra: dict[str, Any] | None = None) -> None:
211
219
  """终端进度回调。"""
@@ -58,19 +58,20 @@ class AISwitchCommand(Command):
58
58
 
59
59
  @property
60
60
  def usage(self) -> str:
61
- return "ai switch <provider_id>"
61
+ return "ai switch [provider_id]"
62
62
 
63
63
  @property
64
64
  def help_text(self) -> str:
65
65
  return """
66
- 用法: ai switch <provider_id>
66
+ 用法: ai switch [provider_id]
67
67
 
68
68
  示例:
69
- ai switch openai # 切换到 OpenAI
70
- ai switch kimi # 切换到 Kimi
69
+ ai switch # 交互式选择 Provider(方向键导航)
70
+ ai switch openai # 切换到 OpenAI
71
+ ai switch kimi # 切换到 Kimi
71
72
 
72
73
  说明:
73
- 切换到已配置的 Provider。
74
+ 缺省参数时进入交互式选择;传入 provider_id 时直接切换(大小写不敏感)。
74
75
  使用 'ai status' 查看可用的 Provider。
75
76
  """.strip()
76
77