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.
Files changed (442) hide show
  1. app/__init__.py +16 -0
  2. app/api/__init__.py +32 -0
  3. app/api/dependencies.py +185 -0
  4. app/api/main.py +301 -0
  5. app/api/middleware/__init__.py +34 -0
  6. app/api/middleware/exception_handler.py +126 -0
  7. app/api/middleware/request_logging.py +97 -0
  8. app/api/middleware/token_auth.py +215 -0
  9. app/api/models/__init__.py +151 -0
  10. app/api/models/connection_rules.py +98 -0
  11. app/api/models/files.py +99 -0
  12. app/api/models/full_validation.py +554 -0
  13. app/api/models/project.py +117 -0
  14. app/api/models/projects.py +80 -0
  15. app/api/models/schema.py +121 -0
  16. app/api/models/v2_responses.py +46 -0
  17. app/api/models/validation.py +303 -0
  18. app/api/models/workspace.py +118 -0
  19. app/api/routers/__init__.py +56 -0
  20. app/api/routers/ai/__init__.py +36 -0
  21. app/api/routers/ai/chat.py +243 -0
  22. app/api/routers/ai/generate.py +156 -0
  23. app/api/routers/ai/hardware.py +199 -0
  24. app/api/routers/ai/jobs.py +544 -0
  25. app/api/routers/ai/migrate.py +471 -0
  26. app/api/routers/ai/models.py +352 -0
  27. app/api/routers/ai/ollama.py +108 -0
  28. app/api/routers/ai/providers.py +491 -0
  29. app/api/routers/ai/router.py +44 -0
  30. app/api/routers/ai/stream.py +281 -0
  31. app/api/routers/ai/utils.py +182 -0
  32. app/api/routers/core/__init__.py +34 -0
  33. app/api/routers/core/connection_rules.py +188 -0
  34. app/api/routers/core/data_sources.py +374 -0
  35. app/api/routers/core/regex.py +333 -0
  36. app/api/routers/core/reporting.py +263 -0
  37. app/api/routers/files/__init__.py +24 -0
  38. app/api/routers/files/ops.py +179 -0
  39. app/api/routers/files/transfer.py +101 -0
  40. app/api/routers/preview/__init__.py +35 -0
  41. app/api/routers/preview/content_mode.py +264 -0
  42. app/api/routers/preview/header_row.py +154 -0
  43. app/api/routers/preview/models.py +81 -0
  44. app/api/routers/preview/path_mode.py +440 -0
  45. app/api/routers/preview/router.py +30 -0
  46. app/api/routers/project/__init__.py +61 -0
  47. app/api/routers/project/base.py +111 -0
  48. app/api/routers/project/constraint.py +345 -0
  49. app/api/routers/project/full_config.py +411 -0
  50. app/api/routers/project/full_config_writer.py +327 -0
  51. app/api/routers/project/helpers.py +167 -0
  52. app/api/routers/project/inspection_fix.py +330 -0
  53. app/api/routers/project/manifest.py +605 -0
  54. app/api/routers/project/models.py +177 -0
  55. app/api/routers/project/pattern.py +268 -0
  56. app/api/routers/project/regex.py +358 -0
  57. app/api/routers/project/scanner.py +157 -0
  58. app/api/routers/project/schema.py +567 -0
  59. app/api/routers/project/schema_helpers.py +149 -0
  60. app/api/routers/project/settings.py +399 -0
  61. app/api/routers/project/template.py +325 -0
  62. app/api/routers/project/validation.py +227 -0
  63. app/api/routers/project/view.py +177 -0
  64. app/api/routers/project/workspaces.py +167 -0
  65. app/api/routers/projects/__init__.py +25 -0
  66. app/api/routers/projects/create.py +121 -0
  67. app/api/routers/projects/open.py +103 -0
  68. app/api/routers/projects/scan.py +100 -0
  69. app/api/routers/validation/__init__.py +39 -0
  70. app/api/routers/validation/common.py +263 -0
  71. app/api/routers/validation/content_mode.py +401 -0
  72. app/api/routers/validation/history.py +235 -0
  73. app/api/routers/validation/inline_mode.py +196 -0
  74. app/api/routers/validation/path_mode.py +314 -0
  75. app/api/routers/validation/router.py +51 -0
  76. app/api/services/full_validation_response_builder.py +412 -0
  77. app/api/services/io_error_messages.py +41 -0
  78. app/api/services/preview_service.py +248 -0
  79. app/cli/__init__.py +18 -0
  80. app/cli/__main__.py +30 -0
  81. app/cli/shared_services/__init__.py +42 -0
  82. app/cli/shared_services/config_ops.py +581 -0
  83. app/cli/shared_services/generation_ops.py +160 -0
  84. app/cli/shared_services/project_ops.py +288 -0
  85. app/cli/shell/__init__.py +48 -0
  86. app/cli/shell/commands/__init__.py +63 -0
  87. app/cli/shell/commands/ai/__init__.py +260 -0
  88. app/cli/shell/commands/ai/chat.py +331 -0
  89. app/cli/shell/commands/ai/delete.py +165 -0
  90. app/cli/shell/commands/ai/diff.py +70 -0
  91. app/cli/shell/commands/ai/display.py +428 -0
  92. app/cli/shell/commands/ai/executor.py +225 -0
  93. app/cli/shell/commands/ai/executor_utils.py +271 -0
  94. app/cli/shell/commands/ai/generate.py +285 -0
  95. app/cli/shell/commands/ai/interaction.py +262 -0
  96. app/cli/shell/commands/ai/migrate.py +281 -0
  97. app/cli/shell/commands/ai/resolver.py +224 -0
  98. app/cli/shell/commands/ai/status.py +101 -0
  99. app/cli/shell/commands/ai/switch.py +158 -0
  100. app/cli/shell/commands/ai/utils.py +52 -0
  101. app/cli/shell/commands/base.py +343 -0
  102. app/cli/shell/commands/config/__init__.py +131 -0
  103. app/cli/shell/commands/config/base.py +80 -0
  104. app/cli/shell/commands/config/check.py +246 -0
  105. app/cli/shell/commands/config/edit.py +148 -0
  106. app/cli/shell/commands/config/get.py +111 -0
  107. app/cli/shell/commands/config/init.py +123 -0
  108. app/cli/shell/commands/config/inspect.py +164 -0
  109. app/cli/shell/commands/config/list.py +104 -0
  110. app/cli/shell/commands/config/set.py +119 -0
  111. app/cli/shell/commands/config/show.py +150 -0
  112. app/cli/shell/commands/exit.py +67 -0
  113. app/cli/shell/commands/help.py +104 -0
  114. app/cli/shell/commands/infer_schema.py +156 -0
  115. app/cli/shell/commands/open.py +201 -0
  116. app/cli/shell/commands/project.py +215 -0
  117. app/cli/shell/commands/provider.py +619 -0
  118. app/cli/shell/commands/system.py +170 -0
  119. app/cli/shell/commands/validate.py +455 -0
  120. app/cli/shell/completer.py +231 -0
  121. app/cli/shell/config_storage.py +191 -0
  122. app/cli/shell/exceptions.py +90 -0
  123. app/cli/shell/formatter.py +371 -0
  124. app/cli/shell/interactive_menu.py +348 -0
  125. app/cli/shell/main.py +286 -0
  126. app/cli/shell/parser.py +266 -0
  127. app/cli/start.py +180 -0
  128. app/cli_main.py +52 -0
  129. app/mcp_server.py +311 -0
  130. app/shared/core/__init__.py +18 -0
  131. app/shared/core/app_version.py +59 -0
  132. app/shared/core/config/__init__.py +119 -0
  133. app/shared/core/config/server.py +193 -0
  134. app/shared/core/data_source/__init__.py +106 -0
  135. app/shared/core/data_source/loader.py +348 -0
  136. app/shared/core/data_source/loaders/__init__.py +213 -0
  137. app/shared/core/data_source/loaders/base.py +215 -0
  138. app/shared/core/data_source/loaders/converter.py +337 -0
  139. app/shared/core/data_source/loaders/csv_loader.py +252 -0
  140. app/shared/core/data_source/loaders/excel_loader.py +666 -0
  141. app/shared/core/data_source/loaders/extractor.py +268 -0
  142. app/shared/core/data_source/loaders/json_loader.py +354 -0
  143. app/shared/core/data_source/loaders/registry.py +88 -0
  144. app/shared/core/data_source/loaders/sql_loader.py +313 -0
  145. app/shared/core/data_source/loaders/strategies/__init__.py +100 -0
  146. app/shared/core/data_source/loaders/strategies/array_parser.py +277 -0
  147. app/shared/core/data_source/loaders/strategies/lines_parser.py +355 -0
  148. app/shared/core/data_source/loaders/strategies/object_parser.py +278 -0
  149. app/shared/core/data_source/schema_info.py +50 -0
  150. app/shared/core/data_source/specs/__init__.py +128 -0
  151. app/shared/core/data_source/specs/base.py +258 -0
  152. app/shared/core/data_source/specs/csv_source.py +137 -0
  153. app/shared/core/data_source/specs/excel_source.py +141 -0
  154. app/shared/core/data_source/specs/file_base.py +252 -0
  155. app/shared/core/data_source/specs/json_source.py +226 -0
  156. app/shared/core/data_source/specs/sql_source.py +161 -0
  157. app/shared/core/encoding.py +36 -0
  158. app/shared/core/io/__init__.py +24 -0
  159. app/shared/core/io/yaml.py +379 -0
  160. app/shared/core/manifest_schema/__init__.py +52 -0
  161. app/shared/core/manifest_schema/types.py +99 -0
  162. app/shared/core/manifest_schema/version.py +175 -0
  163. app/shared/core/patterns/__init__.py +24 -0
  164. app/shared/core/patterns/loader.py +134 -0
  165. app/shared/core/patterns/writer.py +244 -0
  166. app/shared/core/project/__init__.py +47 -0
  167. app/shared/core/project/constraint/builders/__init__.py +50 -0
  168. app/shared/core/project/constraint/builders/base.py +100 -0
  169. app/shared/core/project/constraint/builders/composite.py +77 -0
  170. app/shared/core/project/constraint/builders/conditional.py +67 -0
  171. app/shared/core/project/constraint/builders/foreign_key.py +53 -0
  172. app/shared/core/project/constraint/builders/registry.py +64 -0
  173. app/shared/core/project/constraint/builders/scripted.py +51 -0
  174. app/shared/core/project/constraint/builders/single_column.py +86 -0
  175. app/shared/core/project/constraint/builders/unique.py +53 -0
  176. app/shared/core/project/constraint/factory.py +170 -0
  177. app/shared/core/project/constraint/reader.py +214 -0
  178. app/shared/core/project/constraint/registry.py +233 -0
  179. app/shared/core/project/constraint/types/__init__.py +63 -0
  180. app/shared/core/project/constraint/types/constraint_file.py +261 -0
  181. app/shared/core/project/constraint/types/refs.py +460 -0
  182. app/shared/core/project/constraint/types.py +28 -0
  183. app/shared/core/project/constraint/writer.py +181 -0
  184. app/shared/core/project/loader/__init__.py +56 -0
  185. app/shared/core/project/loader/loader.py +30 -0
  186. app/shared/core/project/loader/loader_parts/config_inspector.py +137 -0
  187. app/shared/core/project/loader/loader_parts/embedded_constraints.py +224 -0
  188. app/shared/core/project/loader/loader_parts/file_loaders.py +58 -0
  189. app/shared/core/project/loader/loader_parts/inspection_ids.py +85 -0
  190. app/shared/core/project/loader/loader_parts/inspector_helpers.py +312 -0
  191. app/shared/core/project/loader/loader_parts/inspector_id_checks.py +274 -0
  192. app/shared/core/project/loader/loader_parts/inspector_reference_checks.py +690 -0
  193. app/shared/core/project/loader/loader_parts/inspector_uniqueness_checks.py +286 -0
  194. app/shared/core/project/loader/loader_parts/loading_error_messages.py +298 -0
  195. app/shared/core/project/loader/loader_parts/main.py +432 -0
  196. app/shared/core/project/loader/loader_parts/path_validation.py +117 -0
  197. app/shared/core/project/loader/loader_parts/runtime.py +125 -0
  198. app/shared/core/project/loader/types.py +246 -0
  199. app/shared/core/project/manifest/coverage.py +391 -0
  200. app/shared/core/project/manifest/reader.py +260 -0
  201. app/shared/core/project/manifest/types.py +91 -0
  202. app/shared/core/project/manifest/types_parts/__init__.py +60 -0
  203. app/shared/core/project/manifest/types_parts/constants.py +40 -0
  204. app/shared/core/project/manifest/types_parts/data_source.py +68 -0
  205. app/shared/core/project/manifest/types_parts/info.py +59 -0
  206. app/shared/core/project/manifest/types_parts/manifest.py +294 -0
  207. app/shared/core/project/manifest/types_parts/refs.py +153 -0
  208. app/shared/core/project/manifest/types_parts/settings.py +92 -0
  209. app/shared/core/project/manifest/types_parts/settings_file_processing.py +64 -0
  210. app/shared/core/project/manifest/types_parts/settings_script_security.py +78 -0
  211. app/shared/core/project/manifest/types_parts/settings_validation.py +83 -0
  212. app/shared/core/project/manifest/types_parts/template.py +66 -0
  213. app/shared/core/project/manifest/writer.py +262 -0
  214. app/shared/core/project/manual_data/__init__.py +27 -0
  215. app/shared/core/project/manual_data/types.py +75 -0
  216. app/shared/core/project/regex/reader.py +197 -0
  217. app/shared/core/project/regex/types.py +405 -0
  218. app/shared/core/project/regex/writer.py +123 -0
  219. app/shared/core/project/schema/reader.py +170 -0
  220. app/shared/core/project/schema/types.py +47 -0
  221. app/shared/core/project/schema/types_parts/__init__.py +36 -0
  222. app/shared/core/project/schema/types_parts/column.py +174 -0
  223. app/shared/core/project/schema/types_parts/column_utils.py +72 -0
  224. app/shared/core/project/schema/types_parts/constraint.py +165 -0
  225. app/shared/core/project/schema/types_parts/schema_id.py +66 -0
  226. app/shared/core/project/schema/types_parts/source.py +255 -0
  227. app/shared/core/project/schema/types_parts/source_options.py +347 -0
  228. app/shared/core/project/schema/types_parts/table.py +230 -0
  229. app/shared/core/project/schema/writer.py +139 -0
  230. app/shared/core/project/schema_ref_check.py +95 -0
  231. app/shared/core/project/template/__init__.py +27 -0
  232. app/shared/core/project/template/expander.py +263 -0
  233. app/shared/core/project/template/reader.py +120 -0
  234. app/shared/core/project/template/types.py +114 -0
  235. app/shared/core/project/transform/reader.py +76 -0
  236. app/shared/core/project/transform/types.py +116 -0
  237. app/shared/core/project/transform/writer.py +84 -0
  238. app/shared/core/pydantic_messages.py +59 -0
  239. app/shared/core/reporter/__init__.py +46 -0
  240. app/shared/core/reporter/reporter.py +220 -0
  241. app/shared/core/reporter/reporters/__init__.py +65 -0
  242. app/shared/core/reporter/reporters/base.py +188 -0
  243. app/shared/core/reporter/reporters/dingtalk_app_reporter.py +274 -0
  244. app/shared/core/reporter/reporters/email_reporter.py +271 -0
  245. app/shared/core/reporter/reporters/feishu_app_reporter.py +467 -0
  246. app/shared/core/reporter/reporters/local_file_reporter.py +208 -0
  247. app/shared/core/reporter/reporters/wecom_app_reporter.py +268 -0
  248. app/shared/core/utils/__init__.py +18 -0
  249. app/shared/core/utils/path_utils.py +60 -0
  250. app/shared/core/utils/regex_extract.py +113 -0
  251. app/shared/domain/__init__.py +114 -0
  252. app/shared/domain/constraints/__init__.py +76 -0
  253. app/shared/domain/constraints/allowed_values.py +211 -0
  254. app/shared/domain/constraints/base.py +141 -0
  255. app/shared/domain/constraints/charset.py +337 -0
  256. app/shared/domain/constraints/composite.py +174 -0
  257. app/shared/domain/constraints/condition_registry.py +130 -0
  258. app/shared/domain/constraints/conditional.py +629 -0
  259. app/shared/domain/constraints/date_logic.py +731 -0
  260. app/shared/domain/constraints/foreign_key.py +261 -0
  261. app/shared/domain/constraints/key_normalization.py +56 -0
  262. app/shared/domain/constraints/not_null.py +185 -0
  263. app/shared/domain/constraints/range.py +360 -0
  264. app/shared/domain/constraints/regex.py +218 -0
  265. app/shared/domain/constraints/scripted.py +276 -0
  266. app/shared/domain/constraints/unique.py +233 -0
  267. app/shared/domain/data_engine.py +384 -0
  268. app/shared/domain/data_types.py +113 -0
  269. app/shared/domain/data_types_parts/__init__.py +67 -0
  270. app/shared/domain/data_types_parts/base.py +204 -0
  271. app/shared/domain/data_types_parts/composite.py +250 -0
  272. app/shared/domain/data_types_parts/expression.py +202 -0
  273. app/shared/domain/data_types_parts/extracted.py +106 -0
  274. app/shared/domain/data_types_parts/json_types.py +234 -0
  275. app/shared/domain/data_types_parts/scalars.py +719 -0
  276. app/shared/domain/data_types_parts/sequence.py +129 -0
  277. app/shared/domain/dataset_schema.py +48 -0
  278. app/shared/domain/eval_sandbox.py +63 -0
  279. app/shared/domain/expression_system.py +366 -0
  280. app/shared/domain/regex_flags.py +53 -0
  281. app/shared/domain/schema/builder.py +223 -0
  282. app/shared/domain/schema/models.py +339 -0
  283. app/shared/domain/transforms/__init__.py +28 -0
  284. app/shared/domain/transforms/aggregate.py +141 -0
  285. app/shared/domain/transforms/base.py +171 -0
  286. app/shared/domain/transforms/cast_type.py +120 -0
  287. app/shared/domain/transforms/concat.py +103 -0
  288. app/shared/domain/transforms/conditional_assign.py +114 -0
  289. app/shared/domain/transforms/date_format.py +81 -0
  290. app/shared/domain/transforms/digits.py +77 -0
  291. app/shared/domain/transforms/drop_duplicates.py +94 -0
  292. app/shared/domain/transforms/fill_na.py +94 -0
  293. app/shared/domain/transforms/filter_rows.py +97 -0
  294. app/shared/domain/transforms/lookup.py +78 -0
  295. app/shared/domain/transforms/lower_case.py +70 -0
  296. app/shared/domain/transforms/map_value.py +90 -0
  297. app/shared/domain/transforms/math_expr.py +112 -0
  298. app/shared/domain/transforms/modulo.py +82 -0
  299. app/shared/domain/transforms/regex_extract.py +103 -0
  300. app/shared/domain/transforms/registry.py +95 -0
  301. app/shared/domain/transforms/replace.py +88 -0
  302. app/shared/domain/transforms/sort_rows.py +94 -0
  303. app/shared/domain/transforms/string_split.py +84 -0
  304. app/shared/domain/transforms/strip.py +73 -0
  305. app/shared/domain/transforms/substring.py +92 -0
  306. app/shared/domain/transforms/upper_case.py +70 -0
  307. app/shared/domain/transforms/weighted_sum.py +104 -0
  308. app/shared/domain/validation_constraints.py +69 -0
  309. app/shared/services/__init__.py +62 -0
  310. app/shared/services/ai/__init__.py +48 -0
  311. app/shared/services/ai/agent/__init__.py +40 -0
  312. app/shared/services/ai/agent/chat_tools/__init__.py +47 -0
  313. app/shared/services/ai/agent/chat_tools/apply_actions.py +592 -0
  314. app/shared/services/ai/agent/chat_tools/ask_user.py +237 -0
  315. app/shared/services/ai/agent/chat_tools/read_canvas.py +140 -0
  316. app/shared/services/ai/agent/chat_tools/read_project.py +160 -0
  317. app/shared/services/ai/agent/chat_tools/read_table.py +301 -0
  318. app/shared/services/ai/agent/chat_tools/schemas.py +105 -0
  319. app/shared/services/ai/agent/chat_tools/validate_table.py +131 -0
  320. app/shared/services/ai/agent/executor.py +554 -0
  321. app/shared/services/ai/agent/memory.py +256 -0
  322. app/shared/services/ai/agent/planner.py +282 -0
  323. app/shared/services/ai/agent/tool_registry.py +296 -0
  324. app/shared/services/ai/agent/tools/__init__.py +32 -0
  325. app/shared/services/ai/agent/tools/config_generate.py +133 -0
  326. app/shared/services/ai/agent/tools/config_refine.py +109 -0
  327. app/shared/services/ai/agent/tools/config_validate.py +403 -0
  328. app/shared/services/ai/agent/tools/merge_results.py +136 -0
  329. app/shared/services/ai/agent/tools/plan_chunks.py +79 -0
  330. app/shared/services/ai/agent/tools/script_parse.py +359 -0
  331. app/shared/services/ai/agent/types.py +138 -0
  332. app/shared/services/ai/chat_agent_runner.py +596 -0
  333. app/shared/services/ai/chat_orchestrator.py +725 -0
  334. app/shared/services/ai/failure_messages.py +65 -0
  335. app/shared/services/ai/job_storage.py +274 -0
  336. app/shared/services/ai/migrate_service.py +550 -0
  337. app/shared/services/ai/streaming/__init__.py +65 -0
  338. app/shared/services/ai/streaming/event_journal.py +197 -0
  339. app/shared/services/ai/streaming/orchestrator.py +262 -0
  340. app/shared/services/ai/streaming/pending_interaction_store.py +227 -0
  341. app/shared/services/ai/streaming/sse_response.py +148 -0
  342. app/shared/services/ai/streaming/types.py +73 -0
  343. app/shared/services/ai/types.py +213 -0
  344. app/shared/services/ai/utils.py +322 -0
  345. app/shared/services/diff/config_diff.py +320 -0
  346. app/shared/services/hardware.py +237 -0
  347. app/shared/services/llm/__init__.py +62 -0
  348. app/shared/services/llm/actions/__init__.py +42 -0
  349. app/shared/services/llm/actions/_canvas_validator.py +147 -0
  350. app/shared/services/llm/actions/_constraint_validator.py +401 -0
  351. app/shared/services/llm/actions/_regex_validator.py +85 -0
  352. app/shared/services/llm/actions/_schema_validator.py +82 -0
  353. app/shared/services/llm/actions/_settings_validator.py +76 -0
  354. app/shared/services/llm/actions/_transform_validator.py +78 -0
  355. app/shared/services/llm/actions/action_handlers.py +400 -0
  356. app/shared/services/llm/actions/action_parser.py +63 -0
  357. app/shared/services/llm/actions/action_processor.py +468 -0
  358. app/shared/services/llm/actions/action_validator.py +311 -0
  359. app/shared/services/llm/actions/diff_compute.py +195 -0
  360. app/shared/services/llm/actions/regex_handlers.py +284 -0
  361. app/shared/services/llm/actions/registry.py +336 -0
  362. app/shared/services/llm/actions/schema_handlers.py +335 -0
  363. app/shared/services/llm/actions/settings_handlers.py +180 -0
  364. app/shared/services/llm/actions/specs.py +259 -0
  365. app/shared/services/llm/actions/transform_handlers.py +279 -0
  366. app/shared/services/llm/actions/validation_types.py +147 -0
  367. app/shared/services/llm/cache/__init__.py +18 -0
  368. app/shared/services/llm/cache/response_cache.py +80 -0
  369. app/shared/services/llm/chat/__init__.py +35 -0
  370. app/shared/services/llm/chat/chat_system_prompt.py +561 -0
  371. app/shared/services/llm/chat/response_parser.py +296 -0
  372. app/shared/services/llm/config/__init__.py +45 -0
  373. app/shared/services/llm/config/crypto.py +135 -0
  374. app/shared/services/llm/config/loader.py +258 -0
  375. app/shared/services/llm/config/models.py +202 -0
  376. app/shared/services/llm/config/presets.py +128 -0
  377. app/shared/services/llm/config_generator.py +163 -0
  378. app/shared/services/llm/constraints/__init__.py +41 -0
  379. app/shared/services/llm/constraints/constraint_builder.py +177 -0
  380. app/shared/services/llm/constraints/constraint_deletion.py +84 -0
  381. app/shared/services/llm/constraints/constraint_id.py +174 -0
  382. app/shared/services/llm/constraints/frontend_instructions.py +333 -0
  383. app/shared/services/llm/constraints/inline_batch.py +279 -0
  384. app/shared/services/llm/discovery/__init__.py +35 -0
  385. app/shared/services/llm/discovery/scanner.py +181 -0
  386. app/shared/services/llm/generation/__init__.py +42 -0
  387. app/shared/services/llm/generation/agent_wiring.py +156 -0
  388. app/shared/services/llm/generation/config_builder.py +386 -0
  389. app/shared/services/llm/generation/errors.py +36 -0
  390. app/shared/services/llm/generation/existing_config.py +99 -0
  391. app/shared/services/llm/generation/profiler.py +279 -0
  392. app/shared/services/llm/generation/prompt_builder.py +199 -0
  393. app/shared/services/llm/generation/response_parser.py +144 -0
  394. app/shared/services/llm/generation/service.py +622 -0
  395. app/shared/services/llm/models.py +104 -0
  396. app/shared/services/llm/providers/__init__.py +48 -0
  397. app/shared/services/llm/providers/base.py +310 -0
  398. app/shared/services/llm/providers/cached_provider.py +90 -0
  399. app/shared/services/llm/providers/ollama.py +498 -0
  400. app/shared/services/llm/providers/openai.py +334 -0
  401. app/shared/services/llm/providers/registry.py +108 -0
  402. app/shared/services/llm/schema_resolver.py +141 -0
  403. app/shared/services/llm/suggestion_utils.py +219 -0
  404. app/shared/services/llm/validate_executor.py +163 -0
  405. app/shared/services/llm/yaml_io.py +406 -0
  406. app/shared/services/preview/__init__.py +18 -0
  407. app/shared/services/preview/loader.py +145 -0
  408. app/shared/services/preview/path_validation.py +138 -0
  409. app/shared/services/project_loader.py +57 -0
  410. app/shared/services/schema_inference.py +260 -0
  411. app/shared/services/schema_runtime_builder.py +120 -0
  412. app/shared/services/validation/__init__.py +52 -0
  413. app/shared/services/validation/chunked_loader.py +509 -0
  414. app/shared/services/validation/dag/__init__.py +35 -0
  415. app/shared/services/validation/dag/builder.py +141 -0
  416. app/shared/services/validation/dag/executor.py +225 -0
  417. app/shared/services/validation/dag/sorter.py +77 -0
  418. app/shared/services/validation/data_loader.py +231 -0
  419. app/shared/services/validation/engine.py +446 -0
  420. app/shared/services/validation/executor.py +1030 -0
  421. app/shared/services/validation/extractors.py +266 -0
  422. app/shared/services/validation/history.py +209 -0
  423. app/shared/services/validation/json_payload.py +136 -0
  424. app/shared/services/validation/loader.py +152 -0
  425. app/shared/services/validation/memory_monitor.py +179 -0
  426. app/shared/services/validation/postprocess.py +208 -0
  427. app/shared/services/validation/progress.py +64 -0
  428. app/shared/services/validation/report_export.py +222 -0
  429. app/shared/services/validation/resolver.py +180 -0
  430. app/shared/services/validation/service.py +418 -0
  431. app/shared/services/validation/types.py +238 -0
  432. app/shared/services/validation/validators/__init__.py +36 -0
  433. app/shared/services/validation/validators/adapter.py +182 -0
  434. app/shared/services/validation/validators/base.py +332 -0
  435. app/shared/services/validation/validators/composite.py +233 -0
  436. app/shared/services/validation/validators/date_logic.py +276 -0
  437. app/start_server.py +133 -0
  438. precis_cli-0.1.3.dist-info/METADATA +180 -0
  439. precis_cli-0.1.3.dist-info/RECORD +442 -0
  440. precis_cli-0.1.3.dist-info/WHEEL +5 -0
  441. precis_cli-0.1.3.dist-info/entry_points.txt +4 -0
  442. precis_cli-0.1.3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,123 @@
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
+ # backend/app/cli/shell/commands/config/init.py
17
+ """
18
+ @fileoverview 配置初始化命令模块
19
+
20
+ 功能概述:
21
+ - 提供 config init 子命令基于模板创建新配置文件
22
+ - 支持 project、constraint、pattern 三种模板类型
23
+ - 自动使用项目名称填充模板变量
24
+
25
+ 架构设计:
26
+ - ConfigInitCommand 继承 Command 基类
27
+ - 使用字典映射模板类型到 (默认文件名, 模板内容)
28
+ - 检查文件是否已存在,避免意外覆盖
29
+
30
+ 输入示例:
31
+ config init project
32
+ config init constraint my_constraints.yaml
33
+
34
+ 输出示例:
35
+ CommandResult.ok("已创建配置文件: project.precis.yaml")
36
+ CommandResult.error("文件已存在: project.precis.yaml")
37
+ """
38
+
39
+ import os
40
+
41
+ from app.cli.shell.commands.base import Command, CommandResult, ProjectContext
42
+ from app.cli.shell.commands.config.base import CONSTRAINT_TEMPLATE, PATTERNS_TEMPLATE, PROJECT_TEMPLATE
43
+
44
+
45
+ class ConfigInitCommand(Command):
46
+ """初始化配置命令。
47
+
48
+ 基于内置模板创建新的配置文件。
49
+ """
50
+
51
+ def __init__(self) -> None:
52
+ super().__init__("init")
53
+
54
+ @property
55
+ def description(self) -> str:
56
+ return "初始化新的配置文件"
57
+
58
+ @property
59
+ def usage(self) -> str:
60
+ return "config init <project|constraint|pattern> [filename]"
61
+
62
+ def execute(self, args: list[str], context: ProjectContext) -> CommandResult:
63
+ """执行初始化配置命令。
64
+
65
+ Args:
66
+ args: 命令参数列表,第一个为模板类型,第二个可选为文件名
67
+ context: 项目上下文
68
+
69
+ Returns:
70
+ 创建成功或失败的结果
71
+ """
72
+ project_path = context.project_path
73
+ if project_path is None:
74
+ return CommandResult.error("未打开项目,请先使用 'open <path>' 命令打开项目")
75
+
76
+ if not args:
77
+ return CommandResult.error(
78
+ "请指定模板类型: project, constraint, pattern\n用法: config init <type> [filename]"
79
+ )
80
+
81
+ template_type = args[0].lower()
82
+
83
+ # 模板类型映射:类型 -> (默认文件名, 模板内容)
84
+ templates = {
85
+ "project": ("project.precis.yaml", PROJECT_TEMPLATE),
86
+ "constraint": ("constraints.yaml", CONSTRAINT_TEMPLATE),
87
+ "pattern": ("patterns.yaml", PATTERNS_TEMPLATE),
88
+ "patterns": ("patterns.yaml", PATTERNS_TEMPLATE),
89
+ }
90
+
91
+ if template_type not in templates:
92
+ return CommandResult.error(f"未知模板类型: {template_type}\n可用类型: project, constraint, pattern")
93
+
94
+ default_filename, template = templates[template_type]
95
+
96
+ # 使用指定的文件名或默认文件名
97
+ filename = args[1] if len(args) > 1 else default_filename
98
+ # 4.7: 路径穿越防护——对齐 config set 的防护语义(禁止绝对路径/../~
99
+ # 前缀),否则 config init ../../x.yaml 可写项目外
100
+ from pathlib import Path as _Path
101
+
102
+ if os.path.isabs(filename) or any(seg == ".." for seg in _Path(filename).parts) or filename.startswith("~"):
103
+ return CommandResult.error(f"非法的文件名(不允许绝对路径或 .. 引用): {filename}")
104
+ filepath = os.path.join(project_path, filename)
105
+ if not os.path.abspath(filepath).startswith(os.path.abspath(project_path) + os.sep):
106
+ return CommandResult.error(f"文件路径超出项目目录: {filename}")
107
+
108
+ # 检查文件是否已存在
109
+ if os.path.exists(filepath):
110
+ return CommandResult.error(f"文件已存在: {filename}\n如需覆盖,请先删除原文件")
111
+
112
+ # 生成文件内容(使用项目名称填充模板变量)。
113
+ # 用 replace 而非 str.format:模板内含正则量词 {2,} 等花括号字面量,
114
+ # format 会把它们当替换字段抛 KeyError(config init pattern 必崩)
115
+ project_name = os.path.basename(project_path)
116
+ content = template.replace("{project_name}", project_name)
117
+
118
+ try:
119
+ with open(filepath, "w", encoding="utf-8") as f:
120
+ f.write(content)
121
+ return CommandResult.ok(f"已创建配置文件: {filename}")
122
+ except Exception as e:
123
+ return CommandResult.error(f"创建文件失败: {e}")
@@ -0,0 +1,164 @@
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
+ # backend/app/cli/shell/commands/config/inspect.py
17
+ """
18
+ @fileoverview 配置自检命令模块
19
+
20
+ 功能概述:
21
+ - 提供 config inspect 子命令,执行项目配置的跨文件一致性自检
22
+ - 复用后端 inspect_config 检查引擎(ID 一致性、引用完整性、数据源冲突等)
23
+ - 将检查结果格式化为友好的 CLI 输出(按严重度分组、显示修复建议)
24
+
25
+ 架构设计:
26
+ - ConfigInspectCommand 继承 Command 基类
27
+ - 通过 load_project() 加载项目(内部已执行 inspect_config,结果在 loading_errors)
28
+ - _format_report(): 将 LoadingError 列表格式化为可读文本
29
+
30
+ 输入示例:
31
+ config inspect
32
+
33
+ 输出示例:
34
+ CommandResult.ok("✓ 配置自检通过,未发现问题")
35
+ CommandResult.error("配置自检发现 3 个问题:\n...")
36
+ """
37
+
38
+ from __future__ import annotations
39
+
40
+ from app.cli.shell.commands.base import Command, CommandResult, ProjectContext
41
+ from app.cli.shell.formatter import _supports_unicode
42
+ from app.shared.core.project.loader.loader_parts.main import load_project
43
+ from app.shared.core.project.loader.types import LoadingError
44
+
45
+ _CHECK_MARK = "\u2713" if _supports_unicode() else "[OK]"
46
+ _CROSS_MARK = "\u2717" if _supports_unicode() else "[FAIL]"
47
+
48
+ # 严重度展示符号与标签
49
+ _SEVERITY_DISPLAY = {
50
+ "blocker": ("🔴", "阻塞"),
51
+ "warning": ("⚠️", "警告"),
52
+ "info": ("ℹ️", "提示"),
53
+ }
54
+
55
+
56
+ class ConfigInspectCommand(Command):
57
+ """配置自检命令。
58
+
59
+ 执行项目配置的跨文件一致性检查,复用后端 inspect_config 引擎。
60
+ """
61
+
62
+ def __init__(self) -> None:
63
+ super().__init__("inspect")
64
+
65
+ @property
66
+ def description(self) -> str:
67
+ return "执行配置文件跨文件一致性自检(ID 一致性、引用完整性、数据源冲突)"
68
+
69
+ @property
70
+ def usage(self) -> str:
71
+ return "config inspect"
72
+
73
+ def execute(self, args: list[str], context: ProjectContext) -> CommandResult:
74
+ """执行配置自检命令。
75
+
76
+ 通过 load_project 加载项目,复用其内置的 inspect_config 检查逻辑,
77
+ 将收集到的 loading_errors 格式化为友好的 CLI 报告。
78
+
79
+ Args:
80
+ args: 命令参数列表(本命令不接收参数)
81
+ context: 项目上下文
82
+
83
+ Returns:
84
+ 自检结果,成功表示无问题,失败表示发现问题
85
+ """
86
+ project_path = context.project_path
87
+ if project_path is None:
88
+ return CommandResult.error("未打开项目,请先使用 'open <path>' 命令打开项目")
89
+
90
+ # 定位 manifest 文件
91
+ import os
92
+
93
+ manifest_path = os.path.join(project_path, "project.precis.yaml")
94
+ if not os.path.isfile(manifest_path):
95
+ # 兼容 .yml 后缀
96
+ alt = os.path.join(project_path, "project.precis.yml")
97
+ if os.path.isfile(alt):
98
+ manifest_path = alt
99
+ else:
100
+ return CommandResult.error(f"未找到项目清单文件 project.precis.yaml(在 {project_path} 下)")
101
+
102
+ # 复用 load_project:其内部已执行 inspect_config,结果在 loading_errors
103
+ try:
104
+ loaded = load_project(manifest_path)
105
+ except Exception as e:
106
+ return CommandResult.error(f"加载项目失败: {e}")
107
+
108
+ errors: list[LoadingError] = loaded.loading_errors or []
109
+ warnings: list[str] = loaded.warnings or []
110
+
111
+ if not errors:
112
+ extra = f"\n(另有 {len(warnings)} 条加载警告)" if warnings else ""
113
+ return CommandResult.ok(f"{_CHECK_MARK} 配置自检通过,未发现问题{extra}")
114
+
115
+ report = self._format_report(errors)
116
+ # 有 blocker 时整体视为失败,否则视为成功附带警告
117
+ has_blocker = any(e.severity == "blocker" for e in errors)
118
+ summary = f"配置自检发现 {len(errors)} 个问题"
119
+ if has_blocker:
120
+ return CommandResult.error(f"{summary}:\n{report}")
121
+ return CommandResult.ok(f"{summary}(均为非阻塞,建议处理):\n{report}")
122
+
123
+ def _format_report(self, errors: list[LoadingError]) -> str:
124
+ """将 LoadingError 列表格式化为可读文本报告。
125
+
126
+ 按 severity 分组展示,每组下列出各问题的标题、文件路径与修复建议。
127
+
128
+ Args:
129
+ errors: 自检收集的错误列表
130
+
131
+ Returns:
132
+ 格式化的多行文本报告
133
+ """
134
+ lines: list[str] = []
135
+
136
+ # 按严重度分组:blocker → warning → info
137
+ severity_order = ["blocker", "warning", "info"]
138
+ grouped: dict[str, list[LoadingError]] = {s: [] for s in severity_order}
139
+ for err in errors:
140
+ grouped.setdefault(err.severity, []).append(err)
141
+
142
+ for sev in severity_order:
143
+ group = grouped.get(sev, [])
144
+ if not group:
145
+ continue
146
+ icon, label = _SEVERITY_DISPLAY.get(sev, ("•", sev))
147
+ lines.append(f"\n{icon} {label}({len(group)})")
148
+
149
+ for err in group:
150
+ title = err.title or err.error_type or "未知问题"
151
+ lines.append(f" {_CROSS_MARK} {title}")
152
+ if err.file_path:
153
+ lines.append(f" 文件: {err.file_path}")
154
+ if err.ref_id:
155
+ lines.append(f" 编号: {err.ref_id}")
156
+ if err.description:
157
+ lines.append(f" 说明: {err.description}")
158
+ if err.fix_hint:
159
+ lines.append(f" 建议: {err.fix_hint}")
160
+ # 若有一键修复 API,提示用户(CLI 暂不自动修复,但告知可修复)
161
+ if err.fix_api:
162
+ lines.append(f" 可修复: {err.fix_api.get('method', 'POST')} {err.fix_api.get('path', '')}")
163
+
164
+ return "\n".join(lines)
@@ -0,0 +1,104 @@
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
+ # backend/app/cli/shell/commands/config/list.py
17
+ """
18
+ @fileoverview 配置列表命令模块
19
+
20
+ 功能概述:
21
+ - 提供 config list 子命令列出项目中所有 YAML 配置文件
22
+ - 递归扫描 schemas、constraints、patterns、regex 目录
23
+ - 格式化显示文件名与大小
24
+
25
+ 架构设计:
26
+ - ConfigListCommand 继承 Command 基类
27
+ - 先扫描项目根目录,再递归扫描子目录
28
+ - _format_size(): 将字节转换为人类可读格式(B/KB/MB)
29
+
30
+ 输入示例:
31
+ config list
32
+
33
+ 输出示例:
34
+ 配置文件列表表格
35
+ """
36
+
37
+ from app.cli.shared_services.config_ops import list_config_files
38
+ from app.cli.shell.commands.base import Command, CommandResult, ProjectContext
39
+ from app.cli.shell.formatter import Formatter
40
+
41
+
42
+ class ConfigListCommand(Command):
43
+ """列出配置命令。
44
+
45
+ 显示项目中所有 YAML 配置文件及其大小。
46
+ """
47
+
48
+ def __init__(self) -> None:
49
+ super().__init__("list")
50
+
51
+ @property
52
+ def description(self) -> str:
53
+ return "列出项目中的所有配置文件"
54
+
55
+ @property
56
+ def usage(self) -> str:
57
+ return "config list"
58
+
59
+ def execute(self, args: list[str], context: ProjectContext) -> CommandResult:
60
+ """执行列出配置命令。
61
+
62
+ Args:
63
+ args: 命令参数列表(此命令不需要参数)
64
+ context: 项目上下文
65
+
66
+ Returns:
67
+ 配置文件列表或提示信息
68
+ """
69
+ project_path = context.project_path
70
+ if project_path is None:
71
+ return CommandResult.error("未打开项目,请先使用 'open <path>' 命令打开项目")
72
+
73
+ # 扫描配置文件(委托 shared_services 纯逻辑,CLI/TUI 同源)
74
+ config_files = list_config_files(project_path)
75
+
76
+ if not config_files:
77
+ return CommandResult.ok("暂无配置文件")
78
+
79
+ output_lines = [Formatter.header("\n配置文件列表:")]
80
+ output_lines.append(f"{'文件名':<40} {'大小':>10}")
81
+ output_lines.append("-" * 52)
82
+
83
+ for cf in config_files:
84
+ size_str = self._format_size(cf.size)
85
+ output_lines.append(f"{cf.name:<40} {size_str:>10}")
86
+
87
+ output_lines.append(f"\n共 {len(config_files)} 个配置文件")
88
+ return CommandResult.ok("\n".join(output_lines))
89
+
90
+ def _format_size(self, size: int) -> str:
91
+ """格式化文件大小。
92
+
93
+ Args:
94
+ size: 文件大小(字节)
95
+
96
+ Returns:
97
+ 人类可读的字符串,如 "1.5KB"
98
+ """
99
+ if size < 1024:
100
+ return f"{size}B"
101
+ elif size < 1024 * 1024:
102
+ return f"{size / 1024:.1f}KB"
103
+ else:
104
+ return f"{size / (1024 * 1024):.1f}MB"
@@ -0,0 +1,119 @@
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
+ # backend/app/cli/shell/commands/config/set.py
17
+ """
18
+ @fileoverview 配置设置命令模块
19
+
20
+ 功能概述:
21
+ - 提供 config set 子命令按点号路径设置配置项值
22
+ - 支持布尔值、整数、浮点数、列表、字典与字符串的自动类型解析
23
+ - 保留 YAML 注释与格式,原子写回,路径解析含穿越防护
24
+
25
+ 架构设计:
26
+ - ConfigSetCommand 继承 Command 基类
27
+ - _parse_value(): 自动推断值的类型(bool -> int -> float -> yaml -> str),已收敛至
28
+ shared_services 的 parse_config_value(CLI/TUI 同源)
29
+ - 文件定位/写入统一委托 shared_services 的 set_config_value_in_file:
30
+ find_config_file 防路径穿越 + ruamel round-trip 仅替换目标键的值(保留注释)
31
+ + 临时文件原子替换
32
+
33
+ 输入示例:
34
+ config set project.precis.yaml project.name "My Project"
35
+ config set project.precis.yaml validation.auto_validate true
36
+
37
+ 输出示例:
38
+ CommandResult.ok("已设置: project.name = My Project")
39
+ CommandResult.error("配置文件不存在: project.precis.yaml")
40
+ """
41
+
42
+ from app.cli.shared_services.config_ops import (
43
+ MATCH_BASENAME,
44
+ parse_config_value,
45
+ resolve_config_file,
46
+ set_config_value_in_file,
47
+ )
48
+ from app.cli.shell.commands.base import Command, CommandResult, ProjectContext
49
+
50
+
51
+ class ConfigSetCommand(Command):
52
+ """设置配置项命令。
53
+
54
+ 按点号路径设置 YAML 配置文件中指定项的值。
55
+ """
56
+
57
+ def __init__(self) -> None:
58
+ super().__init__("set")
59
+
60
+ @property
61
+ def description(self) -> str:
62
+ return "设置配置项的值(支持点号路径)"
63
+
64
+ @property
65
+ def usage(self) -> str:
66
+ return "config set [--string] <config_file> <key_path> <value>"
67
+
68
+ def execute(self, args: list[str], context: ProjectContext) -> CommandResult:
69
+ """执行设置配置项命令。
70
+
71
+ Args:
72
+ args: 命令参数列表,需要包含文件名、路径和值
73
+ context: 项目上下文
74
+
75
+ Returns:
76
+ 设置成功或失败的结果
77
+ """
78
+ project_path = context.project_path
79
+ if project_path is None:
80
+ return CommandResult.error("未打开项目,请先使用 'open <path>' 命令打开项目")
81
+
82
+ # §4.3: --string 旗标——身份型字段(编号 007/版本 1.10)跳过类型推断按原字面量写入
83
+ raw_args = args
84
+ as_string = False
85
+ if raw_args and raw_args[0] == "--string":
86
+ as_string = True
87
+ raw_args = raw_args[1:]
88
+
89
+ if len(raw_args) < 3:
90
+ return CommandResult.error(
91
+ "用法: config set [--string] <config_file> <key_path> <value>\n"
92
+ '示例: config set project.precis.yaml project.name "My Project"\n'
93
+ " config set --string users.schema.yaml col.code 007 # 保留前导零"
94
+ )
95
+
96
+ config_file = raw_args[0]
97
+ key_path = raw_args[1]
98
+ value_str = raw_args[2]
99
+
100
+ # 解析值(委托 shared_services 纯逻辑,CLI/TUI 同源)
101
+ # parse_config_value 始终成功返回三元组,行为与原 _parse_value 一致
102
+ value = value_str if as_string else parse_config_value(value_str)[1]
103
+
104
+ # 定位结果(用于文件名模糊回退命中时披露实际写入路径)
105
+ resolved_path, match_kind = resolve_config_file(project_path, config_file)
106
+
107
+ # 定位文件并写入(委托 shared_services,CLI/TUI 同源):
108
+ # - find_config_file 统一路径解析,含 .. / 绝对路径等穿越防护与递归回退
109
+ # - ruamel round-trip 仅替换目标键的值,保留注释与格式
110
+ # - 临时文件 + os.replace 原子写回,失败时不损坏原文件
111
+ ok, error_message = set_config_value_in_file(project_path, config_file, key_path, value)
112
+ if not ok:
113
+ return CommandResult.error(error_message)
114
+
115
+ message = f"已设置: {key_path} = {value}"
116
+ if match_kind == MATCH_BASENAME and resolved_path:
117
+ # 模糊回退命中的是同名文件而非用户输入路径,必须披露实际写入位置
118
+ message += f"(按文件名回退匹配,实际写入: {resolved_path})"
119
+ return CommandResult.ok(message)
@@ -0,0 +1,150 @@
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
+ # backend/app/cli/shell/commands/config/show.py
17
+ """
18
+ @fileoverview 配置显示命令模块
19
+
20
+ 功能概述:
21
+ - 提供 config show 子命令显示项目配置文件内容
22
+ - 支持单文件显示与默认配置文件批量显示
23
+ - 使用 YAML 格式化输出
24
+
25
+ 架构设计:
26
+ - ConfigShowCommand 继承 Command 基类
27
+ - 指定文件时调用 _show_single_file() 显示单个文件
28
+ - 未指定时显示一组默认配置文件的内容
29
+
30
+ 输入示例:
31
+ config show
32
+ config show project.precis.yaml
33
+
34
+ 输出示例:
35
+ 格式化的 YAML 配置内容
36
+ """
37
+
38
+ import os
39
+
40
+ import yaml
41
+
42
+ from app.cli.shared_services.config_ops import find_config_file
43
+ from app.cli.shell.commands.base import Command, CommandResult, ProjectContext
44
+ from app.cli.shell.formatter import Formatter
45
+
46
+
47
+ class ConfigShowCommand(Command):
48
+ """显示配置命令。
49
+
50
+ 显示项目配置文件的内容,支持单文件或批量显示。
51
+ """
52
+
53
+ def __init__(self) -> None:
54
+ super().__init__("show")
55
+
56
+ @property
57
+ def description(self) -> str:
58
+ return "显示当前项目的配置文件内容"
59
+
60
+ @property
61
+ def usage(self) -> str:
62
+ return "config show [config_file]"
63
+
64
+ def execute(self, args: list[str], context: ProjectContext) -> CommandResult:
65
+ """执行显示配置命令。
66
+
67
+ Args:
68
+ args: 命令参数列表,可能包含要显示的文件名
69
+ context: 项目上下文
70
+
71
+ Returns:
72
+ 配置文件内容或错误提示
73
+ """
74
+ project_path = context.project_path
75
+ if project_path is None:
76
+ return CommandResult.error("未打开项目,请先使用 'open <path>' 命令打开项目")
77
+
78
+ # 如果指定了具体文件,只显示该文件
79
+ if args:
80
+ config_file = args[0]
81
+ config_path = find_config_file(project_path, config_file)
82
+ if not config_path:
83
+ return CommandResult.error(f"配置文件不存在: {config_file}")
84
+ # 显示相对路径
85
+ rel_path = os.path.relpath(config_path, project_path)
86
+ return self._show_single_file(config_path, rel_path)
87
+
88
+ # 否则显示所有配置文件。
89
+ # §4.26: 无参 show 按 V2 目录结构遍历(schemas/constraints/regex/transforms/
90
+ # templates 分目录 + 根目录两文件)——原硬编码 V1 根目录文件清单,对 V2
91
+ # 项目几乎只显示 manifest 一个文件,其余配置全看不到(不做 V1 兼容层)
92
+ from pathlib import Path as _Path
93
+
94
+ output_lines = [Formatter.header("\n项目配置文件:")]
95
+
96
+ def _show_file(rel_path: str, abs_path: str) -> None:
97
+ output_lines.append(f"\n{Formatter.info('--- ' + rel_path + ' ---')}")
98
+ try:
99
+ output_lines.append(_Path(abs_path).read_text(encoding="utf-8"))
100
+ except Exception as e:
101
+ output_lines.append(f"(读取失败: {e})")
102
+
103
+ # 分目录收集(保持稳定的目录顺序)
104
+ for subdir, pattern in (
105
+ ("schemas", "*.schema.yaml"),
106
+ ("constraints", "*.constraint.yaml"),
107
+ ("regex", "*.regex.yaml"),
108
+ ("transforms", "*.transform.yaml"),
109
+ ("templates", "*.template.yaml"),
110
+ ):
111
+ dir_path = _Path(project_path) / subdir
112
+ if not dir_path.is_dir():
113
+ continue
114
+ for fp in sorted(dir_path.glob(pattern)):
115
+ _show_file(f"{subdir}/{fp.name}", str(fp))
116
+
117
+ # 根目录核心文件
118
+ for root_file in ("project.precis.yaml", "patterns.precis.yaml"):
119
+ root_path = os.path.join(project_path, root_file)
120
+ if os.path.isfile(root_path):
121
+ _show_file(root_file, root_path)
122
+
123
+ if len(output_lines) == 1:
124
+ return CommandResult.ok("项目中没有找到任何配置文件。")
125
+
126
+ return CommandResult.ok("\n".join(output_lines))
127
+
128
+ def _show_single_file(self, config_path: str, config_file: str) -> CommandResult:
129
+ """显示单个配置文件。
130
+
131
+ Args:
132
+ config_path: 文件的完整路径
133
+ config_file: 用于显示的相对路径
134
+
135
+ Returns:
136
+ 文件内容或错误提示
137
+ """
138
+ try:
139
+ with open(config_path, encoding="utf-8") as f:
140
+ content = yaml.safe_load(f)
141
+
142
+ output_lines = [
143
+ Formatter.header(f"\n{config_file}"),
144
+ yaml.dump(content, allow_unicode=True, default_flow_style=False),
145
+ ]
146
+ return CommandResult.ok("\n".join(output_lines))
147
+ except yaml.YAMLError as e:
148
+ return CommandResult.error(f"YAML 解析失败: {e}")
149
+ except Exception as e:
150
+ return CommandResult.error(f"读取失败: {e}")