@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
@@ -1,15 +1,16 @@
1
1
  #!/usr/bin/env python3
2
- """Resolve the platform-authoritative preview URL for one form number."""
2
+ """Resolve platform form and verified standard list preview URLs."""
3
3
 
4
4
  from __future__ import annotations
5
5
 
6
- from urllib.parse import urljoin, urlparse
6
+ from urllib.parse import parse_qs, urlencode, urljoin, urlparse, urlunparse
7
7
 
8
8
  import kd_api
9
9
 
10
10
 
11
11
  PREVIEW_URL_PATH = "/kapi/v2/devportal/ai-meta/getPreviewUrl"
12
12
  FORM_LOOKUP_PATH = "/kapi/v2/devportal/ai-meta/queryForms"
13
+ FORM_SCHEMA_PATH = "/kapi/v2/devportal/ai-meta/getFormSchema"
13
14
 
14
15
 
15
16
  class PreviewUrlError(RuntimeError):
@@ -51,8 +52,11 @@ def _response_preview_url(response: object, base_url: str) -> str:
51
52
  return candidate
52
53
 
53
54
 
54
- def get_preview_url(config: dict, base_url: str, form_number: str, view_type: str = "") -> str:
55
- """调 getPreviewUrl;绝不回退到拼凑的 ``?formId=`` URL。
55
+ def get_preview_url(
56
+ config: dict, base_url: str, form_number: str, view_type: str = "",
57
+ form_preview_url: str = "",
58
+ ) -> str:
59
+ """获取平台预览地址;仅在列表元数据已验证时恢复标准列表入口。
56
60
 
57
61
  ``view_type`` 选择预览面:留空保持平台默认的表单视图;
58
62
  ``"list"`` 请求列表预览 URL。
@@ -70,7 +74,32 @@ def get_preview_url(config: dict, base_url: str, form_number: str, view_type: st
70
74
  config,
71
75
  params,
72
76
  )
73
- return _response_preview_url(response, base_url)
77
+ candidate = _response_preview_url(response, base_url)
78
+ if view != "list":
79
+ return candidate
80
+ editor = form_preview_url or get_preview_url(config, base_url, number)
81
+ if candidate != editor:
82
+ return candidate
83
+
84
+ schema = kd_api.get(base_url, FORM_SCHEMA_PATH, config, {"formId": number})
85
+ data = schema.get("data") if isinstance(schema, dict) and schema.get("status") is True else None
86
+ if (
87
+ not isinstance(data, dict)
88
+ or data.get("formNumber") not in (None, "", number)
89
+ or not isinstance(data.get("listLayout"), dict)
90
+ or not isinstance(data.get("listConfig"), dict)
91
+ or data["listConfig"].get("entityId") != number
92
+ or not isinstance(data.get("listColumns"), list)
93
+ or not data["listColumns"]
94
+ ):
95
+ raise PreviewUrlError("LIST_PREVIEW_METADATA_UNAVAILABLE: platform returned the editor URL for list")
96
+ parsed = urlparse(editor)
97
+ if parse_qs(parsed.query).get("formId") != [number]:
98
+ raise PreviewUrlError("LIST_PREVIEW_EDITOR_URL_UNEXPECTED")
99
+ # 标准 bos_list 路由来自平台先前返回的列表地址,且已在当前表单验证。
100
+ return urlunparse(parsed._replace(query=urlencode({
101
+ "formId": "bos_list", "type": "list", "billFormId": number,
102
+ }), fragment=""))
74
103
 
75
104
 
76
105
  def get_form_name(config: dict, base_url: str, form_number: str) -> str:
@@ -92,6 +121,6 @@ def get_form_name(config: dict, base_url: str, form_number: str) -> str:
92
121
 
93
122
 
94
123
  __all__ = [
95
- "PREVIEW_URL_PATH", "FORM_LOOKUP_PATH", "PreviewUrlError",
124
+ "PREVIEW_URL_PATH", "FORM_LOOKUP_PATH", "FORM_SCHEMA_PATH", "PreviewUrlError",
96
125
  "get_preview_url", "get_form_name",
97
126
  ]
