@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,5 +1,5 @@
1
1
  #!/usr/bin/env python3
2
- """Compile verified emit artifacts into the only payload a coding agent receives."""
2
+ """Prepare current native form context for Java coding, with optional legacy artifact inputs."""
3
3
 
4
4
  from __future__ import annotations
5
5
 
@@ -7,11 +7,17 @@ import argparse
7
7
  import difflib
8
8
  import json
9
9
  import re
10
+ import subprocess
11
+ import sys
12
+ import uuid
10
13
  from pathlib import Path
11
14
  from typing import Any
12
15
 
13
- from java_source import method_signatures
16
+ from java_source import method_signatures, stripped
14
17
  from resolve_output_path import resolve_output_path
18
+ from platform_api_contract import plugin_coding_evidence
19
+
20
+ COSMIC_API = Path(__file__).resolve().parents[1] / "skills" / "cosmic-meta-api" / "scripts" / "cosmic_api.py"
15
21
 
16
22
 
17
23
  CLASS_RE = re.compile(r"^[A-Za-z][A-Za-z0-9]*$")
@@ -38,16 +44,41 @@ def _read_json(path: str | Path) -> Any:
38
44
  raise HandoffError(f"cannot read JSON {target}: {exc}") from exc
39
45
 
40
46
 
47
+ def manifest_artifacts(manifest_path: str) -> list[str]:
48
+ """Use the exact emit receipt; never search for timestamped artifact names."""
49
+ manifest = _read_json(manifest_path)
50
+ items = manifest.get("artifacts") if isinstance(manifest, dict) else None
51
+ if not isinstance(items, list) or not items:
52
+ raise HandoffError("emit manifest must contain non-empty artifacts", "PLUGIN_MANIFEST_INVALID")
53
+ paths = []
54
+ for item in items:
55
+ value = item.get("path") if isinstance(item, dict) else None
56
+ if not isinstance(value, str) or not value:
57
+ raise HandoffError("emit manifest artifact is missing its path", "PLUGIN_MANIFEST_INVALID")
58
+ target = Path(value).expanduser()
59
+ paths.append(str((target if target.is_absolute() else Path(manifest_path).resolve().parent / target).resolve()))
60
+ return paths
61
+
62
+
63
+ def _artifact_external_catalog(entities: list[dict], fields: list[dict]) -> list[dict]:
64
+ """Reuse native verified emit evidence for entities already in this delivery."""
65
+ return [{
66
+ "formNumber": entity["formNumber"], "formName": entity["formName"],
67
+ "entityKey": entity["entityKey"], "entityName": entity["entityName"],
68
+ "fields": {field["fieldKey"]: {key: field.get(key) for key in ("fieldKey", "fieldName", "fieldType", "refEntity", "comboOptions")}
69
+ for field in fields if field["formNumber"] == entity["formNumber"] and field["entityKey"] == entity["entityKey"]},
70
+ } for entity in entities]
71
+
72
+
41
73
  def _exact_one(records: list[dict], selector: str, keys: tuple[str, ...], kind: str) -> dict:
42
74
  value = str(selector or "").strip()
43
75
  matches = [row for row in records if any(str(row.get(key) or "") == value for key in keys)]
44
76
  if len(matches) != 1:
45
77
  examples = []
46
- for row in records:
47
- candidate = str(
48
- row.get("entityName") or row.get("fieldName")
49
- or row.get("authoringId") or ""
50
- ).strip()
78
+ for row in matches or records:
79
+ candidate = (str(row.get("entityKey") or "") + "." + str(row["fieldKey"])
80
+ + " (" + str(row.get("fieldName") or "") + ")" if row.get("fieldKey")
81
+ else str(row.get("entityKey") or row.get("entityName") or row.get("authoringId") or ""))
51
82
  if candidate and candidate not in examples:
52
83
  examples.append(candidate)
53
84
  if len(examples) == 8:
@@ -161,8 +192,6 @@ def _compile_report_contract(artifact: dict, form_number: str) -> dict | None:
161
192
  column presentation or a non-persisted filter requirement that reaches Java delivery as
162
193
  nothing is exactly how those requirements used to disappear.
