@shundoo-ai/dsh-cosmic 1.0.19 → 1.0.21

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 (245) hide show
  1. package/cordis.patch.yml +5 -5
  2. package/package.json +1 -1
  3. package/skills/cosmic-build/CHANGELOG.md +4 -0
  4. package/skills/cosmic-build/LOCAL-PATCHES.md +91 -40
  5. package/skills/cosmic-build/SKILL.md +46 -13
  6. package/skills/cosmic-build/assets/platform-report-filters/TypedReportFilterFields.java +169 -0
  7. package/skills/cosmic-build/assets/platform-report-filters/platform.patch +98 -0
  8. package/skills/cosmic-build/assets/report-algo-benchmark/ReportAlgoBenchmark.java +48 -0
  9. package/skills/cosmic-build/assets/report-algo-benchmark/ReportBenchmarkLogFactory.java +7 -0
  10. package/skills/cosmic-build/examples/java-rules/CosmicEntryQuery.java +26 -0
  11. package/skills/cosmic-build/examples/java-rules/CosmicReportAggregation.java +35 -0
  12. package/skills/cosmic-build/examples/java-rules/CosmicReportFilters.java +77 -0
  13. package/skills/cosmic-build/examples/java-rules/CosmicTimeAdapter.java +25 -0
  14. package/skills/cosmic-build/examples/java-rules/CosmicTimeAdapterCheck.java +26 -0
  15. package/skills/cosmic-build/knowledge/cosmic/platform/api/algo/GroupbyDataSet.md +47 -0
  16. package/skills/cosmic-build/knowledge/cosmic/platform/api/entity/report/FilterInfo.md +12 -12
  17. package/skills/cosmic-build/knowledge/cosmic/platform/api/entity/report/FilterItemInfo.md +2 -0
  18. package/skills/cosmic-build/knowledge/cosmic/platform/form-plugin.md +2 -4
  19. package/skills/cosmic-build/knowledge/cosmic/platform/java-type-contracts.md +11 -2
  20. package/skills/cosmic-build/knowledge/cosmic/platform/list-plugin.md +18 -14
  21. package/skills/cosmic-build/knowledge/cosmic/platform/operation-plugin.md +4 -4
  22. package/skills/cosmic-build/knowledge/cosmic/platform/reportlistdata-plugin.md +17 -9
  23. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/operation/cross-entity-aggregate-writeback.md +2 -0
  24. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/operation/pre-post-operation.md +15 -7
  25. package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-algo.md +24 -68
  26. package/skills/cosmic-build/knowledge/cosmic/platform/workflow-plugin.md +2 -7
  27. package/skills/cosmic-build/references/application-context.md +35 -1
  28. package/skills/cosmic-build/references/common-reference.md +1 -1
  29. package/skills/cosmic-build/references/compile-guide.md +55 -68
  30. package/skills/cosmic-build/references/delivery.md +59 -7
  31. package/skills/cosmic-build/references/execution-contract.md +38 -0
  32. package/skills/cosmic-build/references/form-discovery.md +7 -1
  33. package/skills/cosmic-build/references/java-plugin-delivery.md +32 -162
  34. package/skills/cosmic-build/references/operation-dispatch.md +17 -0
  35. package/skills/cosmic-build/references/plugin-handoff-input.md +131 -0
  36. package/skills/cosmic-build/references/plugin-runtime-routing.md +1 -1
  37. package/skills/cosmic-build/references/report-filter-contract.md +78 -0
  38. package/skills/cosmic-build/references/sdk-inspection.md +23 -0
  39. package/skills/cosmic-build/schemas/data-model-authoring.schema.json +2 -3
  40. package/skills/cosmic-build/scripts/_http_client.py +55 -55
  41. package/skills/cosmic-build/scripts/appconfig_load.py +2 -2
  42. package/skills/cosmic-build/scripts/apply_report_filter_extension.py +39 -0
  43. package/skills/cosmic-build/scripts/bootstrap_cosmic_context.py +37 -2
  44. package/skills/cosmic-build/scripts/compile_plugin_source.py +279 -156
  45. package/skills/cosmic-build/scripts/execution_binding.py +40 -0
  46. package/skills/cosmic-build/scripts/inspect_platform_sdk.py +169 -0
  47. package/skills/cosmic-build/scripts/kd_api.py +23 -22
  48. package/skills/cosmic-build/scripts/kd_auth.py +1 -1
  49. package/skills/cosmic-build/scripts/kd_env_loader.py +19 -10
  50. package/skills/cosmic-build/scripts/kd_token_cache.py +2 -2
  51. package/skills/cosmic-build/scripts/kingscript_sdk_sync.py +350 -0
  52. package/skills/cosmic-build/scripts/kingscript_source_gate.py +11 -8
  53. package/skills/cosmic-build/scripts/platform_api_contract.py +246 -11
  54. package/skills/cosmic-build/scripts/plugin_corpus/build_index.py +403 -0
  55. package/skills/cosmic-build/scripts/plugin_corpus/jvm_classfile.py +281 -0
  56. package/skills/cosmic-build/scripts/plugin_corpus/measure_api_usage.py +446 -0
  57. package/skills/cosmic-build/scripts/plugin_source_gate.py +48 -7
  58. package/skills/cosmic-build/scripts/plugin_target_sdk.py +45 -0
  59. package/skills/cosmic-build/scripts/prepare_plugin_handoff.py +493 -47
  60. package/skills/cosmic-build/scripts/preview_url.py +35 -6
  61. package/skills/cosmic-build/scripts/query_plugin_handoff.py +250 -0
  62. package/skills/cosmic-build/scripts/register_plugins.py +28 -2
  63. package/skills/cosmic-build/scripts/render_output_cards.py +42 -9
  64. package/skills/cosmic-build/scripts/report_algo_benchmark.py +46 -0
  65. package/skills/cosmic-build/scripts/run.py +2 -2
  66. package/skills/cosmic-build/scripts/sync_generated_locale_assets.py +43 -0
  67. package/skills/cosmic-build/scripts/test/fixtures/ReportSdkCheck.java +64 -0
  68. package/skills/cosmic-build/scripts/test/fixtures/java_context_booking_native.json +541 -0
  69. package/skills/cosmic-build/scripts/test/test_add_rule_business_contract.py +85 -14
  70. package/skills/cosmic-build/scripts/test/test_batch5_drift_governance.py +12 -3
  71. package/skills/cosmic-build/scripts/test/test_build_meta_order.py +0 -16
  72. package/skills/cosmic-build/scripts/test/test_cli_continuation.py +87 -0
  73. package/skills/cosmic-build/scripts/test/test_compile_plugin_source.py +224 -3
  74. package/skills/cosmic-build/scripts/test/test_control_authoring.py +123 -4
  75. package/skills/cosmic-build/scripts/test/test_cosmic_sdk_examples.py +141 -0
  76. package/skills/cosmic-build/scripts/test/test_data_model_contract.py +30 -5
  77. package/skills/cosmic-build/scripts/test/test_delivery_stage_boundary.py +2 -10
  78. package/skills/cosmic-build/scripts/test/test_dym_sync_background.py +11 -0
  79. package/skills/cosmic-build/scripts/test/test_dynamic_form_metadata_only_contract.py +59 -1
  80. package/skills/cosmic-build/scripts/test/test_guide_skeleton_contract.py +87 -43
  81. package/skills/cosmic-build/scripts/test/test_inspect_platform_sdk.py +71 -0
  82. package/skills/cosmic-build/scripts/test/test_kd_api.py +67 -19
  83. package/skills/cosmic-build/scripts/test/test_kingscript_sdk_sync.py +221 -0
  84. package/skills/cosmic-build/scripts/test/test_kingscript_source_gate.py +10 -0
  85. package/skills/cosmic-build/scripts/test/test_list_configuration_closure.py +18 -0
  86. package/skills/cosmic-build/scripts/test/test_long_session_regressions.py +116 -0
  87. package/skills/cosmic-build/scripts/test/test_mcp_cli.py +524 -0
  88. package/skills/cosmic-build/scripts/test/test_modeling_input_recovery.py +141 -0
  89. package/skills/cosmic-build/scripts/test/test_open_world_cli_catalog.py +36 -2
  90. package/skills/cosmic-build/scripts/test/test_platform_api_contract.py +161 -1
  91. package/skills/cosmic-build/scripts/test/test_plugin_business_contract.py +91 -0
  92. package/skills/cosmic-build/scripts/test/test_plugin_corpus.py +195 -0
  93. package/skills/cosmic-build/scripts/test/test_plugin_handoff.py +346 -1
  94. package/skills/cosmic-build/scripts/test/test_plugin_target_sdk.py +70 -0
  95. package/skills/cosmic-build/scripts/test/test_preview_url.py +44 -1
  96. package/skills/cosmic-build/scripts/test/test_property_batch.py +22 -0
  97. package/skills/cosmic-build/scripts/test/test_property_input_discovery.py +13 -11
  98. package/skills/cosmic-build/scripts/test/test_query_plugin_handoff.py +160 -0
  99. package/skills/cosmic-build/scripts/test/test_register_plugins.py +31 -1
  100. package/skills/cosmic-build/scripts/test/test_render_output_cards.py +50 -4
  101. package/skills/cosmic-build/scripts/test/test_report_business_contract.py +27 -0
  102. package/skills/cosmic-build/scripts/test/test_report_doc_contract_sync.py +5 -4
  103. package/skills/cosmic-build/scripts/test/test_report_sdk_optimization.py +36 -0
  104. package/skills/cosmic-build/scripts/test/test_rule_batch_evidence.py +1 -1
  105. package/skills/cosmic-build/scripts/test/test_rule_domain_contract.py +20 -0
  106. package/skills/cosmic-build/scripts/test/test_rule_function_formula.py +16 -6
  107. package/skills/cosmic-build/scripts/test/test_skill_entry_contract.py +1 -0
  108. package/skills/cosmic-build/scripts/test/test_test_environment.py +45 -0
  109. package/skills/cosmic-build/scripts/test/test_verify_plugin_loading.py +79 -0
  110. package/skills/cosmic-build/scripts/test_environment.py +49 -0
  111. package/skills/cosmic-build/scripts/verify_plugin_loading.py +71 -0
  112. package/skills/cosmic-build/skills/cosmic-coding-impl/SKILL.md +86 -162
  113. package/skills/cosmic-build/skills/cosmic-coding-script/ATTRIBUTION.md +1 -1
  114. package/skills/cosmic-build/skills/cosmic-coding-script/SKILL.md +45 -14
  115. package/skills/cosmic-build/skills/cosmic-coding-script/references/kwc-controller-authoring.md +6 -0
  116. package/skills/cosmic-build/skills/cosmic-coding-script/references/sdk/README.md +29 -19
  117. package/skills/cosmic-build/skills/cosmic-coding-script/references/sdk/docs//350/204/232/346/234/254SDK.md +3 -4
  118. package/skills/cosmic-build/skills/cosmic-coding-script/references/sdk/indexes/package-index.md +1 -1
  119. package/skills/cosmic-build/skills/cosmic-coding-script/references/sdk/strategy.md +27 -18
  120. package/skills/cosmic-build/skills/cosmic-meta-api/SKILL.md +11 -17
  121. package/skills/cosmic-build/skills/cosmic-meta-api/apis/ai_meta.md +1 -1
  122. package/skills/cosmic-build/skills/cosmic-meta-api/apis/app_menu.md +1 -1
  123. package/skills/cosmic-build/skills/cosmic-meta-api/apis/meta_log.md +1 -1
  124. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/control-authoring.json +45 -4
  125. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/form-control-traits.json +1 -1
  126. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/layout-ui.json +18 -0
  127. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/menu-publish.json +8 -2
  128. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/property-batch.json +7 -0
  129. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/property-input-index.json +3 -3
  130. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/report-write-contracts.json +1 -0
  131. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/rule-domain.json +37 -5
  132. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/rule-event-contract.json +24 -3
  133. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/run-build-meta.json +61 -12
  134. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/dropdown-menu.schema.json +69 -0
  135. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/report-write.schema.json +2 -1
  136. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/rule-set.schema.json +1 -0
  137. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/rule-write.schema.json +14 -7
  138. package/skills/cosmic-build/skills/cosmic-meta-api/docs/api-summary.md +2 -2
  139. package/skills/cosmic-build/skills/cosmic-meta-api/modules/app-action/SKILL.md +6 -3
  140. package/skills/cosmic-build/skills/cosmic-meta-api/modules/develop/SKILL.md +1 -2
  141. package/skills/cosmic-build/skills/cosmic-meta-api/modules/export/SKILL.md +5 -6
  142. package/skills/cosmic-build/skills/cosmic-meta-api/modules/init/SKILL.md +13 -36
  143. package/skills/cosmic-build/skills/cosmic-meta-api/modules/pdm/SKILL.md +8 -9
  144. package/skills/cosmic-build/skills/cosmic-meta-api/modules/pdm/scripts/config.json.example +5 -15
  145. package/skills/cosmic-build/skills/cosmic-meta-api/modules/reset/SKILL.md +6 -6
  146. package/skills/cosmic-build/skills/cosmic-meta-api/modules/usage/SKILL.md +20 -148
  147. package/skills/cosmic-build/skills/cosmic-meta-api/modules/verify/SKILL.md +1 -1
  148. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/build_receipts.py +171 -0
  149. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_apps.py +25 -6
  150. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_controls.py +55 -21
  151. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_fields.py +10 -3
  152. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_formactions.py +63 -6
  153. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_forms.py +59 -2
  154. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_listui.py +8 -0
  155. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_metaops.py +36 -1
  156. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_plugins.py +115 -90
  157. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_properties.py +666 -4
  158. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_rules.py +1415 -87
  159. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/commands.py +6 -1
  160. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/config.py +28 -15
  161. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/execution_errors.py +40 -0
  162. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/formatters.py +32 -5
  163. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/property_policy_contract.py +6 -0
  164. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/report_ops.py +113 -17
  165. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/reset.py +3 -3
  166. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/rule_contract.py +107 -26
  167. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/build_meta_order.py +50 -49
  168. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/cosmic_api.py +856 -150
  169. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/field_decorator.py +47 -14
  170. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_build_receipt_resume.py +374 -0
  171. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_cli_failure_contract.py +24 -0
  172. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_creation_identity_boundary.py +4 -2
  173. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_dropdown_menu_contract.py +382 -0
  174. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_existing_form_ui.py +0 -25
  175. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_field_decorator.py +50 -6
  176. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_functional_group_resolution.py +83 -16
  177. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_identity_template_resolution.py +103 -0
  178. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_plugin_contract.py +28 -2
  179. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_readback_derivation.py +22 -0
  180. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_typed_filters.py +65 -0
  181. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_rule_write_closure.py +1041 -15
  182. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_batch_post_build_recovery.py +65 -0
  183. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_batch_verification_evidence.py +17 -0
  184. package/skills/cosmic-build/skills/cosmic-meta-knowledge/generation/api-param-checklist.md +1 -1
  185. package/skills/cosmic-build/skills/cosmic-meta-knowledge/generation/rule-modeling-guide.md +7 -4
  186. package/skills/cosmic-build/skills/cosmic-meta-modeling/SKILL.md +61 -13
  187. package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/app-menu/SKILL.md +8 -0
  188. package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/formtemplate/SKILL.md +5 -0
  189. package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/formtemplate/dynamic-form-artifact-schema.md +5 -0
  190. package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/SKILL.md +21 -9
  191. package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-entity-graphs.md +1 -1
  192. package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-storage-constraints.md +5 -3
  193. package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-ui-composition.md +6 -3
  194. package/skills/cosmic-build/skills/cosmic-meta-modeling/references/creation-recovery.md +13 -0
  195. package/skills/cosmic-build/skills/cosmic-meta-modeling/references/parse-core.md +3 -0
  196. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/data_model_contract.py +6 -1
  197. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/data_model_emit.py +135 -87
  198. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/field_type_registry.py +11 -1
  199. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/isv_resolver.py +1 -3
  200. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/report_validator.py +29 -0
  201. package/skills/cosmic-build/skills/cosmic-meta-modifier/SKILL.md +4 -2
  202. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/context/SKILL.md +2 -2
  203. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/execute/SKILL.md +1 -1
  204. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/operation/SKILL.md +5 -4
  205. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/operation/operation-write.md +6 -2
  206. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/operation/validation.md +2 -0
  207. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/property/SKILL.md +20 -5
  208. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/property/complex-apis.md +1 -1
  209. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/property/policy-grammar.md +4 -1
  210. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/report-op/SKILL.md +9 -0
  211. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/SKILL.md +61 -4
  212. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/raw-rule-authoring.md +3 -1
  213. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/rule-actions.md +2 -2
  214. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/rule-reference.md +6 -6
  215. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/state.md +1 -1
  216. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/unit/SKILL.md +6 -3
  217. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/verify/usage-guide.md +4 -4
  218. package/skills/cosmic-build/skills/cosmic-meta-modifier/references/list-filter-modification.md +4 -1
  219. package/skills/cosmic-build/skills/cosmic-meta-ui/SKILL.md +9 -1
  220. package/skills/cosmic-build/skills/cosmic-meta-ui/modules/button/SKILL.md +3 -0
  221. package/skills/cosmic-build/skills/mcp-server/SKILL.md +210 -0
  222. package/skills/cosmic-build/skills/mcp-server/scripts/mcp_cli.py +431 -0
  223. package/skills/cosmic-build/skills/openapi-gen/SKILL.md +3 -3
  224. package/skills/cosmic-build/skills/openapi-gen/scripts/api/gen_runner.py +2 -2
  225. package/skills/cosmic-dev-best-practices/references/bill-dev-convention.md +19 -9
  226. package/skills/cosmic-dev-best-practices/references/cosmic-meta-api.md +14 -11
  227. package/skills/cosmic-dev-best-practices/references/design-examples/basedata-design-example.md +1 -1
  228. package/skills/cosmic-dev-best-practices/references/design-examples/bill-design-example.md +1 -1
  229. package/skills/cosmic-dev-best-practices/references/elements/AssistantField.md +1 -1
  230. package/skills/cosmic-dev-best-practices/references/elements/BasedataField.md +2 -2
  231. package/skills/cosmic-dev-best-practices/references/elements/BasedataPropField.md +3 -3
  232. package/skills/cosmic-dev-best-practices/references/elements/CreaterField.md +2 -2
  233. package/skills/cosmic-dev-best-practices/references/elements/CurrencyField.md +2 -2
  234. package/skills/cosmic-dev-best-practices/references/elements/CustomerField.md +2 -2
  235. package/skills/cosmic-dev-best-practices/references/elements/FlexField.md +1 -1
  236. package/skills/cosmic-dev-best-practices/references/elements/MaterielField.md +2 -2
  237. package/skills/cosmic-dev-best-practices/references/elements/OrgField.md +2 -2
  238. package/skills/cosmic-dev-best-practices/references/elements/SupplierField.md +2 -2
  239. package/skills/cosmic-dev-best-practices/references/elements/UnitField.md +1 -1
  240. package/skills/cosmic-dev-best-practices/references/elements/UserField.md +3 -3
  241. package/skills/cosmic-dev-best-practices/references/elements/common-field-property-conventions.md +16 -11
  242. package/skills/cosmic-dev-best-practices/references/layout-control-convention.md +5 -4
  243. package/skills/erp-analysis/references/delivery-contract.md +4 -2
  244. package/skills/erp-analysis/templates/AGENTS.template.md +1 -1
  245. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/.dsh/cosmic/.kd/config.json +0 -21