@@ -0,0 +1,250 @@
1
+ #!/usr/bin/env python3
2
+ """Read only the requested portion of a verified plugin handoff snapshot."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import hashlib
8
+ import json
9
+ import sys
10
+ from pathlib import Path
11
+
12
+
13
+ MAX_OUTPUT_BYTES = 8 * 1024
14
+ MAX_CODING_CONTEXT_BYTES = 48 * 1024
15
+ MAX_CODING_START_BYTES = 16 * 1024
16
+ DEFAULT_PAGE_SIZE = 15
17
+ MAX_PAGE_SIZE = 50
18
+ IDENTITY_KEYS = (
19
+ "formNumber", "entityKey", "operationKey", "fieldKey", "class",
20
+ "reference", "entityName", "operationName", "fieldName",
21
+ )
22
+
23
+
24
+ class QueryError(ValueError):
25
+ def __init__(self, code: str, message: str):
26
+ super().__init__(message)
27
+ self.code = code
28
+
29
+
30
+ def encoded(value: object) -> str:
31
+ return json.dumps(value, ensure_ascii=False, separators=(",", ":"))
32
+
33
+
34
+ def pointer_child(parent: str, key: str | int) -> str:
35
+ return f"{parent}/{str(key).replace('~', '~0').replace('/', '~1')}"
36
+
37
+
38
+ def resolve_pointer(document: object, pointer: str) -> object:
39
+ if not pointer:
40
+ return document
41
+ if not pointer.startswith("/"):
42
+ raise QueryError("HANDOFF_POINTER_INVALID", "path must be an exact JSON pointer beginning with /")
43
+ value = document
44
+ for token in pointer[1:].split("/"):
45
+ key = token.replace("~1", "/").replace("~0", "~")
46
+ if isinstance(value, dict) and key in value:
47
+ value = value[key]
48
+ continue
49
+ if isinstance(value, list) and key.isdecimal() and str(int(key)) == key and int(key) < len(value):
50
+ value = value[int(key)]
51
+ continue
52
+ raise QueryError("HANDOFF_POINTER_NOT_FOUND", f"path does not exist: {pointer}")
53
+ return value
54
+
55
+
56
+ def describe(value: object, pointer: str) -> dict:
57
+ kind = "object" if isinstance(value, dict) else "array" if isinstance(value, list) else type(value).__name__
58
+ result = {"path": pointer, "kind": kind, "bytes": len(encoded(value).encode("utf-8"))}
59
+ if isinstance(value, (dict, list)):
60
+ result["count"] = len(value)
61
+ if isinstance(value, dict):
62
+ result["identity"] = {
63
+ key: value[key][:120] for key in IDENTITY_KEYS
64
+ if isinstance(value.get(key), str)
65
+ }
66
+ return result
67
+
68
+
69
+ def load_handoff(filename: str) -> tuple[Path, dict]:
70
+ file = Path(filename)
71
+ if not file.is_absolute() or file.suffix.lower() != ".json":
72
+ raise QueryError("HANDOFF_PATH_INVALID", "handoff must be an absolute JSON path returned by preparation")
73
+ try:
74
+ document = json.loads(file.read_text(encoding="utf-8"))
75
+ except (OSError, UnicodeError, json.JSONDecodeError) as error:
76
+ raise QueryError("HANDOFF_UNAVAILABLE", f"cannot read handoff: {type(error).__name__}") from error
77
+ if not isinstance(document, dict) or document.get("status") is not True or not isinstance(document.get("handoff"), dict):
78
+ raise QueryError("HANDOFF_INVALID", "file is not a successful plugin handoff")
79
+ if not isinstance(document.get("handoffPath"), str) or Path(document["handoffPath"]).resolve() != file.resolve():
80
+ raise QueryError("HANDOFF_PATH_MISMATCH", "handoffPath does not match the selected file")
81
+ return file, document
82
+
83
+
84
+ def query(document: dict, args: argparse.Namespace) -> dict:
85
+ if args.operation in ("coding-context", "coding-start"):
86
+ handoff = document["handoff"]
87
+ graph = handoff.get("formGraph")
88
+ evidence = handoff.get("codingEvidence")
89
+ if not isinstance(graph, dict) or not isinstance(evidence, dict):
90
+ raise QueryError("HANDOFF_CODING_CONTEXT_INVALID", "formGraph and codingEvidence are required")
91
+ if not isinstance(handoff.get("relatedForms", []), list) or not isinstance(evidence.get("apiContracts", []), list):
92
+ raise QueryError("HANDOFF_CODING_CONTEXT_INVALID", "relatedForms and apiContracts must be arrays")
93
+
94
+ def pick(record: dict, keys: tuple[str, ...]) -> dict:
95
+ return {key: record[key] for key in keys if key in record and record[key] is not None}
96
+
97
+ def target_sdk() -> dict:
98
+ if not isinstance(handoff.get("targetSdk"), dict):
99
+ return {"status": "not-provided", "evidenceScope": "This snapshot does not establish target SDK readiness"}
100
+ return {**pick(handoff["targetSdk"], ("status", "projectRoot", "module", "evidenceScope")),
101
+ "path": "/handoff/targetSdk"}
102
+
103
+ if args.operation == "coding-start":
104
+ from compile_plugin_source import inspect_compile_environment
105
+ if not isinstance(graph.get("entities"), list):
106
+ raise QueryError("HANDOFF_CODING_CONTEXT_INVALID", "formGraph entities are required")
107
+ if any(not isinstance(entity, dict) or not isinstance(entity.get("fields"), list)
108
+ or any(not isinstance(field, dict) for field in entity["fields"]) for entity in graph["entities"]):
109
+ raise QueryError("HANDOFF_CODING_CONTEXT_INVALID", "entities and fields must be objects")
110
+ start = {
111
+ "status": True, "contextMode": "coding-start", "handoffPath": document["handoffPath"],
112
+ "handoffSha256": hashlib.sha256(Path(document["handoffPath"]).read_bytes()).hexdigest(),
113
+ **pick(handoff, ("appRoot", "projectRoot", "formNumber", "className", "sourcePath", "fqcn", "operationKeys", "coreLogic", "sourceGate")),
114
+ "entities": [{**pick(entity, ("entityKey", "role", "collectionProperty", "collectionPath")),
115
+ "fieldsPath": f"/handoff/formGraph/entities/{index}/fields",
116
+ "fields": [pick(field, ("fieldKey", "fieldName", "fieldType", "javaType", "valueAccessor", "idAccessor"))
117
+ for field in entity.get("fields", [])]}
118
+ for index, entity in enumerate(graph["entities"])],
119
+ "relatedForms": [{**pick(form, ("formNumber", "formName", "metadataVersion")),
120
+ "path": f"/handoff/relatedForms/{index}"}
121
+ for index, form in enumerate(handoff.get("relatedForms", []))],
122
+ "operationContracts": handoff.get("operationContracts", []),
123
+ "availableOperationKeys": [row["operationKey"] for row in handoff.get("availableOperations", [])
124
+ if isinstance(row, dict) and isinstance(row.get("operationKey"), str)],
125
+ "availableOperationsPath": "/handoff/availableOperations",
126
+ "selectedOperations": [pick(row, ("operationKey", "operationName", "operationType"))
127
+ for row in handoff.get("availableOperations", [])
128
+ if isinstance(row, dict) and row.get("operationKey") in handoff.get("operationKeys", [])],
129
+ "stateWarnings": handoff.get("stateWarnings", []),
130
+ "stateFields": [pick(field, ("fieldKey", "fieldName", "entityKey", "comboOptions")) for field in handoff.get("stateFields", [])],
131
+ "buildEnvironment": inspect_compile_environment(handoff["projectRoot"]) if handoff.get("projectRoot") else {"status": "not-provided"},
132
+ "targetSdk": target_sdk(),
133
+ "codingEvidence": {**pick(evidence, ("codingEntry", "operationDispatch", "apiLookup", "sdkLookup")),
134
+ "signatureTypes": [pick(item, ("class", "import")) for item in evidence.get("signatureTypes", [])],
135
+ "signatureTypesPath": "/handoff/codingEvidence/signatureTypes",
136
+ **({"guide": evidence["guide"]} if isinstance(evidence.get("guide"), str) and len(evidence["guide"]) <= 512 else {}),
137
+ "apiContracts": [{**pick(contract, ("class",)),
138
+ "path": f"/handoff/codingEvidence/apiContracts/{index}",
139
+ "members": list(dict.fromkeys(member["name"] for member in contract.get("members", []) if "name" in member))}
140
+ for index, contract in enumerate(evidence.get("apiContracts", []))]},
141
+ "remainingContext": {"stateFields": "/handoff/stateFields", "specialized": "/handoff/specialized", "reportContract": "/handoff/reportContract"},
142
+ "evidenceScope": "Startup facts and pointers only; collection membership is not proof of a standalone query root, and source compilation does not establish business coverage",
143
+ }
144
+ if len(encoded(start).encode("utf-8")) > MAX_CODING_START_BYTES:
145
+ for entity in start["entities"]:
146
+ entity["fieldCount"] = len(entity.pop("fields"))
147
+ entity["fieldsOmitted"] = True
148
+ return start
149
+
150
+ def compact_graph(value: dict) -> dict:
151
+ if not isinstance(value, dict) or not isinstance(value.get("entities"), list):
152
+ raise QueryError("HANDOFF_CODING_CONTEXT_INVALID", "formGraph entities are required")
153
+ if any(not isinstance(entity, dict) or not isinstance(entity.get("fields"), list) or any(not isinstance(field, dict) for field in entity["fields"]) for entity in value["entities"]):
154
+ raise QueryError("HANDOFF_CODING_CONTEXT_INVALID", "entities and fields must be objects")
155
+ return {
156
+ **pick(value, ("formNumber", "formName", "metadataVersion")),
157
+ "entities": [
158
+ {
159
+ **pick(entity, ("entityKey", "entityName", "entityType", "role", "parentEntityKey", "collectionProperty", "collectionPath")),
160
+ "fields": [pick(field, ("fieldKey", "fieldName", "fieldType", "fieldPath", "javaType", "valueAccessor", "timeValueUnit", "refTo", "comboOptions", "reference", "idAccessor", "filterValue")) for field in entity.get("fields", [])],
161
+ }
162
+ for entity in value["entities"]
163
+ ],
164
+ }
165
+
166
+ return {
167
+ "status": True,
168
+ "contextMode": "coding-context",
169
+ "handoffPath": document["handoffPath"],
170
+ "handoffSha256": hashlib.sha256(Path(document["handoffPath"]).read_bytes()).hexdigest(),
171
+ **pick(handoff, ("appRoot", "projectRoot", "formNumber", "className", "sourcePath", "fqcn", "operationKeys", "coreLogic", "specialized", "sourceGate")),
172
+ "formGraph": compact_graph(graph),
173
+ "targetSdk": target_sdk(),
174
+ "queryRootEvidence": {
175
+ "formNumber": graph.get("formNumber"),
176
+ "nonRootEntityKeys": [entity["entityKey"] for entity in graph["entities"]
177
+ if entity.get("role") in ("entries", "subentries") and isinstance(entity.get("entityKey"), str)],
178
+ "boundary": "EntryType and SubEntryType are collection members here, not evidence of standalone QueryServiceHelper roots; runtime query behavior is unverified by this snapshot.",
179
+ },
180
+ "relatedForms": [compact_graph(form) for form in handoff.get("relatedForms", [])],
181
+ "operationContracts": handoff.get("operationContracts", []),
182
+ "availableOperations": handoff.get("availableOperations", []),
183
+ "stateFields": handoff.get("stateFields", []),
184
+ "stateWarnings": handoff.get("stateWarnings", []),
185
+ "codingEvidence": {
186
+ **pick(evidence, ("codingEntry", "signatureTypes", "operationDispatch", "apiLookup", "sdkLookup")),
187
+ **({"guide": evidence["guide"]} if isinstance(evidence.get("guide"), str) and len(evidence["guide"]) <= 512 else {}),
188
+ "apiContracts": [
189
+ {
190
+ **pick(contract, ("class", "import", "reference")),
191
+ "members": [pick(member, ("name", "signature", "declaredBy", "deprecated")) for member in contract.get("members", [])],
192
+ }
193
+ for contract in evidence.get("apiContracts", [])
194
+ ],
195
+ },
196
+ }
197
+ value = resolve_pointer(document, args.path)
198
+ if args.operation == "get":
199
+ return {"status": True, "path": args.path, "value": value}
200
+ if args.operation == "index":
201
+ if not isinstance(value, (dict, list)):
202
+ raise QueryError("HANDOFF_NOT_CONTAINER", "index requires an object or array; use get for a scalar")
203
+ children = list(value.items()) if isinstance(value, dict) else list(enumerate(value))
204
+ page = children[args.offset:args.offset + args.limit]
205
+ return {
206
+ "status": True, "path": args.path, "kind": "object" if isinstance(value, dict) else "array",
207
+ "total": len(children), "offset": args.offset,
208
+ "nextOffset": args.offset + len(page) if args.offset + len(page) < len(children) else None,
209
+ "items": [describe(child, pointer_child(args.path, key)) for key, child in page],
210
+ }
211
+ if not isinstance(value, list) or any(not isinstance(item, dict) for item in value):
212
+ raise QueryError("HANDOFF_NOT_RECORD_LIST", "find requires an array of objects")
213
+ matches = [describe(item, pointer_child(args.path, index)) for index, item in enumerate(value) if item.get(args.field) == args.equals]
214
+ return {"status": True, "path": args.path, "field": args.field, "equals": args.equals, "matches": matches}
215
+
216
+
217
+ def main() -> int:
218
+ parser = argparse.ArgumentParser(description=__doc__)
219
+ parser.add_argument("operation", choices=("index", "find", "get", "coding-context", "coding-start"))
220
+ parser.add_argument("--handoff", required=True, help="Exact handoffPath returned by prepare_plugin_handoff.py")
221
+ parser.add_argument("--path", default="", help="JSON pointer, such as /handoff/formGraph/entities")
222
+ parser.add_argument("--offset", type=int, default=0, help="First child for index")
223
+ parser.add_argument("--limit", type=int, default=DEFAULT_PAGE_SIZE, help="Children per index page")
224
+ parser.add_argument("--field", help="Direct object field for exact find")
225
+ parser.add_argument("--equals", help="Exact value for find")
226
+ args = parser.parse_args()
227
+ try:
228
+ if args.offset < 0 or not 1 <= args.limit <= MAX_PAGE_SIZE:
229
+ raise QueryError("HANDOFF_PAGE_INVALID", "offset must be nonnegative and limit must be 1–50")
230
+ if args.operation == "find" and (not args.field or args.equals is None or not args.path):
231
+ raise QueryError("HANDOFF_FIND_INVALID", "find requires --path, --field and --equals")
232
+ if args.operation != "find" and (args.field is not None or args.equals is not None):
233
+ raise QueryError("HANDOFF_FIND_INVALID", "--field and --equals apply only to find")
234
+ if args.operation in ("coding-context", "coding-start") and (args.path or args.offset or args.limit != DEFAULT_PAGE_SIZE):
235
+ raise QueryError("HANDOFF_CODING_CONTEXT_ARGUMENT_INVALID", "coding-context accepts only --handoff")
236
+ _, document = load_handoff(args.handoff)
237
+ response = query(document, args)
238
+ output = encoded(response)
239
+ limit = MAX_CODING_CONTEXT_BYTES if args.operation == "coding-context" else MAX_CODING_START_BYTES if args.operation == "coding-start" else MAX_OUTPUT_BYTES
240
+ if len(output.encode("utf-8")) > limit:
241
+ raise QueryError("HANDOFF_QUERY_TOO_LARGE", f"selected result exceeds {limit} bytes; index its children or select a narrower path")
242
+ print(output)
243
+ return 0
244
+ except QueryError as error:
245
+ print(encoded({"status": False, "errorCode": error.code, "message": str(error)}))
246
+ return 2
247
+
248
+
249
+ if __name__ == "__main__":
250
+ sys.exit(main())
@@ -19,6 +19,7 @@ import urllib.error
19
19
  from pathlib import Path
20
20
 
21
21
  import kd_api
22
+ from plugin_source_gate import validate_registration_entry
22
23
 
23
24
  REGISTER_PATH = "/kapi/v2/devportal/ai-meta/plugin/register"
24
25
  QUERY_PATH = "/kapi/v2/devportal/ai-meta/plugin/queryEditable"
@@ -200,7 +201,8 @@ def _load_classes_from_archive(app_root):
200
201
  error_code='JAVA_ARCHIVE_ENTRY_INVALID',
201
202
  details=invalid,
202
203
  )
203
- return entries
204
+ biz_app_id = str(archive.get('appId') or '').strip()
205
+ return [{**entry, 'bizAppId': biz_app_id} for entry in entries] if biz_app_id else entries
204
206
 
205
207
 
206
208
  def _validate_candidate_sources(candidates):
@@ -262,7 +264,7 @@ def _filter_candidates(candidates, requested_names):
262
264
  ]
263
265
 
264
266
 
265
- def _build_register_payload(form_number, class_name, description, target_type, operation_key=None):
267
+ def _build_register_payload(form_number, class_name, description, target_type, operation_key=None, biz_app_id=None):
266
268
  """构造注册请求 payload。"""
267
269
  payload = {
268
270
  'formNumber': form_number,
@@ -270,7 +272,10 @@ def _build_register_payload(form_number, class_name, description, target_type, o
270
272
  'description': description,
271
273
  'enabled': True,
272
274
  'targetType': target_type,
275
+ 'pluginType': 'JAVA',
273
276
  }
277
+ if biz_app_id:
278
+ payload['bizAppId'] = biz_app_id
274
279
  if operation_key:
275
280
  payload['operationKey'] = operation_key
276
281
  return payload
@@ -389,6 +394,16 @@ def main():
389
394
  root_dir = _find_root_dir()
390
395
  type_map = _load_type_registry(root_dir)
391
396
  type_owners = _load_type_owners(root_dir)
397
+ source_checks = []
398
+ try:
399
+ for candidate in candidates:
400
+ if candidate.get('filePath') and type_map.get(candidate.get('type')):
401
+ checked = validate_registration_entry(candidate, type_map[candidate['type']], _resolve_fqcn(candidate))
402
+ source_checks.append({'className': _resolve_fqcn(candidate), 'compileStatus': checked['compileStatus']})
403
+ except ValueError as exc:
404
+ code, _, message = str(exc).partition(':')
405
+ print(json.dumps(_failure(code, message.strip(), writeAttempted=False), ensure_ascii=False))
406
+ return 2
392
407
  server = _load_config(root_dir)
393
408
 
394
409
  if not server:
@@ -475,6 +490,7 @@ def main():
475
490
  description=c.get('description', ''),
476
491
  target_type=target_type,
477
492
  operation_key=operation_key,
493
+ biz_app_id=c.get('bizAppId'),
478
494
  )
479
495
  try:
480
496
  response = kd_api.post(
@@ -536,8 +552,18 @@ def main():
536
552
  'failed': sum(1 for r in results if not r['success']),
537
553
  },
538
554
  }
555
+ if source_checks:
556
+ output['sourceChecks'] = source_checks
539
557
  if verification is not None:
540
558
  output['verification'] = verification
559
+ if expected:
560
+ # Registration read-back does not establish deployment activation or restart needs.
561
+ output['activation'] = {
562
+ 'requiresServiceRestart': None,
563
+ 'runtimeVerified': False,
564
+ 'state': 'registration_incomplete' if has_failure else 'runtime_unverified' if verification and verification['status'] == 'pass' else 'registration_unverified',
565
+ 'registeredClasses': sorted({item['className'] for item in expected}),
566
+ }
541
567
  print(json.dumps(output, ensure_ascii=False, indent=2))
542
568
  return 2 if has_failure else 0
543
569
 
@@ -14,6 +14,7 @@ from __future__ import annotations
14
14
  import argparse
15
15
  import hashlib
16
16
  import json
17
+ import os
17
18
  import subprocess
18
19
  import sys
19
20
  from pathlib import Path
@@ -25,8 +26,10 @@ if str(SCRIPT_DIR) not in sys.path:
25
26
  sys.path.insert(0, str(SCRIPT_DIR))
26
27
 
27
28
  from preview_url import PreviewUrlError, get_form_name, get_preview_url
29
+ from test_environment import export_test_environment
28
30
 
29
31
  _DYM_BACKGROUND_NOTICE = None
32
+ _TEST_ENVIRONMENT_HANDOFF = None
30
33
 
31
34
 
32
35
  class ReportPluginSourceMissing(Exception):
@@ -343,6 +346,8 @@ def render(
343
346
  ) -> list[str]:
344
347
  global _DYM_BACKGROUND_NOTICE
345
348
  _DYM_BACKGROUND_NOTICE = None
349
+ global _TEST_ENVIRONMENT_HANDOFF
350
+ _TEST_ENVIRONMENT_HANDOFF = None
346
351
  archive_path = Path(app_root) / "archive.json"
347
352
  if not archive_path.exists():
348
353
  print(f"Warning: {archive_path} does not exist, skipping kdform cards", file=sys.stderr)
@@ -394,6 +399,7 @@ def render(
394
399
 
395
400
  blocks: list[str] = []
396
401
  deleted_numbers: list[str] = []
402
+ browser_entries: dict[str, dict[str, str]] = {}
397
403
  artifacts: list[dict[str, str]] = archive.get("artifacts", [])
398
404
  if not isinstance(artifacts, list):
399
405
  artifacts = []
@@ -411,6 +417,9 @@ def render(
411
417
  if not base_url:
412
418
  print("Warning: server.baseUrl is empty (check ~/.kd/config.json)", file=sys.stderr)
413
419
  else:
420
+ _TEST_ENVIRONMENT_HANDOFF = export_test_environment(
421
+ app_root, server_config, [number for number, _title in form_entries]
422
+ )
414
423
  archived_urls = _archived_preview_urls(artifacts)
415
424
  # 子集渲染不能清掉其他表单已经归档的卡片。
416
425
  requested_set = {fn for fn, _title in form_entries}
@@ -444,12 +453,19 @@ def render(
444
453
  url = archived_urls.get(fn, "")
445
454
  fallback = "reusing archived preview link" if url else "rendering card without preview link"
446
455
  print(f"Warning: getPreviewUrl({fn}) failed ({e}); {fallback}", file=sys.stderr)
447
- # 列表预览地址(viewType=list)是尽力而为的只读:失败仅省略卡片里的
448
- # listUrl 字段,绝不阻塞卡片本身。
456
+ # 平台忽略 viewType=list 时,由已验证列表元数据恢复标准 bos_list 入口。
449
457
  try:
450
- list_url = get_preview_url(server_config, base_url, fn, view_type="list")
458
+ list_url = get_preview_url(
459
+ server_config, base_url, fn, view_type="list", form_preview_url=url,
460
+ )
451
461
  except Exception:
452
462
  list_url = ""
463
+ if list_url and list_url == url:
464
+ print(
465
+ f"Warning: getPreviewUrl({fn}, viewType=list) returned the editor URL; omitted listUrl",
466
+ file=sys.stderr,
467
+ )
468
+ list_url = ""
453
469
  metadata = _join_url(base_url, f"/kapi/v2/devportal/ai-meta/getEntityFields?formNumber={fn}")
454
470
  plugins = _join_url(base_url, f"/kapi/v2/devportal/ai-meta/getFormPlugins?formNumber={fn}")
455
471
  designer = _join_url(base_url, f"/kapi/v2/devportal/ai-meta/getDesignerUrl?formNumber={fn}")
@@ -458,6 +474,7 @@ def render(
458
474
  card = {"title": title, "url": url, "metadata": metadata, "plugins": plugins, "designer": designer}
459
475
  if list_url:
460
476
  card["listUrl"] = list_url
477
+ browser_entries[fn] = {"formName": title, "previewUrl": url, "listUrl": list_url}
461
478
  if api:
462
479
  card["api"] = api
463
480
 
@@ -465,6 +482,12 @@ def render(
465
482
  card_json = json.dumps(card, ensure_ascii=False)
466
483
  blocks.append(f":::render:kdform\n{card_json}\n:::")
467
484
 
485
+ _TEST_ENVIRONMENT_HANDOFF = export_test_environment(
486
+ app_root, server_config,
487
+ [number for number, _title in form_entries if number not in deleted_numbers],
488
+ form_links=browser_entries,
489
+ )
490
+
468
491
  archive["artifacts"] = artifacts
469
492
  _write_json(archive_path, archive)
470
493
 
@@ -487,6 +510,8 @@ def render(
487
510
 
488
511
 
489
512
  def main() -> int:
513
+ global _TEST_ENVIRONMENT_HANDOFF
514
+ _TEST_ENVIRONMENT_HANDOFF = None
490
515
  parser = argparse.ArgumentParser(description="Render output cards")
491
516
  parser.add_argument("--app-root", required=True, help="App root directory")
492
517
  parser.add_argument("--wait-dym", action="store_true",
@@ -511,6 +536,7 @@ def main() -> int:
511
536
  help="Display name paired with --form-number",
512
537
  )
513
538
  args = parser.parse_args()
539
+ os.environ["PROJECT_ROOT"] = str(Path(args.app_root).resolve())
514
540
 
515
541
  if args.form_name and not args.form_numbers:
516
542
  parser.error("--form-name requires --form-number")
@@ -536,20 +562,27 @@ def main() -> int:
536
562
  except DeletedArtifactNoCard as exc:
537
563
  print(str(exc), file=sys.stderr)
538
564
  return 0
565
+ except ValueError as exc:
566
+ print(f"Error: {exc}", file=sys.stderr)
567
+ return 1
539
568
  if not blocks:
540
569
  return 1
541
570
 
542
571
  for block in blocks:
543
572
  print(block, flush=True)
573
+ if _TEST_ENVIRONMENT_HANDOFF:
574
+ print("TEST_ENVIRONMENT_HANDOFF=" + json.dumps(_TEST_ENVIRONMENT_HANDOFF, ensure_ascii=False), flush=True)
544
575
  if _DYM_BACKGROUND_NOTICE:
545
- print(_DYM_BACKGROUND_NOTICE, file=sys.stderr, flush=True)
576
+ # 后台同步是必须交给宿主执行的动作,不是诊断日志。放在卡片块后的
577
+ # stdout,避免调用方隐藏 stderr 时把同步请求一起丢弃。
578
+ print(_DYM_BACKGROUND_NOTICE, flush=True)
546
579
 
547
580
  print(
548
- "DELIVERY: The blocks above are the final answer artifact, not links to summarize. "
549
- "Next call output_check once (discover it with search_tools if deferred), then copy "
550
- "the blocks according to references/delivery.md. Do not use Markdown links or "
551
- "code fences, or rerun creation/rendering. If the host has no output_check, "
552
- "copy the blocks directly.",
581
+ "DELIVERY: Preserve the exact blocks for the final delivery response; do not rerender them. "
582
+ "These are domain artifacts, not evidence that the whole request or acceptance is complete. "
583
+ "For delegated implementation, return the blocks and actual results to the owner and continue "
584
+ "its remaining acceptance work. At whole-request finalization, call output_check once if "
585
+ "available, then include the blocks without Markdown links or code fences.",
553
586
  file=sys.stderr,
554
587
  flush=True,
555
588
  )
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env python3
2
+ """Run the production Algo examples against a supplied target SDK, without database queries."""
3
+ import argparse
4
+ import json
5
+ import os
6
+ import subprocess
7
+ import tempfile
8
+ from pathlib import Path
9
+
10
+
11
+ def main():
12
+ parser=argparse.ArgumentParser(description=__doc__)
13
+ parser.add_argument("--sdk-classpath", required=True)
14
+ parser.add_argument("--java-home", required=True)
15
+ parser.add_argument("--rows", type=int, default=10000)
16
+ parser.add_argument("--groups", type=int, default=100)
17
+ parser.add_argument("--iterations", type=int, default=5)
18
+ parser.add_argument("--output")
19
+ args=parser.parse_args()
20
+ if not 1 <= args.groups <= args.rows or args.iterations < 3:
21
+ parser.error("1 <= groups <= rows and iterations >= 3 are required")
22
+ root=Path(__file__).resolve().parents[1]
23
+ suffix=".exe" if os.name=="nt" else ""
24
+ java=Path(args.java_home)/"bin"/("java"+suffix)
25
+ javac=Path(args.java_home)/"bin"/("javac"+suffix)
26
+ with tempfile.TemporaryDirectory(prefix="report-algo-") as directory:
27
+ compiled=subprocess.run([str(javac),"-source","8","-target","8","-cp",args.sdk_classpath,"-d",directory,
28
+ str(root/"examples/java-rules/CosmicReportAggregation.java"),
29
+ str(root/"assets/report-algo-benchmark/ReportBenchmarkLogFactory.java"),
30
+ str(root/"assets/report-algo-benchmark/ReportAlgoBenchmark.java")],capture_output=True,text=True,timeout=60)
31
+ if compiled.returncode:
32
+ print(json.dumps({"status":"blocked","phase":"compile","diagnostics":compiled.stderr[-4000:]}));return 2
33
+ # Use the verified Java 8 runtime. Newer JVMs may need opens flags for SDK managed threads.
34
+ run=subprocess.run([str(java),"-DclusterName=report-benchmark","-Dlogfactory.className=ReportBenchmarkLogFactory",
35
+ "-cp",os.pathsep.join([directory,args.sdk_classpath]),"ReportAlgoBenchmark",str(args.rows),str(args.groups),str(args.iterations)],
36
+ cwd=directory,capture_output=True,text=True,timeout=75)
37
+ lines=[line for line in run.stdout.splitlines() if line.startswith('{"rows":')]
38
+ result={"status":"passed",**json.loads(lines[-1]),"scope":"local Algo computation only; no database, permission or browser latency benchmark"} if run.returncode==0 and lines else {
39
+ "status":"blocked","phase":"runtime","diagnostics":run.stderr[-4000:]}
40
+ if args.output: Path(args.output).write_text(json.dumps(result,ensure_ascii=False,indent=2)+"\n",encoding="utf-8")
41
+ print(json.dumps(result,ensure_ascii=False))
42
+ return 0 if result["status"]=="passed" else 2
43
+
44
+
45
+ if __name__=="__main__":
46
+ raise SystemExit(main())
@@ -1282,8 +1282,8 @@ def parse_entry(prop):
1282
1282
  if not sub_name or sub_type == 'VarcharProp':
1283
1283
  continue
1284
1284
 
1285
- # 检查子单据体(MulBasedataProp / SubEntryProp 均为子单据体;漏 SubEntryProp 会导致子单据体字段丢失)
1286
- # Check for sub-entry
1285
+ # Check for sub-entry(本地补丁 #1:MulBasedataProp / SubEntryProp 均为子单据体;
1286
+ # 漏 SubEntryProp 会导致子单据体字段静默丢失)
1287
1287
  if sub_type in ('MulBasedataProp', 'SubEntryProp') and 'ItemType' in sub_prop:
1288
1288
  sub_entry = parse_sub_entry(sub_prop)
1289
1289
  entry['sub_entries'].append(sub_entry)