163
194
  """
164
- import sys
165
-
166
195
  scripts = Path(__file__).resolve().parents[1] / "skills" / "cosmic-meta-api" / "scripts"
167
196
  if str(scripts) not in sys.path:
168
197
  sys.path.insert(0, str(scripts))
@@ -269,6 +298,11 @@ def _catalog(
269
298
  "entityName": entity_name,
270
299
  "entityId": entity.get("id"),
271
300
  "authoringId": entity.get("id"),
301
+ "entityType": entity.get("type"),
302
+ "role": "header" if entity is main else "subentries" if entity.get("type") == "SubEntryEntity" else "entries",
303
+ "parentEntityKey": next((row.get("entityKey") for row in authored_entities
304
+ if row.get("id") == entity.get("parentEntityId")), None),
305
+ "collectionProperty": entity_key if entity is not main else None,
272
306
  }
273
307
  entities.append(entity_row)
274
308
  authored_fields = list(entity.get("fields") or [])
@@ -307,6 +341,7 @@ def _catalog(
307
341
  # database ("type=DynamicObject") rather than in Java.
308
342
  "fieldType": str(field.get("dataType") or "").strip(),
309
343
  "refEntity": str(field.get("basedataNumber") or "").strip(),
344
+ "comboOptions": field.get("comboOptions") or [],
310
345
  })
311
346
  return forms, entities, fields, registered_plugins
312
347
 
@@ -316,12 +351,19 @@ def _scope(records: list[dict], selector: dict, *, form_keys: tuple[str, ...]) -
316
351
  if selector.get("form"):
317
352
  value = str(selector["form"]).strip()
318
353
  scoped = [row for row in scoped if any(str(row.get(key) or "") == value for key in form_keys)]
354
+ if not scoped:
355
+ raise HandoffError("form scope " + repr(value) + " is unknown; candidates: "
356
+ + ", ".join(sorted({str(row.get("formNumber") or "") for row in records})))
319
357
  if selector.get("entity"):
320
358
  value = str(selector["entity"]).strip()
359
+ candidates = scoped
321
360
  scoped = [row for row in scoped if value in (
322
361
  str(row.get("entityName") or ""), str(row.get("entityKey") or ""),
323
362
  str(row.get("entityId") or ""), str(row.get("authoringId") or ""),
324
- )]
363
+ ) or value in {"header", "entries", "subentries"} and row.get("role") == value]
364
+ if not scoped:
365
+ raise HandoffError("entity scope " + repr(value) + " is unknown; candidates: "
366
+ + ", ".join(sorted({str(row.get("entityKey") or "") for row in candidates})))
325
367
  return scoped
326
368
 
327
369
 
@@ -331,6 +373,10 @@ def _resolve_entity(records: list[dict], selector: Any) -> dict:
331
373
  if not isinstance(selector, dict) or not selector.get("entity"):
332
374
  raise HandoffError("entity selector must be a string or {form, entity}")
333
375
  scoped = _scope(records, selector, form_keys=("formName", "formNumber"))
376
+ if selector["entity"] in {"header", "entries", "subentries"}:
377
+ if len(scoped) == 1:
378
+ return scoped[0]
379
+ raise HandoffError("entity scope is ambiguous; candidates: " + ", ".join(row["entityKey"] for row in scoped))
334
380
  return _exact_one(scoped, selector["entity"], ("entityName", "entityKey", "entityId", "authoringId"), "entity")
335
381
 
336
382
 
@@ -383,6 +429,8 @@ _IMPLICIT_ENTITY_FIELDS = {"id", "masterid", "seq"}
383
429
  # Cosmic field type -> the Java type its value carries. Only the families that carry the bug this
384
430
  # verification exists to stop are mapped: a reference field holds the referenced record's id, and
385
431
  # reading that as a String is what produced "operator does not exist: bigint = character varying".
432
+ # The target 8.0 SDK's TimeProp.getPropertyType() is Integer. It stores seconds after midnight;
433
+ # DynamicObject.getDate() converts that Integer to null, silently skipping interval checks.
386
434
  # Everything else keeps its verbatim fieldType and no javaType, because a plausible-looking guess
387
435
  # here would carry the same authority as a verified one.
388
436
  _FIELD_JAVA_TYPES = (
@@ -411,6 +459,9 @@ def _field_java_type(field_type: str) -> str | None:
411
459
  # A Mul* field holds a collection, not one value, so the single-value mapping does not apply.
412
460
  if field_type.startswith("Mul"):
413
461
  return None
462
+ # Match the exact time-only types: DateTimeField also contains "TimeField" as a suffix.
463
+ if field_type in ("TimeProp", "TimeField"):
464
+ return "Integer"
414
465
  for marker, java_type in _FIELD_JAVA_TYPES:
415
466
  if marker.lower() in field_type.lower():
416
467
  return java_type
@@ -443,15 +494,23 @@ def _field_contract(row: dict) -> dict | None:
443
494
  "fieldKey": field_key,
444
495
  "fieldName": str(row.get("fieldName") or "").strip(),
445
496
  "fieldType": field_type,
497
+ **{key: row[key] for key in ("entityKey", "entityName", "parentEntityKey", "collectionPath", "fieldPath", "required") if key in row},
446
498
  }
447
499
  if row.get("refEntity"):
448
500
  contract["refEntity"] = row["refEntity"]
501
+ if row.get("comboOptions"):
502
+ contract["comboOptions"] = row["comboOptions"]
449
503
  java_type = _field_java_type(field_type)
450
504
  if java_type:
451
505
  contract["javaType"] = java_type
452
- if _is_reference_field(field_type):
506
+ if field_type in ("TimeProp", "TimeField"):
507
+ contract["valueAccessor"] = f'getInt("{field_key}")'
508
+ contract["timeValueUnit"] = "seconds-after-midnight"
509
+ reference = (bool(row.get("refEntity") or row.get("refIdProperty"))
510
+ if field_type.endswith("Prop") else _is_reference_field(field_type))
511
+ if reference and not field_type.startswith("Mul"):
453
512
  contract["valueAccessor"] = f'getDynamicObject("{field_key}")'
454
- contract["idAccessor"] = f'getLong("{field_key}_id")'
513
+ contract["idAccessor"] = f'getLong("{row.get("refIdProperty") or field_key + "_id"}")'
455
514
  contract["filterValue"] = f'getDynamicObject("{field_key}").getPkValue()'
456
515
  contract["note"] = (
457
516
  f'get("{field_key}") returns the referenced DynamicObject, not its id. A QFilter on '
@@ -587,6 +646,15 @@ def _verify_external_contracts(contracts: list, catalog: list[dict]) -> list:
587
646
  Returns the same list with each verified entry enriched by ``verifiedFields``, so the
588
647
  coding step receives the real key and its Java value type instead of rediscovering both.
589
648
  """
649
+ missing = list(dict.fromkeys(str(entry["entity"]).strip() for entry in contracts
650
+ if isinstance(entry, dict) and entry.get("entity")
651
+ and not _external_scope(catalog, str(entry["entity"]))))
652
+ if missing:
653
+ raise HandoffError(
654
+ "external contract entities/forms are not verified: " + ", ".join(missing)
655
+ + "; supply exact getFormSchema read-backs via --external-schema or use verified batch evidence",
656
+ "PLUGIN_EXTERNAL_CONTRACT_UNVERIFIED",
657
+ )
590
658
  verified: list = []
591
659
  for entry in contracts:
592
660
  if not isinstance(entry, dict):
@@ -597,13 +665,6 @@ def _verify_external_contracts(contracts: list, catalog: list[dict]) -> list:
597
665
  verified.append(entry)
598
666
  continue
599
667
  scope = _external_scope(catalog, entity_selector)
600
- if not scope:
601
- raise HandoffError(
602
- f"external contract entity/form {entity_selector!r} is not verified; save its "
603
- "exact form read-back with cosmic_api.py --output <path> getFormSchema "
604
- "--formNumber <formNumber> and pass that file as --external-schema",
605
- "PLUGIN_EXTERNAL_CONTRACT_UNVERIFIED",
606
- )
607
668
  selectors = _contract_field_selectors(entry)
608
669
  resolved = [_external_field(scope, selector) for selector in selectors]