@@ -0,0 +1,195 @@
1
+ """Behavioral tests for the L0 plugin corpus tools (parser, index build, API usage measurement).
2
+
3
+ The fixtures are real class files compiled by javac into small bos/biz/trd JARs. Platform class
4
+ names deliberately match the type registry and API pages, so the measurement runs against the
5
+ same registry and pages it uses on the real corpus.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import shutil
11
+ import sqlite3
12
+ import subprocess
13
+ import sys
14
+ import zipfile
15
+ from pathlib import Path
16
+
17
+ import pytest
18
+
19
+ SCRIPTS = Path(__file__).resolve().parents[1]
20
+ sys.path.insert(0, str(SCRIPTS))
21
+ sys.path.insert(0, str(SCRIPTS / "plugin_corpus"))
22
+
23
+ import build_index # noqa: E402
24
+ import jvm_classfile # noqa: E402
25
+ import measure_api_usage # noqa: E402
26
+
27
+ PLATFORM_SOURCES = {
28
+ "kd/sdk/annotation/SdkPlugin.java": """package kd.sdk.annotation;
29
+ import java.lang.annotation.*;
30
+ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE)
31
+ public @interface SdkPlugin { String name() default ""; }
32
+ """,
33
+ "kd/sdk/annotation/SdkInternal.java": """package kd.sdk.annotation;
34
+ import java.lang.annotation.*;
35
+ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD})
36
+ public @interface SdkInternal { String[] reasons() default {}; }
37
+ """,
38
+ "kd/bos/entity/plugin/AbstractOperationServicePlugIn.java": """package kd.bos.entity.plugin;
39
+ @kd.sdk.annotation.SdkPlugin(name = "operation")
40
+ public abstract class AbstractOperationServicePlugIn {
41
+ public Object[] getDataEntities() { return new Object[0]; }
42
+ public void onPreparePropertys(Object e) { }
43
+ @kd.sdk.annotation.SdkInternal(reasons = {"platform only"})
44
+ public void hidden() { }
45
+ }
46
+ """,
47
+ "kd/bos/servicehelper/Helper.java": """package kd.bos.servicehelper;
48
+ public class Helper { public static void run() { } }
49
+ """,
50
+ }
51
+ BUSINESS_SOURCES = {
52
+ "kd/fi/demo/DemoOp.java": """package kd.fi.demo;
53
+ import kd.bos.entity.plugin.AbstractOperationServicePlugIn;
54
+ import kd.bos.servicehelper.Helper;
55
+ public class DemoOp extends AbstractOperationServicePlugIn {
56
+ @Override
57
+ public void onPreparePropertys(Object e) {
58
+ super.onPreparePropertys(e);
59
+ Object[] rows = getDataEntities();
60
+ hidden();
61
+ Helper.run();
62
+ Runnable later = new Runnable() { public void run() { getDataEntities(); } };
63
+ later.run();
64
+ try { Class.forName("kd.fi.demo.Registered"); } catch (ClassNotFoundException ignored) { }
65
+ }
66
+ }
67
+ """,
68
+ "kd/fi/demo/Util.java": """package kd.fi.demo;
69
+ public class Util { public static void call() { kd.bos.servicehelper.Helper.run(); } }
70
+ """,
71
+ }
72
+ THIRD_PARTY_SOURCES = {"org/demo/Lib.java": "package org.demo;\npublic class Lib { public int size() { return 1; } }\n"}
73
+
74
+
75
+ def _jar(tmp: Path, name: str, sources: dict[str, str], classpath: list[Path] | None = None) -> Path:
76
+ source_root = tmp / f"src-{name}"
77
+ classes = tmp / f"classes-{name}"
78
+ files = []
79
+ for relative, text in sources.items():
80
+ path = source_root / relative
81
+ path.parent.mkdir(parents=True, exist_ok=True)
82
+ path.write_text(text, encoding="utf-8")
83
+ files.append(str(path))
84
+ classes.mkdir()
85
+ command = ["javac", "-d", str(classes)]
86
+ if classpath:
87
+ command += ["-cp", ":".join(str(item) for item in classpath)]
88
+ subprocess.run(command + files, check=True, capture_output=True)
89
+ jar = tmp / f"{name}.jar"
90
+ with zipfile.ZipFile(jar, "w") as archive:
91
+ archive.writestr("META-INF/MANIFEST.MF", "Manifest-Version: 1.0\nImplementation-Version: 9.9\n")
92
+ for path in sorted(classes.rglob("*.class")):
93
+ archive.write(path, path.relative_to(classes).as_posix())
94
+ return jar
95
+
96
+
97
+ @pytest.fixture
98
+ def library(tmp_path):
99
+ if not shutil.which("javac"):
100
+ pytest.skip("javac unavailable")
101
+ platform = _jar(tmp_path, "platform", PLATFORM_SOURCES)
102
+ business = _jar(tmp_path, "business", BUSINESS_SOURCES, [platform])
103
+ third = _jar(tmp_path, "lib", THIRD_PARTY_SOURCES)
104
+ root = tmp_path / "lib"
105
+ for layer, jar in (("bos", platform), ("biz", business), ("trd", third)):
106
+ (root / layer).mkdir(parents=True)
107
+ shutil.copy(jar, root / layer / jar.name)
108
+ return root
109
+
110
+
111
+ def _class_bytes(jar: Path, entry: str) -> bytes:
112
+ with zipfile.ZipFile(jar) as archive:
113
+ return archive.read(entry)
114
+
115
+
116
+ def test_class_file_reports_hierarchy_annotations_and_references(library):
117
+ parsed = jvm_classfile.parse_class(_class_bytes(library / "biz" / "business.jar", "kd/fi/demo/DemoOp.class"))
118
+ platform = jvm_classfile.parse_class(_class_bytes(
119
+ library / "bos" / "platform.jar", "kd/bos/entity/plugin/AbstractOperationServicePlugIn.class"))
120
+
121
+ assert parsed["name"] == "kd.fi.demo.DemoOp"
122
+ assert parsed["super"] == "kd.bos.entity.plugin.AbstractOperationServicePlugIn"
123
+ assert ("M", "kd.bos.entity.plugin.AbstractOperationServicePlugIn", "onPreparePropertys",
124
+ "(Ljava/lang/Object;)V") in parsed["memberRefs"]
125
+ assert ("M", "kd.bos.servicehelper.Helper", "run", "()V") in parsed["memberRefs"]
126
+ assert parsed["dynamicClassNames"] == ["kd.fi.demo.Registered"]
127
+ assert "kd.fi.demo.DemoOp$1" in parsed["classRefs"]
128
+ assert platform["annotations"] == ["kd.sdk.annotation.SdkPlugin"]
129
+ assert {name: annotations for name, _, _, annotations, _ in platform["methods"]}["hidden"] == (
130
+ "kd.sdk.annotation.SdkInternal",)
131
+
132
+
133
+ def test_descriptors_convert_to_the_parameter_types_pages_write():
134
+ params, returned = jvm_classfile.descriptor_types("(Ljava/lang/String;[ILkd/bos/algo/DataSet$Listener;)V")
135
+
136
+ assert params == ["java.lang.String", "int[]", "kd.bos.algo.DataSet$Listener"]
137
+ assert returned == "void"
138
+ assert [jvm_classfile.simple_type(item) for item in params] == ["String", "int[]", "Listener"]
139
+ with pytest.raises(jvm_classfile.ClassFormatError):
140
+ jvm_classfile.parse_class(b"not a class")
141
+
142
+
143
+ def test_index_rebuild_reuses_unchanged_jars_and_replaces_changed_ones(library, tmp_path):
144
+ workspace = tmp_path / "workspace"
145
+ first = build_index.build(str(library), [], str(workspace), ["biz"], workers=0)
146
+
147
+ assert first["jars"] == {"new": 3, "changed": 0, "unchanged": 0, "removed": 0}
148
+ assert {layer: data["classes"] for layer, data in first["layers"].items()} == {"biz": 3, "bos": 4, "trd": 1}
149
+ db = sqlite3.connect(str(workspace / build_index.DB_NAME))
150
+ refs_by_layer = dict(db.execute(
151
+ "SELECT j.layer, COUNT(r.class_id) FROM jars j JOIN classes c ON c.jar_id = j.id "
152
+ "LEFT JOIN refs r ON r.class_id = c.id GROUP BY j.layer"))
153
+ db.close()
154
+ assert refs_by_layer["biz"] > 0 and refs_by_layer["bos"] == 0 and refs_by_layer["trd"] == 0
155
+
156
+ assert build_index.build(str(library), [], str(workspace), ["biz"], workers=0)["jars"]["unchanged"] == 3
157
+
158
+ extra = dict(BUSINESS_SOURCES, **{"kd/fi/demo/More.java": "package kd.fi.demo;\npublic class More { }\n"})
159
+ rebuilt = _jar(tmp_path, "business2", extra, [library / "bos" / "platform.jar"])
160
+ shutil.copy(rebuilt, library / "biz" / "business.jar")
161
+ (library / "trd" / "lib.jar").unlink()
162
+ (library / "trd").rmdir()
163
+ second = build_index.build(str(library), [], str(workspace), ["biz"], workers=0)
164
+
165
+ assert second["jars"] == {"new": 0, "changed": 1, "unchanged": 1, "removed": 1}
166
+ assert second["layers"]["biz"]["classes"] == 4
167
+ assert "trd" not in second["layers"]
168
+
169
+
170
+ def test_workspace_inside_the_skill_package_is_rejected(library):
171
+ with pytest.raises(build_index.CorpusIndexError) as error:
172
+ build_index.build(str(library), [], str(SCRIPTS / "plugin_corpus" / "workspace"), ["biz"], workers=0)
173
+
174
+ assert error.value.code == "CORPUS_WORKSPACE_INSIDE_REPOSITORY"
175
+ assert not (SCRIPTS / "plugin_corpus" / "workspace").exists()
176
+
177
+
178
+ def test_measurement_labels_platform_references_and_checks_the_api_pages(library, tmp_path):
179
+ workspace = tmp_path / "workspace"
180
+ build_index.build(str(library), [], str(workspace), ["biz"], workers=0)
181
+ result = measure_api_usage.measure(str(workspace), top=10, platform_layers=["bos"], extra_roots=[])
182
+
183
+ assert result["population"]["pluginClasses"] == 1
184
+ assert result["population"]["pluginClassesByRegistryType"] == {"operation": 1}
185
+ assert result["population"]["classesIncludingNested"] == 2
186
+ members = {row["signature"]: row for key in ("topUndocumentedPublicMembers", "topNonPublicMembers",
187
+ "topPublicConstructors") for row in result[key]}
188
+ assert members["void kd.bos.entity.plugin.AbstractOperationServicePlugIn.hidden()"]["label"] == "internal"
189
+ assert members["void kd.bos.servicehelper.Helper.run()"]["label"] == "unmarked"
190
+ assert members["void kd.bos.entity.plugin.AbstractOperationServicePlugIn.onPreparePropertys(java.lang.Object)"][
191
+ "documentation"] == "member-missing"
192
+ # getDataEntities() is on the real AbstractOperationServicePlugIn page, from both DemoOp and DemoOp$1.
193
+ assert result["measurements"]["publicReferenceDocumentation"]["documented"] == 2
194
+ assert result["measurements"]["pluginsUsingOnlyPublicPlatformMembers"] == 0
195
+ assert result["measurements"]["pluginsWithPlatformReferences"] == 1
@@ -1,12 +1,13 @@
1
1
  import json
2
2
  import sys
3
+ import pytest
3
4
  from pathlib import Path
4
5
 
5
6
 
6
7
  SCRIPTS = Path(__file__).resolve().parents[1]
7
8
  sys.path.insert(0, str(SCRIPTS))
8
9
 
9
- from prepare_plugin_handoff import HandoffError, prepare_handoff # noqa: E402
10
+ from prepare_plugin_handoff import HandoffError, prepare_handoff, manifest_artifacts # noqa: E402
10
11
  from resolve_output_path import resolve_output_path # noqa: E402
11
12
 
12
13
 
@@ -31,6 +32,43 @@ def _artifact(form_name, form_key, entry_name, entry_key, fields):
31
32
  }
32
33
 
33
34
 
35
+ def test_legacy_artifact_receipt_preserves_verified_peer_fields(tmp_path):
36
+ app_root = tmp_path / "app" / "sample_demo"
37
+ _write(app_root / "archive.json", {"appCode": "sample_demo"})
38
+ _java_project(tmp_path)
39
+ artifact = tmp_path / "emit" / "source.json"
40
+ _write(artifact, {"entities": [
41
+ {"id": "root", "displayName": "来源单", "type": "BillEntity", "entityKey": "sample_source", "fields": []},
42
+ ]})
43
+ peer = tmp_path / "emit" / "peer.json"
44
+ _write(peer, {"entities": [
45
+ {"id": "root", "displayName": "关联资料", "type": "BaseEntity", "entityKey": "sample_peer",
46
+ "fields": [{"displayName": "余额", "fieldKey": "sample_balance", "dataType": "AmountField"}]},
47
+ ]})
48
+ manifest = tmp_path / "emit" / "_emit_manifest.json"
49
+ _write(manifest, {"artifacts": [{"path": "source.json"}, {"path": str(peer)}]})
50
+ assert manifest_artifacts(str(manifest)) == [str(artifact), str(peer)]
51
+ result = tmp_path / "write.json"
52
+ _write(result, {"appRoot": str(app_root), "artifactPaths": [str(artifact), str(peer)], "results": [
53
+ {"artifact": path.name, "ok": True, "verification": {"status": "pass"},
54
+ "result": {"data": {"entityResults": [{"formNumber": number, "formName": name}]}}}
55
+ for path, number, name in [(artifact, "sample_source", "来源单"), (peer, "sample_peer", "关联资料")]
56
+ ]})
57
+ operations = tmp_path / "operations.json"
58
+ _write(operations, {"status": True, "data": [{"operationKey": "process"}]})
59
+ intent = {"form": "来源单", "trigger": "处理前", "operationKeys": ["process"],
60
+ "coreLogic": ["检查关联资料余额"],
61
+ "specialized": {"unresolvedDependencies": [], "externalContracts": [{"entity": "关联资料", "fields": ["余额"]}]}}
62
+ handoff = prepare_handoff(app_root=str(app_root), project_root=str(tmp_path), plugin_type="operation",
63
+ artifact_paths=[str(artifact), str(peer)], run_result=json.loads(result.read_text()), intent=intent,
64
+ operation_result=json.loads(operations.read_text()))["handoff"]
65
+ assert handoff["specialized"]["externalContracts"][0]["verifiedFields"][0]["fieldKey"] == "sample_balance"
66
+ assert handoff["fqcn"].endswith("." + handoff["className"])
67
+ assert Path(handoff["sourcePath"]).name == handoff["className"] + ".java"
68
+ assert not Path(handoff["sourcePath"]).exists()
69
+ assert handoff["codingEvidence"]["baseContract"]["status"] is True
70
+
71
+
34
72
  def test_resolve_output_path_uses_same_app_package_not_project_common_prefix(tmp_path):
35
73
  app_root = tmp_path / "app" / "uhyl_demo"
36
74
  _write(app_root / "archive.json", {"appCode": "uhyl_demo"})
@@ -592,6 +630,11 @@ def test_report_handoff_carries_filter_requirements_the_platform_dropped(tmp_pat
592
630
  # comparison is, so query() has to apply between / in / = itself.
593
631
  assert [row["requirement"] for row in enforcement] == ["compare", "mustInput"]
594
632
  assert {row["filterKey"] for row in enforcement} == {"flt_period"}
633
+ values = payload["reportContract"]["filterValueContracts"]
634
+ assert values[0]["filterKey"] == "flt_period"
635
+ assert values[0]["valueChannel"] == "getValue"
636
+ assert values[0]["rangeItemKeys"] is None
637
+ assert values[0]["runtimeKeysVerified"] is False
595
638
 
596
639
 
597
640
  def test_report_handoff_compiles_the_contract_instead_of_trusting_a_persisted_one(tmp_path):
@@ -871,6 +914,15 @@ def test_modify_carries_the_methods_the_target_class_already_declares(tmp_path):
871
914
  assert payload["sourcePath"] == str(source.resolve())
872
915
 
873
916
 
917
+ def test_modify_fqcn_follows_existing_source_package(tmp_path):
918
+ source = _existing_plugin(tmp_path, "UhylTargetBillPlugin")
919
+ source.write_text(source.read_text().replace("package kd.bos.app.uhyl.demo.form;", "package custom.extension;"))
920
+ payload = _billform_handoff(tmp_path, _modify_intent(source))["handoff"]
921
+ assert payload["packagePath"] == "custom/extension"
922
+ assert payload["fqcn"] == "custom.extension.UhylTargetBillPlugin"
923
+ assert payload["sourcePath"] == str(source.resolve())
924
+
925
+
874
926
  def test_patch_existing_is_accepted_as_modify_instead_of_losing_the_intent(tmp_path):
875
927
  source = _existing_plugin(tmp_path, "UhylTargetBillPlugin")
876
928
 
@@ -1013,6 +1065,13 @@ def test_scalar_field_gets_its_java_type_and_no_accessor_advice(tmp_path):
1013
1065
  assert "valueAccessor" not in contract
1014
1066
 
1015
1067
 
1068
+ def test_time_only_field_does_not_retype_datetime_field():
1069
+ from prepare_plugin_handoff import _field_java_type
1070
+ assert _field_java_type("TimeProp") == "Integer"
1071
+ assert _field_java_type("TimeField") == "Integer"
1072
+ assert _field_java_type("DateTimeField") == "java.util.Date"
1073
+
1074
+
1016
1075
  def test_a_field_whose_type_the_artifact_omits_gets_no_contract(tmp_path):
1017
1076
  """A report column carries no dataType; an invented accessor would read as verified."""
1018
1077
  app_root = tmp_path / "app" / "kdec_yzxz"
@@ -1036,3 +1095,289 @@ def test_a_field_whose_type_the_artifact_omits_gets_no_contract(tmp_path):
1036
1095
 
1037
1096
  assert handoff["fields"] == {"kdec_remark": "kdec_remark"}
1038
1097
  assert "fieldContracts" not in handoff
1098
+
1099
+
1100
+ def test_prepare_reads_only_missing_evidence_and_keeps_operation_state_contract(tmp_path, monkeypatch):
1101
+ import prepare_plugin_handoff as module
1102
+ _java_project(tmp_path)
1103
+ app_root = tmp_path / "app" / "sample_demo"
1104
+ _write(app_root / "archive.json", {"appCode": "sample_demo"})
1105
+ source = tmp_path / "source.json"
1106
+ peer = tmp_path / "peer.json"
1107
+ _write(source, {"entities": [{"displayName": "来源单", "type": "BillEntity", "entityKey": "sample_source", "fields": [
1108
+ {"id": "state-id", "displayName": "阶段", "fieldKey": "sample_phase", "dataType": "ComboField",
1109
+ "comboOptions": [{"label": "待处理", "value": "pending"}]}]}]})
1110
+ _write(peer, {"entities": [{"displayName": "资料", "type": "BaseEntity", "entityKey": "sample_peer", "fields": [
1111
+ {"displayName": "余额", "fieldKey": "sample_balance", "dataType": "AmountField"}]}]})
1112
+ receipt = {"appRoot": str(app_root), "results": [
1113
+ {"artifact": str(p), "ok": True, "verification": {"status": "pass"},
1114
+ "result": {"data": {"entityResults": [{"formName": n, "formNumber": k}]}}}
1115
+ for p, n, k in [(source, "来源单", "sample_source"), (peer, "资料", "sample_peer")]]}
1116
+ operations = {"status": True, "data": {"operations": [{"operationKey": "process", "operationName": "处理",
1117
+ "parameter": {"StatusFieldId": "state-id", "Value": "B"}}]}}
1118
+ schema = {"status": True, "data": {"formNumber": "sample_external", "formName": "外部资料", "entities": [
1119
+ {"entityKey": "sample_external", "entityName": "外部资料", "fields": [
1120
+ {"fieldKey": "sample_quantity", "fieldName": "数量", "fieldType": "IntegerField"}]}]}}
1121
+ responses = tmp_path / "responses.json"
1122
+ _write(responses, {"listOperations": operations, "getFormSchema": schema})
1123
+ api = tmp_path / "fixture_api.py"
1124
+ calls = tmp_path / "calls.jsonl"
1125
+ # Run a real child CLI; only its remote service response is replaced by local fixtures.
1126
+ api.write_text("import sys,json\nfrom pathlib import Path\n"
1127
+ f"responses=json.loads(Path({str(responses)!r}).read_text())\n"
1128
+ "command=next(v for v in sys.argv if v in responses)\n"
1129
+ f"with Path({str(calls)!r}).open('a') as f: f.write(json.dumps(sys.argv[1:])+'\\n')\n"
1130
+ "Path(sys.argv[sys.argv.index('--output')+1]).write_text(json.dumps(responses[command]))\n")
1131
+ monkeypatch.setattr(module, "COSMIC_API", api)
1132
+ intent = {"form": "来源单", "trigger": "处理前", "operationKeys": ["process"], "fields": {"phase": "阶段"},
1133
+ "coreLogic": ["检查关联资料余额和外部数量,按业务规则处理"],
1134
+ "scenes": ["operation/cross-entity-aggregate-writeback"],
1135
+ "specialized": {"unresolvedDependencies": [], "externalContracts": [
1136
+ {"entity": "资料", "fields": ["余额"]}, {"entity": "sample_external", "fields": ["数量"]}]}}
1137
+ operation_result, schemas = module.prepare_inputs(app_root=str(app_root), intent=intent, plugin_type="operation",
1138
+ artifact_paths=[str(source), str(peer)], run_result=receipt, operation_result=None, external_schemas=[])
1139
+ result = prepare_handoff(app_root=str(app_root), project_root=str(tmp_path), plugin_type="operation", intent=intent,
1140
+ artifact_paths=[str(source), str(peer)], run_result=receipt, operation_result=operation_result, external_schemas=schemas)
1141
+ recorded = [json.loads(line) for line in calls.read_text().splitlines()]
1142
+ assert len(recorded) == 2
1143
+ assert sum("listOperations" in args for args in recorded) == 1
1144
+ assert sum("getFormSchema" in args for args in recorded) == 1
1145
+ assert all(str(app_root.resolve()) == args[args.index("--app-root") + 1] for args in recorded)
1146
+ assert result["handoff"]["operationContracts"][0]["parameter"]["Value"] == "B"
1147
+ assert result["handoff"]["stateWarnings"][0]["code"] == "OPERATION_STATE_VALUE_MISMATCH"
1148
+ assert result["handoff"]["fieldContracts"][0]["comboOptions"][0]["value"] == "pending"
1149
+ assert len(result["handoff"]["description"]) <= 20
1150
+ assert result["handoff"]["codingEvidence"]["apiContracts"]
1151
+
1152
+
1153
+ def test_unverified_entity_diagnostics_include_all_missing_targets(tmp_path):
1154
+ import pytest
1155
+ with pytest.raises(HandoffError) as error:
1156
+ _external_handoff(tmp_path, [{"entity": "missing_one", "fields": ["id"]},
1157
+ {"entity": "missing_two", "fields": ["id"]}])
1158
+ assert "missing_one" in str(error.value) and "missing_two" in str(error.value)
1159
+
1160
+
1161
+ def test_failed_operation_readback_is_never_positive_evidence():
1162
+ import pytest
1163
+ from prepare_plugin_handoff import _verified_operation_keys
1164
+ with pytest.raises(HandoffError, match="did not succeed"):
1165
+ _verified_operation_keys({"status": True, "data": {"success": False, "operations": [{"operationKey": "process"}]}})
1166
+
1167
+
1168
+ def test_failed_evidence_child_does_not_accept_its_partial_json(tmp_path, monkeypatch):
1169
+ import pytest
1170
+ import prepare_plugin_handoff as module
1171
+ source = tmp_path / "source.json"
1172
+ _write(source, {"entities": [{"displayName": "来源单", "type": "BillEntity", "entityKey": "sample_source", "fields": []}]})
1173
+ api = tmp_path / "failed_api.py"
1174
+ api.write_text("import sys\nfrom pathlib import Path\n"
1175
+ "Path(sys.argv[sys.argv.index('--output')+1]).write_text('{\"status\":true}')\n"
1176
+ "sys.exit(1)\n")
1177
+ monkeypatch.setattr(module, "COSMIC_API", api)
1178
+ with pytest.raises(HandoffError) as error:
1179
+ module.prepare_inputs(app_root=str(tmp_path), intent={"form": "来源单", "operationKeys": ["process"], "coreLogic": ["校验资料"],
1180
+ "specialized": {"unresolvedDependencies": []}},
1181
+ plugin_type="operation", artifact_paths=[str(source)], run_result=None, operation_result=None, external_schemas=[])
1182
+ assert error.value.error_code == "PLUGIN_EVIDENCE_READ_FAILED"
1183
+
1184
+
1185
+ def test_recorded_runtime_graph_resolves_header_entries_and_subentries_without_artifacts(tmp_path):
1186
+ from prepare_plugin_handoff import runtime_catalog
1187
+ native = json.loads((Path(__file__).parent / "fixtures/java_context_booking_native.json").read_text())
1188
+ _java_project(tmp_path)
1189
+ app = tmp_path / "app" / "sample_app"
1190
+ _write(app / "archive.json", {"appCode": "sample_app"})
1191
+ intent = {"form": "定场单", "trigger": "保存时", "operationKeys": ["save"], "coreLogic": ["校验场地时段冲突"],
1192
+ "fields": {"date": {"form": "定场单", "entity": "header", "field": "定场日期"},
1193
+ "price": {"form": "定场单", "entity": "entries", "field": "单价"},
1194
+ "servicePrice": {"form": "定场单", "entity": "subentries", "field": "单价"}},
1195
+ "specialized": {"unresolvedDependencies": []}}
1196
+ result = prepare_handoff(app_root=str(app), project_root=str(tmp_path), plugin_type="operation", intent=intent,
1197
+ artifact_paths=[str(tmp_path / "absent-authoring-artifact.json")], runtime_metadata=[native],
1198
+ operation_result={"status": True, "data": [{"operationKey": "save"}]})
1199
+ handoff = result["handoff"]
1200
+ assert handoff["fields"]["date"] == "sample_booking_date"
1201
+ assert handoff["fields"]["price"] == "sample_unit_price"
1202
+ assert handoff["fields"]["servicePrice"] == "sample_svc_price"
1203
+ graph = handoff["formGraph"]["entities"]
1204
+ assert [entity["role"] for entity in graph] == ["header", "entries", "subentries"]
1205
+ assert graph[2]["parentEntityKey"] == graph[1]["entityKey"]
1206
+ # The runtime property differs from the authoring sub-entry key in the observed session.
1207
+ assert graph[2]["collectionProperty"] == "sample_booking_order_e"
1208
+ assert graph[2]["collectionPath"] == ["sample_bookin_order_line", "sample_booking_order_e"]
1209
+ start_time = next(field for field in graph[1]["fields"] if field["fieldKey"] == "sample_start_time")
1210
+ assert start_time["fieldType"] == "TimeProp"
1211
+ assert start_time["javaType"] == "Integer"
1212
+ assert start_time["valueAccessor"] == 'getInt("sample_start_time")'
1213
+ assert start_time["timeValueUnit"] == "seconds-after-midnight"
1214
+ _, _, raw_fields, _ = runtime_catalog([native])
1215
+ assert sum(len(entity["fields"]) for entity in graph) == len(raw_fields) == 37
1216
+ assert all(field["entityKey"] == entity["entityKey"] for entity in graph for field in entity["fields"])
1217
+ assert next(field for field in graph[2]["fields"] if field["fieldKey"] == "sample_svc_price")["fieldPath"] == \
1218
+ "sample_bookin_order_line.sample_booking_order_e.sample_svc_price"
1219
+
1220
+
1221
+ def test_runtime_context_without_field_declarations_still_has_the_complete_graph(tmp_path):
1222
+ native = json.loads((Path(__file__).parent / "fixtures/java_context_booking_native.json").read_text())
1223
+ _java_project(tmp_path)
1224
+ app = tmp_path / "app" / "sample_app"
1225
+ _write(app / "archive.json", {"appCode": "sample_app"})
1226
+ result = prepare_handoff(app_root=str(app), project_root=str(tmp_path), plugin_type="operation",
1227
+ intent={"form": "定场单", "trigger": "保存时", "operationKeys": ["save"], "coreLogic": ["校验场地状态"],
1228
+ "specialized": {"unresolvedDependencies": []}}, artifact_paths=[], runtime_metadata=[native],
1229
+ operation_result={"status": True, "data": [{"operationKey": "save"}]})
1230
+ assert len(result["handoff"]["formGraph"]["entities"]) == 3
1231
+ fields = [field for entity in result["handoff"]["formGraph"]["entities"] for field in entity["fields"]]
1232
+ assert any(field["fieldKey"] == "sample_court" for field in fields)
1233
+ assert any(field["fieldKey"] == "sample_member" for field in fields)
1234
+ assert any(field["fieldKey"] == "sample_status" for field in fields)
1235
+ assert result["handoff"]["fields"] == {} # No keyword-based selection is needed to preserve fields.
1236
+
1237
+
1238
+ def test_runtime_collection_property_is_not_assumed_to_be_the_item_entity_name():
1239
+ from prepare_plugin_handoff import runtime_catalog
1240
+ _, entities, fields, _ = runtime_catalog([{"Name": "sample_main", "Properties": [
1241
+ {"Name": "lines", "_Type_": "EntryProp", "ItemType": {"Name": "sample_line_type", "Properties": [
1242
+ {"Name": "qty", "_Type_": "DecimalProp"}]}}]}])
1243
+ assert entities[1]["entityKey"] == "sample_line_type"
1244
+ assert entities[1]["collectionProperty"] == "lines"
1245
+ assert fields[0]["fieldPath"] == "lines.qty"
1246
+
1247
+
1248
+ def test_runtime_collection_missing_item_metadata_cannot_be_ready():
1249
+ import pytest
1250
+ from prepare_plugin_handoff import runtime_catalog
1251
+ with pytest.raises(HandoffError) as error:
1252
+ runtime_catalog([{"Name": "sample_main", "Properties": [{"Name": "lines", "_Type_": "EntryProp"}]}])
1253
+ assert error.value.error_code == "PLUGIN_CONTEXT_INCOMPLETE"
1254
+
1255
+
1256
+ def test_duplicate_runtime_field_names_return_scoped_candidates(tmp_path):
1257
+ import pytest
1258
+ from prepare_plugin_handoff import runtime_catalog, _resolve_field
1259
+ native = json.loads((Path(__file__).parent / "fixtures/java_context_booking_native.json").read_text())
1260
+ _, _, fields, _ = runtime_catalog([native])
1261
+ with pytest.raises(HandoffError) as error:
1262
+ _resolve_field(fields, {"form": "定场单", "field": "单价"})
1263
+ assert "sample_unit_price" in str(error.value)
1264
+ assert "sample_svc_price" in str(error.value)
1265
+
1266
+
1267
+ def test_external_contract_requires_exact_live_field_identity():
1268
+ from prepare_plugin_handoff import _external_field
1269
+ scope = [{"entityKey": "sample_court", "entityName": "场地档案", "fields": {
1270
+ "sample_court_status": {"fieldName": "场地状态"},
1271
+ }}]
1272
+ assert _external_field(scope, "场地状态")[1] == "sample_court_status"
1273
+ assert _external_field(scope, "sample_court_status")[1] == "sample_court_status"
1274
+ with pytest.raises(HandoffError, match="sample_court_status"):
1275
+ _external_field(scope, "court_status")
1276
+
1277
+
1278
+ @pytest.mark.parametrize("with_receipt", [False, True])
1279
+ @pytest.mark.parametrize("full_output", [False, True])
1280
+ def test_independent_cli_reads_native_metadata_without_modeling_receipts(tmp_path, monkeypatch, capsys, with_receipt, full_output):
1281
+ import prepare_plugin_handoff as module
1282
+ native = json.loads((Path(__file__).parent / "fixtures/java_context_booking_native.json").read_text())
1283
+ _java_project(tmp_path)
1284
+ config = tmp_path / "appconfig.json"
1285
+ _write(config, {"compile": False})
1286
+ monkeypatch.setenv("KD_APPCONFIG", str(config))
1287
+ app = tmp_path / "app" / "sample_app"
1288
+ _write(app / "archive.json", {"appCode": "sample_app"})
1289
+ responses = tmp_path / "responses.json"
1290
+ _write(responses, {"getEntityType": {"sample_booking_order": native,
1291
+ "sample_court": {"status": True, "data": {"Name": "sample_court", "DisplayName": {"zh_CN": "场地档案"},
1292
+ "Properties": [{"Name": "sample_court_status", "DisplayName": {"zh_CN": "场地状态"}, "_Type_": "ComboProp"}]}},
1293
+ "sample_member": {"status": True, "data": {"Name": "sample_member", "DisplayName": {"zh_CN": "会员档案"},
1294
+ "Properties": [{"Name": "sample_balance", "DisplayName": {"zh_CN": "余额"}, "_Type_": "AmountProp"}]}}},
1295
+ "listOperations": {"status": True, "data": {"formNumber": "sample_booking_order", "operations": [{"operationKey": "save"}]}}})
1296
+ calls = tmp_path / "calls.jsonl"
1297
+ api = tmp_path / "fixture_api.py"
1298
+ api.write_text("import sys,json\nfrom pathlib import Path\n"
1299
+ f"responses=json.loads(Path({str(responses)!r}).read_text())\n"
1300
+ "command=next(v for v in sys.argv if v in responses)\n"
1301
+ "value=responses[command]\n"
1302
+ "if command=='getEntityType': value=value[sys.argv[sys.argv.index('--entityId')+1]]\n"
1303
+ f"with Path({str(calls)!r}).open('a') as f: f.write(json.dumps(sys.argv[1:])+'\\n')\n"
1304
+ "Path(sys.argv[sys.argv.index('--output')+1]).write_text(json.dumps(value))\n")
1305
+ monkeypatch.setattr(module, "COSMIC_API", api)
1306
+ intent = {"form": "定场单", "trigger": "保存时", "operationKeys": ["save"], "coreLogic": ["校验场地状态"],
1307
+ "fields": {"date": {"form": "定场单", "entity": "header", "field": "定场日期"}},
1308
+ "specialized": {"unresolvedDependencies": [], "externalContracts": [{"entity": "场地档案", "fields": ["场地状态"]}]}}
1309
+ args = ["prepare_plugin_handoff.py", "--app-root", str(app), "--java-project-root", str(tmp_path),
1310
+ "--type", "operation", "--intent-json", json.dumps(intent), "--prepare-source"]
1311
+ if with_receipt:
1312
+ receipt = tmp_path / "old-receipt.json"
1313
+ _write(receipt, {"appRoot": str(app), "artifactPaths": [str(tmp_path / "absent-artifact.json")],
1314
+ "forms": [{"formNumber": "sample_booking_order", "formName": "定场单"}]})
1315
+ args += ["--run-result", str(receipt)]
1316
+ else:
1317
+ args += ["--form-number", "sample_booking_order"]
1318
+ if full_output:
1319
+ args += ["--full-output"]
1320
+ monkeypatch.setattr(sys, "argv", args)
1321
+ module.main()
1322
+ result = json.loads(capsys.readouterr().out)
1323
+ assert result["status"] is True
1324
+ assert Path(result["handoffPath"]).is_file()
1325
+ saved = json.loads(Path(result["handoffPath"]).read_text())
1326
+ assert len(saved["handoff"]["formGraph"]["entities"]) == 3
1327
+ assert saved["handoff"]["fields"]["date"] == "sample_booking_date"
1328
+ if full_output:
1329
+ assert result["handoff"] == saved["handoff"]
1330
+ else:
1331
+ assert result["contextMode"] == "reference-only"
1332
+ assert "formGraph" not in result["handoff"]
1333
+ assert result["metadata"]["forms"][0]["metadataSource"] == "getEntityType"
1334
+ recorded = [json.loads(line) for line in calls.read_text().splitlines()]
1335
+ assert sum("listOperations" in call for call in recorded) == 1
1336
+ native_reads = [call[call.index("--entityId") + 1] for call in recorded if "getEntityType" in call]
1337
+ assert sorted(native_reads) == ["sample_booking_order", "sample_court", "sample_member"]
1338
+ assert all(str(app.resolve()) == call[call.index("--app-root") + 1] for call in recorded)
1339
+ assert Path(result["handoff"]["sourcePath"]).parent.is_dir()
1340
+ assert not Path(result["handoff"]["sourcePath"]).exists()
1341
+
1342
+
1343
+ def test_large_native_coding_context_is_saved_completely_without_truncating_stdout(tmp_path):
1344
+ from prepare_plugin_handoff import handoff_response
1345
+ native = json.loads((Path(__file__).parent / "fixtures/java_context_booking_native.json").read_text())
1346
+ native["data"]["Properties"] += [
1347
+ {"Name": f"sample_extra_{index}", "DisplayName": {"zh_CN": f"补充字段{index}"}, "_Type_": "TextProp"}
1348
+ for index in range(600)
1349
+ ]
1350
+ _java_project(tmp_path)
1351
+ app = tmp_path / "app" / "sample_app"
1352
+ _write(app / "archive.json", {"appCode": "sample_app"})
1353
+ handoff = prepare_handoff(
1354
+ app_root=str(app), project_root=str(tmp_path), plugin_type="operation", artifact_paths=[],
1355
+ runtime_metadata=[native], operation_result={"status": True, "data": [{"operationKey": "save"}]},
1356
+ intent={"form": "sample_booking_order", "trigger": "保存时", "operationKeys": ["save"],
1357
+ "scenes": ["operation/custom-validator"], "coreLogic": ["校验补充字段"],
1358
+ "specialized": {"unresolvedDependencies": [], "externalContracts": []}},
1359
+ )["handoff"]
1360
+ target = tmp_path / "handoff.json"
1361
+ result = {"status": True, "handoffPath": str(target), "handoff": handoff}
1362
+ _write(target, result)
1363
+ stdout = json.dumps(handoff_response(result), ensure_ascii=False).encode("utf-8")
1364
+ assert len(target.read_bytes()) > 50 * 1024
1365
+ assert len(stdout) < 12 * 1024
1366
+ assert json.loads(target.read_text())["handoff"] == handoff
1367
+ assert handoff_response(result, full_output=True)["handoff"] == handoff
1368
+ assert json.loads(stdout)["metadata"]["forms"][0]["fieldCount"] >= 600
1369
+
1370
+
1371
+ def test_prepared_class_assignment_cannot_be_silently_overwritten(tmp_path):
1372
+ from prepare_plugin_handoff import assert_handoff_replace, HandoffError
1373
+ target = tmp_path / "handoff.json"
1374
+ result = {"status": True, "handoff": {"fqcn": "sample.Plugin", "operationKeys": ["save"], "coreLogic": ["validate"]}}
1375
+ _write(target, result)
1376
+ original = target.read_bytes()
1377
+ assert_handoff_replace(target, result)
1378
+ revised = {"status": True, "handoff": {**result["handoff"], "operationKeys": ["confirm"]}}
1379
+ with pytest.raises(HandoffError) as error:
1380
+ assert_handoff_replace(target, revised)
1381
+ assert error.value.error_code == "PLUGIN_HANDOFF_CONFLICT"
1382
+ assert target.read_bytes() == original
1383
+ assert_handoff_replace(target, revised, replace=True)