609
670
  entities = {row["entityKey"] for row, _key, _field in resolved}
@@ -648,16 +709,25 @@ def _verified_field(field_key: str, field: dict | None, entity: dict | None = No
648
709
  result.update({"entityKey": entity["entityKey"], "entityName": entity["entityName"]})
649
710
  if field.get("refEntity"):
650
711
  result["refEntity"] = field["refEntity"]
712
+ if field.get("comboOptions"):
713
+ result["comboOptions"] = field["comboOptions"]
651
714
  java_type = _field_java_type(field_type)
652
715
  if java_type:
653
716
  result["javaType"] = java_type
717
+ if field_type in ("TimeProp", "TimeField"):
718
+ result["valueAccessor"] = f'getInt("{field_key}")'
719
+ result["timeValueUnit"] = "seconds-after-midnight"
654
720
  return result
655
721
 
656
722
 
657
723
  def _verified_operation_keys(value: Any) -> set[str]:
658
724
  """Extract operation keys from a listOperations response without assuming one envelope."""
659
- if isinstance(value, dict) and value.get("status") is False:
660
- raise HandoffError("listOperations did not succeed")
725
+ if isinstance(value, dict):
726
+ data = value.get("data")
727
+ if (any(layer.get("status") is False or layer.get("success") is False
728
+ for layer in (value, data) if isinstance(layer, dict))
729
+ or value.get("errorCode") not in (None, "", 0, "0")):
730
+ raise HandoffError("listOperations did not succeed")
661
731
  found: set[str] = set()
662
732
 
663
733
  def visit(node: Any) -> None:
@@ -680,6 +750,195 @@ def _verified_operation_keys(value: Any) -> set[str]:
680
750
  return found
681
751
 
682
752
 
753
+ def operation_contracts(value: Any, keys: list[str]) -> list[dict]:
754
+ """Preserve actual operation parameters instead of guessing business state transitions."""
755
+ if isinstance(value, list):
756
+ return [entry for child in value for entry in operation_contracts(child, keys)]
757
+ if not isinstance(value, dict):
758
+ return []
759
+ key = value.get("operationKey") or value.get("Key")
760
+ if key in keys:
761
+ return [{"operationKey": key, **{name: value[name] for name in
762
+ ("operationId", "operationName", "operationType", "parameter") if name in value}}]
763
+ return [entry for child in value.values() if isinstance(child, (dict, list))
764
+ for entry in operation_contracts(child, keys)]
765
+
766
+
767
+ def read_platform(app_root: str, command: str, selector: str, flag: str = "--formNumber") -> Any:
768
+ import hashlib
769
+ destination = Path(app_root).resolve() / "_runtime" / "plugin-evidence" / (
770
+ command + "." + hashlib.sha256(selector.encode()).hexdigest()[:12] + "." + uuid.uuid4().hex + ".json")
771
+ destination.parent.mkdir(parents=True, exist_ok=True)
772
+ # Do not let a previous successful file stand in for a failed current read.
773
+ destination.unlink(missing_ok=True)
774
+ try:
775
+ completed = subprocess.run([sys.executable, str(COSMIC_API), "--output", str(destination), command,
776
+ "--app-root", str(Path(app_root).resolve()), flag, selector],
777
+ capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=60)
778
+ except (OSError, subprocess.TimeoutExpired) as exc:
779
+ raise HandoffError(f"{command} could not complete for {selector}", "PLUGIN_EVIDENCE_READ_FAILED") from exc
780
+ if completed.returncode or not destination.is_file():
781
+ raise HandoffError(f"{command} failed for {selector}; no fresh evidence saved",
782
+ "PLUGIN_EVIDENCE_READ_FAILED")
783
+ value = _read_json(destination)
784
+ if isinstance(value, dict) and (value.get("status") is False
785
+ or value.get("errorCode") not in (None, "", 0, "0")
786
+ or isinstance(value.get("data"), dict) and value["data"].get("success") is False):
787
+ raise HandoffError(f"{command} failed for {selector}: {value.get('message') or value.get('errorCode')}",
788
+ "PLUGIN_EVIDENCE_READ_FAILED")
789
+ return value
790
+
791
+
792
+ def prepare_inputs(*, app_root: str, intent: dict, plugin_type: str, artifact_paths: list[str],
793
+ run_result: Any, operation_result: Any, external_schemas: list[Any]) -> tuple[Any, list[Any]]:
794
+ """Collect only missing read-only platform evidence for this verified target."""
795
+ forms, entities, fields, _ = _catalog(artifact_paths, run_result)
796
+ main = _exact_one(forms, intent.get("form"), ("formName", "formNumber"), "form")
797
+ _string_list(intent.get("coreLogic"), "coreLogic", required=True)
798
+ _string_list(intent.get("scenes"), "scenes")
799
+ keys = _string_list(intent.get("operationKeys"), "operationKeys")
800
+ if plugin_type == "operation" and not keys:
801
+ raise HandoffError("type=operation requires operationKeys")
802
+ if plugin_type != "operation" and keys:
803
+ raise HandoffError("operationKeys are only valid for type=operation")
804
+ if isinstance(run_result, dict) and run_result.get("appRoot") and Path(run_result["appRoot"]).resolve() != Path(app_root).resolve():
805
+ raise HandoffError("write result belongs to another application", "PLUGIN_HANDOFF_UNRESOLVED")
806
+ specialized = intent.get("specialized") or {}
807
+ if not isinstance(specialized, dict) or "unresolvedDependencies" not in specialized:
808
+ raise HandoffError("declare specialized.unresolvedDependencies before platform reads")
809
+ if specialized.get("unresolvedDependencies"):
810
+ raise HandoffError("resolve declared dependencies before platform reads", "PLUGIN_EXTERNAL_CONTRACT_UNRESOLVED")
811
+ catalog = _external_entity_catalog(external_schemas)
812
+ if run_result is not None:
813
+ catalog += _artifact_external_catalog(entities, fields)
814
+ missing = [entry for name in ("externalContracts", "externalApis")
815
+ for entry in _contract_list(specialized.get(name), name)
816
+ if isinstance(entry, dict) and entry.get("entity")
817
+ and not _external_scope(catalog, str(entry["entity"]))]
818
+ selectors = list(dict.fromkeys(str(entry.get("formNumber") or entry["entity"]) for entry in missing))
819
+ invalid = [selector for selector in selectors if not re.fullmatch(r"[A-Za-z0-9_]+", selector)]
820
+ if invalid:
821
+ raise HandoffError("automatic schema reads require exact formNumber for: " + ", ".join(invalid),
822
+ "PLUGIN_EXTERNAL_CONTRACT_UNVERIFIED")
823
+
824
+ from concurrent.futures import ThreadPoolExecutor
825
+ with ThreadPoolExecutor(max_workers=4) as pool:
826
+ schemas = list(pool.map(lambda selector: read_platform(app_root, "getFormSchema", selector), selectors))
827
+ if plugin_type == "operation" and operation_result is None:
828
+ operation_result = read_platform(app_root, "listOperations", main["formNumber"])
829
+ return operation_result, external_schemas + schemas
830
+
831
+
832
+ def runtime_catalog(snapshots: list[Any]) -> tuple[list[dict], list[dict], list[dict], list[dict]]:
833
+ """Project getEntityType's native Properties/ItemType graph without authoring-key derivation."""
834
+ forms, entities, fields = [], [], []
835
+
836
+ def text(value: Any) -> str:
837
+ if isinstance(value, dict):
838
+ return str(value.get("zh_CN") or value.get("en_US") or "")
839
+ return str(value or "")
840
+
841
+ def visit(node: dict, form: dict, parent: dict | None = None, prop: dict | None = None) -> None:
842
+ key = str(node.get("Name") or "").strip()
843
+ properties = node.get("Properties")
844
+ if not key or not isinstance(properties, list):
845
+ raise HandoffError("runtime entity has no Name/Properties", "PLUGIN_CONTEXT_INCOMPLETE")
846
+ role = "header" if parent is None else "entries" if parent["role"] == "header" else "subentries"
847
+ collection = str((prop or {}).get("Name") or "")
848
+ path = parent["collectionPath"] + [collection] if parent else []
849
+ if parent and not collection:
850
+ raise HandoffError("runtime collection property has no Name", "PLUGIN_CONTEXT_INCOMPLETE")
851
+ entity = {**form, "entityKey": key, "entityName": text((prop or {}).get("DisplayName") or node.get("DisplayName")) or key,
852
+ "entityId": key, "authoringId": key, "entityType": node.get("_Type_"), "role": role,
853
+ "parentEntityKey": parent["entityKey"] if parent else None,
854
+ "collectionProperty": collection or None, "collectionPath": path}
855
+ entities.append(entity)
856
+ for property in properties:
857
+ if not isinstance(property, dict) or not property.get("Name"):
858
+ continue
859
+ kind = str(property.get("_Type_") or "")
860
+ if kind in {"EntryProp", "SubEntryProp"}:
861
+ item = property.get("ItemType")
862
+ if not isinstance(item, dict):
863
+ raise HandoffError("runtime collection has no ItemType", "PLUGIN_CONTEXT_INCOMPLETE")
864
+ visit(item, form, entity, property)
865
+ continue
866
+ name = str(property["Name"])
867
+ if not kind:
868
+ raise HandoffError(f"runtime field {name} has no type", "PLUGIN_CONTEXT_INCOMPLETE")
869
+ fields.append({**entity, "fieldKey": name, "fieldName": text(property.get("DisplayName")) or name,
870
+ "fieldType": kind, "fieldId": property.get("Id"), "authoringId": name,
871
+ "fieldPath": ".".join(path + [name]), "refEntity": property.get("BaseEntityId") or "",
872
+ "required": property.get("MustInput") is True,
873
+ "refIdProperty": property.get("RefIdPropName"),
874
+ "metadataResourceId": (property.get("DisplayName") or {}).get("resId")
875
+ if isinstance(property.get("DisplayName"), dict) else None,
876
+ "comboOptions": [{"label": text(option.get("Name")), "value": option.get("Value")}
877
+ for option in property.get("ComboItems") or []]})
878
+
879
+ for snapshot in snapshots:
880
+ if not isinstance(snapshot, dict) or snapshot.get("status") is False:
881
+ raise HandoffError("runtime metadata read did not succeed", "PLUGIN_CONTEXT_INCOMPLETE")
882
+ data = snapshot.get("data", snapshot)
883
+ if isinstance(data, str):
884
+ data = json.loads(data)
885
+ if not isinstance(data, dict) or not data.get("Name"):
886
+ raise HandoffError("runtime metadata carries no entity definition", "PLUGIN_CONTEXT_INCOMPLETE")
887
+ form = {"formNumber": data["Name"], "formName": text(data.get("DisplayName")) or data["Name"],
888
+ "appCode": data.get("BizAppNumber"), "metadataVersion": data.get("Version"),
889
+ "metadataSource": "getEntityType"}
890
+ forms.append(form)
891
+ visit(data, form)
892
+ if not any(row["formNumber"] == form["formNumber"] for row in fields):
893
+ raise HandoffError("runtime entity carries no named fields", "PLUGIN_CONTEXT_INCOMPLETE")
894
+ return forms, entities, fields, []
895
+
896
+
897
+ def prepare_runtime_inputs(app_root: str, form_number: str, intent: dict, plugin_type: str) -> tuple[list[Any], Any]:
898
+ """Read current main/explicitly referenced forms once, independently of modeling artifacts."""
899
+ _string_list(intent.get("coreLogic"), "coreLogic", required=True)
900
+ keys = _string_list(intent.get("operationKeys"), "operationKeys")
901
+ if plugin_type == "operation" and not keys:
902
+ raise HandoffError("type=operation requires operationKeys")
903
+ if plugin_type != "operation" and keys:
904
+ raise HandoffError("operationKeys are only valid for type=operation")
905
+ specialized = intent.get("specialized") or {}
906
+ if not isinstance(specialized, dict) or "unresolvedDependencies" not in specialized:
907
+ raise HandoffError("declare specialized.unresolvedDependencies before platform reads")
908
+ if specialized["unresolvedDependencies"]:
909
+ raise HandoffError("resolve declared dependencies before platform reads", "PLUGIN_EXTERNAL_CONTRACT_UNRESOLVED")
910
+ main = read_platform(app_root, "getEntityType", form_number, "--entityId")
911
+ forms, entities, fields, _ = runtime_catalog([main])
912
+ if forms[0]["formNumber"] != form_number:
913
+ raise HandoffError("runtime metadata belongs to another form", "PLUGIN_CONTEXT_INCOMPLETE")
914
+ archive = Path(app_root) / "archive.json"
915
+ identity = _read_json(archive) if archive.is_file() else {}
916
+ if forms[0].get("appCode") and identity.get("appCode") and forms[0]["appCode"] != identity["appCode"]:
917
+ raise HandoffError("runtime main form belongs to another application", "PLUGIN_CONTEXT_INCOMPLETE")
918
+ references = {field["refEntity"] for field in fields if field.get("refEntity")}
919
+ requests = []
920
+ catalog = _artifact_external_catalog(entities, fields)
921
+ for name in ("externalContracts", "externalApis"):
922
+ for entry in _contract_list(specialized.get(name), name):
923
+ if not isinstance(entry, dict) or not entry.get("entity") or _external_scope(catalog, str(entry["entity"])):
924
+ continue
925
+ selector = str(entry.get("formNumber") or entry["entity"])
926
+ if not re.fullmatch(r"[A-Za-z0-9_]+", selector):
927
+ # Resolve a unique declared reference's display name from its native metadata.
928
+ # Fetching declared reference targets is bounded to this form, never a global form tour.
929
+ requests += sorted(references)
930
+ else:
931
+ requests.append(selector)
932
+ from concurrent.futures import ThreadPoolExecutor
933
+ with ThreadPoolExecutor(max_workers=4) as pool:
934
+ related = list(pool.map(lambda selector: read_platform(app_root, "getEntityType", selector, "--entityId"),
935
+ dict.fromkeys(requests)))
936
+ snapshots = [main] + related
937
+ runtime_catalog(snapshots) # Fail before coding when any requested graph is incomplete.
938
+ operations = read_platform(app_root, "listOperations", form_number) if plugin_type == "operation" else None
939
+ return snapshots, operations
940
+
941
+
683
942
  def _bind_unique_mentions(
684
943
  records: list[dict], text: str, existing: dict[str, str],
685
944
  *, persisted_key: str, mention_keys: tuple[str, ...],
@@ -731,10 +990,16 @@ def prepare_handoff(
731
990
  *, app_root: str, project_root: str | None, plugin_type: str,
732
991
  intent: dict, artifact_paths: list[str], run_result: Any = None,
733
992
  operation_result: Any = None, external_schemas: list[Any] | None = None,
993
+ runtime_metadata: list[Any] | None = None,
734
994
  ) -> dict:
735
995
  if not isinstance(intent, dict):
736
996
  raise HandoffError("intent must be a JSON object")
737
- forms, entities, fields, registered_plugins = _catalog(artifact_paths, run_result)
997
+ if runtime_metadata is not None:
998
+ if not runtime_metadata:
999
+ raise HandoffError("runtime metadata is empty", "PLUGIN_CONTEXT_INCOMPLETE")
1000
+ artifact_paths = []
1001
+ forms, entities, fields, registered_plugins = (runtime_catalog(runtime_metadata) if runtime_metadata
1002
+ else _catalog(artifact_paths, run_result))
738
1003
  main_form = _exact_one(forms, intent.get("form"), ("formName", "formNumber"), "form")
739
1004
  related = None
740
1005
  if intent.get("relatedForm"):
@@ -748,6 +1013,12 @@ def prepare_handoff(
748
1013
  if operation_result is None:
749
1014
  raise HandoffError("type=operation requires a listOperations read-back via --operations-file")
750
1015
  verified_operations = _verified_operation_keys(operation_result)
1016
+ operation_data = operation_result.get("data") if isinstance(operation_result, dict) else None
1017
+ if isinstance(operation_data, dict):
1018
+ if operation_data.get("formNumber") and operation_data["formNumber"] != main_form["formNumber"]:
1019
+ raise HandoffError("listOperations result belongs to another form")
1020
+ if operation_data.get("appRoot") and Path(operation_data["appRoot"]).resolve() != Path(app_root).resolve():
1021
+ raise HandoffError("listOperations result belongs to another application")
751
1022
  missing_operations = [key for key in operation_keys if key not in verified_operations]
752
1023
  if missing_operations:
753
1024
  raise HandoffError(
@@ -851,7 +1122,15 @@ def prepare_handoff(
851
1122
  "PLUGIN_SOURCE_NOT_FOUND",
852
1123
  )
853
1124
  class_name = class_name or existing.stem
854
- existing_methods = method_signatures(existing.read_text(encoding="utf-8-sig"))
1125
+ source_text = existing.read_text(encoding="utf-8-sig")
1126
+ existing_methods = method_signatures(source_text)
1127
+ declared_package = re.search(r"\bpackage\s+([A-Za-z_][\w.]*)\s*;", stripped(source_text))
1128
+ if declared_package:
1129
+ source_package = declared_package.group(1).replace(".", "/")
1130
+ if form_plugins and source_package != package_path:
1131
+ raise HandoffError("existing source package conflicts with the registered plugin class", "PLUGIN_CLASS_NOT_REGISTERED")
1132
+ package_path = source_package
1133
+ package_source = "existing source declaration"
855
1134
  else:
856
1135
  # An existing file at the create-new target means the class is already in this project,
857
1136
  # so generating it again would either duplicate its methods or overwrite its logic. The
@@ -870,6 +1149,8 @@ def prepare_handoff(
870
1149
  f'ownershipMode=modify with sourcePath "{target}"',
871
1150
  "PLUGIN_OWNERSHIP_CONFLICT",
872
1151
  )
1152
+ class_name = candidate or class_name
1153
+ source_path = str(target) if target else source_path
873
1154
 
874
1155
  entity_selectors = intent.get("entities") or {}
875
1156
  field_selectors = intent.get("fields") or {}
@@ -900,6 +1181,11 @@ def prepare_handoff(
900
1181
  "PLUGIN_EXTERNAL_CONTRACT_UNRESOLVED",
901
1182
  )
902
1183
  external_catalog = _external_entity_catalog(external_schemas or [])
1184
+ if run_result is not None or runtime_metadata:
1185
+ external_catalog += [
1186
+ row for row in _artifact_external_catalog(entities, fields)
1187
+ if not any(existing["entityKey"] == row["entityKey"] for existing in external_catalog)
1188
+ ]
903
1189
  for key in ("externalContracts", "externalApis"):
904
1190
  specialized[key] = _verify_external_contracts(specialized[key], external_catalog)
905
1191
  entity_map = {
@@ -911,14 +1197,14 @@ def prepare_handoff(
911
1197
  for label, selector in field_selectors.items()
912
1198
  }
913
1199
  logic_text = "\n".join(core_logic)
914
- auto_entities = _bind_unique_mentions(
1200
+ auto_entities = ([] if runtime_metadata else _bind_unique_mentions(
915
1201
  entities, logic_text, entity_map,
916
1202
  persisted_key="entityKey", mention_keys=("entityKey", "entityName", "authoringId"),
917
- )
918
- auto_fields = _bind_unique_mentions(
1203
+ ))
1204
+ auto_fields = ([] if runtime_metadata else _bind_unique_mentions(
919
1205
  fields, logic_text, field_map,
920
1206
  persisted_key="fieldKey", mention_keys=("fieldKey", "fieldName", "authoringId"),
921
- )
1207
+ ))
922
1208
  # Built after auto-binding so a field the core logic mentioned by name carries the same type
923
1209
  # evidence as one the intent selected explicitly.
924
1210
  contracts_by_key: dict[str, dict | None] = {}
@@ -931,11 +1217,9 @@ def prepare_handoff(
931
1217
  contracts_by_key[key] = None
932
1218
  continue
933
1219
  contracts_by_key[key] = contract
934
- field_contracts = [
935
- contracts_by_key[key]
936
- for key in dict.fromkeys(field_map.values())
937
- if contracts_by_key.get(key)
938
- ]
1220
+ field_contracts = ([_field_contract(row) for row in fields if row["fieldKey"] in field_map.values()] if runtime_metadata else [
1221
+ contracts_by_key[key] for key in dict.fromkeys(field_map.values()) if contracts_by_key.get(key)
1222
+ ])
939
1223
  handoff = {
940
1224
  "appRoot": str(Path(app_root).resolve()),
941
1225
  "projectRoot": path_result["projectRoot"],
@@ -950,7 +1234,54 @@ def prepare_handoff(
950
1234
  "fields": field_map,
951
1235
  "coreLogic": core_logic,
952
1236
  "specialized": specialized,
1237
+ "description": (str(intent.get("description") or "").strip() or core_logic[0])[:20],
953
1238
  }
1239
+ if runtime_metadata:
1240
+ def graph(form: dict) -> dict:
1241
+ return {key: form[key] for key in ("formNumber", "formName", "metadataSource", "metadataVersion")} | {
1242
+ "entities": [{key: entity[key] for key in
1243
+ ("entityKey", "entityName", "entityType", "role", "parentEntityKey", "collectionProperty", "collectionPath")}
1244
+ | {"fields": [_field_contract(row) for row in fields
1245
+ if row["formNumber"] == form["formNumber"] and row["entityKey"] == entity["entityKey"]]}
1246
+ for entity in entities if entity["formNumber"] == form["formNumber"]]}
1247
+ handoff["formGraph"] = graph(main_form)
1248
+ handoff["relatedForms"] = [graph(form) for form in forms if form["formNumber"] != main_form["formNumber"]]
1249
+ handoff["entities"].update({row["entityName"]: row["entityKey"] for row in entities
1250
+ if row["formNumber"] == main_form["formNumber"]})
1251
+ if operation_keys:
1252
+ handoff["operationContracts"] = operation_contracts(operation_result, operation_keys)
1253
+ if runtime_metadata:
1254
+ handoff["availableOperations"] = [{key: operation[key] for key in
1255
+ ("operationKey", "operationName", "operationType") if key in operation}
1256
+ for operation in operation_contracts(operation_result, sorted(verified_operations))]
1257
+ handoff["stateFields"] = [{key: row[key] for key in
1258
+ ("fieldKey", "fieldName", "fieldId", "entityKey", "comboOptions")}
1259
+ for row in fields if row.get("comboOptions")]
1260
+ warnings = []
1261
+ for operation in handoff["operationContracts"]:
1262
+ parameter = operation.get("parameter")
1263
+ if not isinstance(parameter, dict):
1264
+ continue
1265
+ target_id = parameter.get("StatusFieldId")
1266
+ if not target_id or "Value" not in parameter:
1267
+ continue
1268
+ targets = [row for row in fields if row["formNumber"] == main_form["formNumber"] and
1269
+ (row.get("fieldId") == target_id or str(row.get("metadataResourceId") or "").endswith("." + str(target_id) + ".Name"))]
1270
+ target = targets[0] if len(targets) == 1 else None
1271
+ if target:
1272
+ operation["statusFieldKey"] = target["fieldKey"]
1273
+ if target is None:
1274
+ warnings.append({"operationKey": operation["operationKey"], "statusFieldId": target_id,
1275
+ "value": parameter["Value"], "code": "OPERATION_STATE_TARGET_UNRESOLVED",
1276
+ "message": "Do not equate this operation with a business state until its status field is resolved."})
1277
+ continue
1278
+ options = target.get("comboOptions") or []
1279
+ if options and not any(str(option.get("value")) == str(parameter["Value"]) for option in options):
1280
+ warnings.append({"operationKey": operation["operationKey"], "fieldKey": target["fieldKey"],
1281
+ "value": parameter["Value"], "code": "OPERATION_STATE_VALUE_MISMATCH",
1282
+ "message": "Operation value is absent from the verified field options; resolve the business mapping before coding."})
1283
+ if warnings:
1284
+ handoff["stateWarnings"] = warnings
954
1285
  if field_contracts:
955
1286
  # `fields` stays a bare {label: fieldKey} map because it is what the coding step turns
956
1287
  # into constants. The value types travel alongside it so a reference field cannot be
@@ -973,6 +1304,12 @@ def prepare_handoff(
973
1304
  handoff["className"] = class_name
974
1305
  if source_path:
975
1306
  handoff["sourcePath"] = str(Path(source_path).expanduser().resolve())
1307
+ if class_name:
1308
+ handoff["fqcn"] = package_path.replace("/", ".") + "." + class_name
1309
+ try:
1310
+ handoff["codingEvidence"] = plugin_coding_evidence(plugin_type, _string_list(intent.get("scenes"), "scenes"))
1311
+ except ValueError as exc:
1312
+ raise HandoffError(str(exc), "PLUGIN_CODING_EVIDENCE_INVALID") from exc
976
1313
  if ownership_mode == "modify":
977
1314
  # The methods the target class already declares, always present for modify so an empty
978
1315
  # list means an empty class body rather than an unread file. New logic belongs inside
@@ -998,18 +1335,86 @@ def prepare_handoff(
998
1335
  }
999
1336
 
1000
1337
 
1338
+ def handoff_response(result: dict, *, full_output: bool = False) -> dict:
1339
+ """Keep the CLI below the host output limit; the saved context remains complete."""
1340
+ if full_output:
1341
+ return {key: result[key] for key in ("status", "handoffPath", "handoff")}
1342
+ handoff = result["handoff"]
1343
+ coding = handoff.get("codingEvidence", {})
1344
+ graphs = [handoff["formGraph"], *handoff.get("relatedForms", [])] if handoff.get("formGraph") else []
1345
+ return {
1346
+ "status": result["status"],
1347
+ "handoffPath": result["handoffPath"],
1348
+ "contextMode": "reference-only",
1349
+ "handoff": {
1350
+ **{key: handoff[key] for key in (
1351
+ "appRoot", "projectRoot", "packagePath", "type", "ownershipMode", "formNumber",
1352
+ "className", "sourcePath", "fqcn", "operationKeys", "sourceGate",
1353
+ ) if key in handoff},
1354
+ **({'targetSdk': {key: handoff['targetSdk'][key] for key in
1355
+ ('status', 'projectRoot', 'module', 'evidenceScope') if key in handoff['targetSdk']}}
1356
+ if 'targetSdk' in handoff else {}),
1357
+ "codingEvidence": {
1358
+ **{key: coding[key] for key in ("codingEntry", "guide", "typeKnowledge", "apiLookup", "sdkLookup", "operationDispatch") if key in coding},
1359
+ "signatureTypes": coding.get("signatureTypes", [])[:12],
1360
+ "signatureTypeCount": len(coding.get("signatureTypes", [])),
1361
+ },
1362
+ },
1363
+ "metadata": {
1364
+ "formCount": len(graphs),
1365
+ "forms": [{
1366
+ **{key: graph[key] for key in ("formNumber", "metadataSource", "metadataVersion") if key in graph},
1367
+ "entityCount": len(graph.get("entities", [])),
1368
+ "fieldCount": sum(len(entity.get("fields", [])) for entity in graph.get("entities", [])),
1369
+ } for graph in graphs[:8]],
1370
+ },
1371
+ "contextSections": ["handoff.formGraph", "handoff.relatedForms", "handoff.specialized",
1372
+ "handoff.operationContracts", "handoff.stateFields", "handoff.stateWarnings",
1373
+ "handoff.codingEvidence", "handoff.reportContract", "handoff.targetSdk"],
1374
+ "instruction": (
1375
+ "This response is a summary, not the coding context. Keep the complete saved handoffPath "
1376
+ "for native compilation; begin with query_plugin_handoff.py coding-start, then query only missing field paths, collection hierarchy, external contracts, "
1377
+ "operation values, warnings and SDK signatures needed for this class with query_plugin_handoff.py. "
1378
+ "Index or find exact paths before get; do not read the whole JSON into the model. Use its exact codingEntry "
1379
+ "and verified signatureTypes imports; missing or ambiguous SDK types require the supplied API lookup. "
1380
+ "Pass handoffPath directly to compile_plugin_source.py --handoff; do not search for another file."
1381
+ ),
1382
+ }
1383
+
1384
+
1385
+ def assert_handoff_replace(destination: Path, result: dict, replace: bool = False) -> None:
1386
+ if not destination.is_file() or replace:
1387
+ return
1388
+ existing = _read_json(destination)
1389
+ previous = existing.get("handoff") if isinstance(existing, dict) and existing.get("status") is True else None
1390
+ current = result["handoff"]
1391
+ fields = ("projectRoot", "appRoot", "sourcePath", "fqcn", "formNumber", "type", "coreLogic")
1392
+ if not isinstance(previous, dict) or any(previous.get(key) != current.get(key) for key in fields) or sorted(
1393
+ previous.get("operationKeys", [])) != sorted(current.get("operationKeys", [])):
1394
+ raise HandoffError(
1395
+ f"Prepared class already has a different snapshot at {destination}. "
1396
+ "Review the existing snapshot and reconcile the class identity and business scope before explicitly replacing it with --replace-handoff.",
1397
+ "PLUGIN_HANDOFF_CONFLICT")
1398
+
1399
+
1001
1400
  def main() -> None:
1002
1401
  parser = argparse.ArgumentParser(description="Prepare a verified compact Java-plugin handoff")
1003
1402
  parser.add_argument("--app-root", required=True)
1004
1403
  parser.add_argument("--java-project-root", "--project-root", dest="project_root")
1005
1404
  parser.add_argument("--type", required=True, dest="plugin_type")
1006
- parser.add_argument("--artifact", action="append", required=True, dest="artifact_paths")
1405
+ parser.add_argument("--replace-handoff", action="store_true")
1406
+ artifacts = parser.add_mutually_exclusive_group()
1407
+ artifacts.add_argument("--artifact", action="append", dest="artifact_paths")
1408
+ artifacts.add_argument("--emit-manifest", help="Exact manifest path returned by the emitter; includes every declared artifact")
1007
1409
  parser.add_argument("--run-result")
1008
- parser.add_argument("--operations-file", help="JSON stdout from listOperations; required for type=operation")
1410
+ parser.add_argument("--form-number", help="Exact existing form number; independently reads runtime metadata")
1411
+ parser.add_argument("--operations-file", help="Reuse an existing listOperations result; otherwise read once automatically")
1412
+ parser.add_argument("--output", help="Save prepared context here; default is the app-local class handoff file")
1413
+ parser.add_argument("--full-output", action="store_true", help="Print the complete handoff JSON instead of its compact summary")
1414
+ parser.add_argument("--prepare-source", action="store_true", help="Mark this verified class writable for implementation; does not write Java source")
1009
1415
  parser.add_argument(
1010
1416
  "--external-schema", action="append", dest="external_schemas",
1011
- help="JSON stdout from getFormSchema for an entity outside the artifacts; required for "
1012
- "every specialized.externalContracts entry that names an entity. Repeatable.",
1417
+ help="JSON from getFormSchema for an entity contract outside the verified artifact batch. Repeatable.",
1013
1418
  )
1014
1419
  intent_group = parser.add_mutually_exclusive_group(required=True)
1015
1420
  intent_group.add_argument("--intent-json")
@@ -1017,21 +1422,62 @@ def main() -> None:
1017
1422
  args = parser.parse_args()
1018
1423
  try:
1019
1424
  intent = json.loads(args.intent_json) if args.intent_json else _read_json(args.intent_file)
1425
+ if not isinstance(intent, dict):
1426
+ raise HandoffError("intent must be a JSON object")
1020
1427
  run_result = _read_json(args.run_result) if args.run_result else None
1428
+ if run_result is not None and not isinstance(run_result, dict):
1429
+ raise HandoffError("write result must be a JSON object")
1021
1430
  operation_result = _read_json(args.operations_file) if args.operations_file else None
1431
+ artifact_paths = (manifest_artifacts(args.emit_manifest) if args.emit_manifest else args.artifact_paths
1432
+ or (run_result or {}).get("artifactPaths"))
1433
+ runtime_metadata = None
1434
+ if args.form_number or not artifact_paths or args.plugin_type == "operation":
1435
+ form_number = args.form_number or str(intent.get("form") or "")
1436
+ if not args.form_number and artifact_paths:
1437
+ hints = (run_result or {}).get("forms") or _catalog(artifact_paths, run_result)[0]
1438
+ form_number = _exact_one(hints, intent.get("form"), ("formName", "formNumber"), "form")["formNumber"]
1439
+ if not re.fullmatch(r"[A-Za-z0-9_]+", form_number):
1440
+ raise HandoffError("independent preparation requires --form-number with an exact existing form code")
1441
+ runtime_metadata, operation_result = prepare_runtime_inputs(args.app_root, form_number, intent, args.plugin_type)
1442
+ intent["form"] = form_number
1443
+ artifact_paths = []
1444
+ else:
1445
+ operation_result, external_schemas = prepare_inputs(
1446
+ app_root=args.app_root, intent=intent, plugin_type=args.plugin_type,
1447
+ artifact_paths=artifact_paths, run_result=run_result, operation_result=operation_result,
1448
+ external_schemas=[_read_json(path) for path in args.external_schemas or []])
1022
1449
  result = prepare_handoff(
1023
- app_root=args.app_root,
1024
- project_root=args.project_root,
1025
- plugin_type=args.plugin_type,
1026
- intent=intent,
1027
- artifact_paths=args.artifact_paths,
1028
- run_result=run_result,
1029
- operation_result=operation_result,
1030
- external_schemas=[_read_json(path) for path in args.external_schemas or []],
1450
+ app_root=args.app_root, project_root=args.project_root, plugin_type=args.plugin_type,
1451
+ intent=intent, artifact_paths=artifact_paths, run_result=run_result,
1452
+ operation_result=operation_result, runtime_metadata=runtime_metadata,
1453
+ external_schemas=([_read_json(path) for path in args.external_schemas or []]
1454
+ if runtime_metadata else external_schemas),
1031
1455
  )
1032
- print(json.dumps(result, ensure_ascii=False))
1033
- except (HandoffError, json.JSONDecodeError) as exc:
1034
- error_code = getattr(exc, "error_code", "PLUGIN_HANDOFF_UNRESOLVED")
1456
+ destination = (Path(args.output).resolve() if args.output else Path(args.app_root).resolve()
1457
+ / "_runtime" / "plugin-handoffs" / (result["handoff"]["className"] + ".json"))
1458
+ assert_handoff_replace(destination, result, args.replace_handoff)
1459
+ if args.prepare_source:
1460
+ from plugin_target_sdk import verify_target_sdk
1461
+ result['handoff']['targetSdk'] = verify_target_sdk(result['handoff']['projectRoot'],
1462
+ result['handoff']['codingEvidence'], intent.get('sdkUsage'))
1463
+ from plugin_source_gate import write_state, gate_path
1464
+ import os
1465
+ handoff = result["handoff"]
1466
+ if not handoff.get("className") or not handoff.get("sourcePath"):
1467
+ raise HandoffError("source preparation requires a resolved className and sourcePath")
1468
+ target = Path(handoff["sourcePath"]).resolve()
1469
+ if not target.is_relative_to(Path(handoff["projectRoot"]).resolve() / "src" / "main" / "java"):
1470
+ raise HandoffError("source preparation target is outside the verified Java module", "PLUGIN_SOURCE_INVALID")
1471
+ target.parent.mkdir(parents=True, exist_ok=True)
1472
+ write_state(handoff["projectRoot"], handoff["className"], "coding",
1473
+ os.environ.get("LINGEE_SESSION_ID") or os.environ.get("OPENCODE_SESSION_ID"), emit=False)
1474
+ handoff["sourceGate"] = {"state": "coding", "path": gate_path(handoff["projectRoot"], handoff["className"])}
1475
+ destination.parent.mkdir(parents=True, exist_ok=True)
1476
+ result["handoffPath"] = str(destination)
1477
+ destination.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
1478
+ print(json.dumps(handoff_response(result, full_output=args.full_output), ensure_ascii=False))
1479
+ except (HandoffError, json.JSONDecodeError, ValueError) as exc:
1480
+ error_code = getattr(exc, "error_code", getattr(exc, "code", "PLUGIN_HANDOFF_UNRESOLVED"))
1035
1481
  print(json.dumps({"status": False, "errorCode": error_code, "message": str(exc)}, ensure_ascii=False))
1036
1482
  raise SystemExit(2)
1037
1483