@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
@@ -4,6 +4,7 @@
4
4
  from __future__ import annotations
5
5
 
6
6
  import argparse
7
+ import hashlib
7
8
  import json
8
9
  import os
9
10
  import re
@@ -14,7 +15,7 @@ from pathlib import Path
14
15
  from typing import Any
15
16
 
16
17
  from appconfig_load import inspect_config
17
- from java_source import commentless, duplicate_methods
18
+ from java_source import commentless, duplicate_methods, method_bodies, method_signatures, stripped
18
19
  from plugin_source_gate import write_state
19
20
  from platform_api_contract import (
20
21
  base_class_issues,
@@ -27,6 +28,7 @@ from platform_api_contract import (
27
28
  unverified_member_issues,
28
29
  )
29
30
  from write_archive_json import write_archive
31
+ from plugin_corpus.jvm_classfile import parse_class
30
32
 
31
33
 
32
34
  PROJECT_DIR_RE = re.compile(
@@ -36,6 +38,77 @@ PROJECT_DIR_RE = re.compile(
36
38
  INCLUDE_RE = re.compile(r"['\"](?P<module>:[A-Za-z0-9_.:-]+)['\"]")
37
39
  JAVA_ERROR_RE = re.compile(r"^(?P<file>.*\.java):(?P<line>\d+):\s*(?:错误|error):\s*(?P<message>.*)$")
38
40
  DIAGNOSTIC_LIMIT = 30
41
+
42
+
43
+ def time_accessor_issues(source_text: str, time_fields: set[str]) -> list[dict[str, Any]]:
44
+ """Flag possible TimeProp reads without pretending to resolve Java receiver ownership."""
45
+ if not time_fields:
46
+ return []
47
+ source = commentless(source_text)
48
+ code = stripped(source_text)
49
+ constants: dict[str, set[str]] = {}
50
+ for match in re.finditer(r'\b(?:static\s+)?final\s+String\s+([A-Za-z_]\w*)\s*=\s*"([^"\n]+)"', source):
51
+ if code[match.start():match.start() + 5].strip():
52
+ constants.setdefault(match[1], set()).add(match[2])
53
+ receivers: dict[str, set[str]] = {}
54
+ for match in re.finditer(r'\b([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)\s+([A-Za-z_]\w*)\s*(?=[,;=:)])', code):
55
+ receivers.setdefault(match[2], set()).add(match[1].rsplit('.', 1)[-1])
56
+ issues = []
57
+ for match in re.finditer(r'(?P<receiver>[A-Za-z_]\w*)\.getDate\s*\(\s*(?:"(?P<literal>[^"\n]+)"|(?P<symbol>[A-Za-z_]\w*))\s*\)', source):
58
+ if not code[match.start():match.start() + len(match['receiver'])].strip():
59
+ continue
60
+ kinds = receivers.get(match['receiver'], set())
61
+ if kinds and 'DynamicObject' not in kinds:
62
+ continue
63
+ values = {match['literal']} if match['literal'] else constants.get(match['symbol'], set())
64
+ fields = sorted(values & time_fields)
65
+ if fields:
66
+ issues.append({"line": source.count("\n", 0, match.start()) + 1, "fieldKeys": fields,
67
+ "receiver": match['receiver'], "severity": "warning",
68
+ "message": "Possible TimeProp read via getDate; verify this receiver's field ownership and use integer seconds for a confirmed TimeProp. This scan does not resolve Java scopes or entity types."})
69
+ return issues
70
+
71
+
72
+ def time_fields_from_handoff(handoff: dict) -> set[str]:
73
+ types: dict[str, set[str]] = {}
74
+ for graph in [handoff.get("formGraph"), *(handoff.get("relatedForms") or [])]:
75
+ if not isinstance(graph, dict):
76
+ continue
77
+ for entity in graph.get("entities", []):
78
+ if not isinstance(entity, dict):
79
+ continue
80
+ for field in entity.get("fields", []):
81
+ if isinstance(field, dict) and isinstance(field.get("fieldKey"), str):
82
+ types.setdefault(field["fieldKey"], set()).add(str(field.get("fieldType") or ""))
83
+ return {key for key, kinds in types.items() if kinds and kinds <= {"TimeProp", "TimeField"}}
84
+
85
+
86
+ def operation_dispatch_warnings(source_text: str, operation_keys: list[str]) -> list[dict]:
87
+ code = stripped(source_text)
88
+ source = commentless(source_text)
89
+ issues = []
90
+ for match in re.finditer(r'\boperateMeta\s*\.\s*get\s*\(\s*"([^"\n]+)"\s*\)', source):
91
+ if code[match.start():match.start() + len("operateMeta")].strip():
92
+ issues.append({"line": source.count("\n", 0, match.start()) + 1, "severity": "warning",
93
+ "errorCode": "PLUGIN_OPERATION_MAP_KEY_UNVERIFIED", "mapKey": match[1],
94
+ "message": "The handoff does not establish this operateMeta Map key. Prefer OperationArgs.getOperationKey() in transaction callbacks or AbstractValidator.getOperateKey() in validators; otherwise supply exact SDK evidence."})
95
+ if len(set(operation_keys)) > 1:
96
+ for body in method_bodies(source_text):
97
+ if body["name"] in ("beginOperationTransaction", "endOperationTransaction") and not re.search(
98
+ r'\b(?:getOperationKey|getOperateKey)\s*\(', code[body["start"]:body["end"]]):
99
+ issues.append({"line": code.count("\n", 0, body["start"]) + 1, "severity": "warning",
100
+ "errorCode": "PLUGIN_OPERATION_SCOPE_UNVERIFIED", "method": body["name"],
101
+ "message": "Several operations share this plugin and this callback has no visible typed operation selector. Verify whether its effects apply to every registered operation or are selected by a verified helper."})
102
+ return issues
103
+
104
+
105
+ def compile_handoff(filename: str) -> dict:
106
+ prepared = json.loads(Path(filename).read_text(encoding="utf-8-sig"))
107
+ context = prepared.get("handoff") if prepared.get("status") is True else None
108
+ if not isinstance(context, dict) or not all(context.get(key) for key in
109
+ ("projectRoot", "appRoot", "sourcePath", "className", "type", "formNumber", "description")):
110
+ raise CompileError("handoff must contain successfully prepared coding context", "PLUGIN_SOURCE_INVALID")
111
+ return context
39
112
  # Scanned over the whole file, comments included: these three are markers of an unresolved
40
113
  # dependency, so one sitting in a comment is exactly the case to block.
41
114
  SOURCE_BLOCKERS = (
@@ -43,12 +116,10 @@ SOURCE_BLOCKERS = (
43
116
  (re.compile(r"\bTODO\b", re.IGNORECASE), "TODO placeholder"),
44
117
  (re.compile(r"\bFIXME\b", re.IGNORECASE), "FIXME placeholder"),
45
118
  )
46
- # Scanned over code only. These are ordinary Chinese business words, so when the response language
47
- # is Chinese they turn up in correct comments describing a status rather than standing in for
48
- # missing work, and reporting one stopped delivery as L3-BLOCK on a comment that was fine. A string
49
- # literal still counts.
119
+ # Scanned over code only. Explicit implementation placeholders remain blockers, including string
120
+ # literals. Ordinary business vocabulary such as 待确认 is valid in comments and user messages.
50
121
  CODE_BLOCKERS = (
51
- (re.compile(r"待确认|待替换|占位实现"), "unresolved placeholder text"),
122
+ (re.compile(r"待替换|占位实现"), "unresolved placeholder text"),
52
123
  )
53
124
 
54
125
 
@@ -89,6 +160,42 @@ def resolve_module(gradle_root: Path, project_root: Path) -> str:
89
160
  return matches[0]
90
161
 
91
162
 
163
+ def inspect_compile_environment(project_root: str) -> dict:
164
+ config = inspect_config(write_derived=False)
165
+ if config.get("status") != "ready":
166
+ return {"status": config.get("status", "unknown"), "message": config.get("message", "Compile environment unavailable")}
167
+ root = Path(str(config["GRADLE_ROOT"])).resolve()
168
+ try:
169
+ module = resolve_module(root, Path(project_root))
170
+ except CompileError as error:
171
+ return {"status": "unbound", "code": error.code, "message": str(error)}
172
+ sdk_paths = []
173
+ properties = root / "gradle.properties"
174
+ scheme = root / "config.gradle"
175
+ scheme_text = scheme.read_text(encoding="utf-8-sig") if scheme.is_file() else ""
176
+ property_text = properties.read_text(encoding="utf-8-sig") if properties.is_file() else ""
177
+ home_property = bool(re.search(r"^\s*systemProp\.cosmic_home\s*=", property_text, re.MULTILINE))
178
+ libs_property = bool(re.search(r"^\s*systemProp\.cosmic_libs_path\s*=", property_text, re.MULTILINE))
179
+ if property_text and "${cosmic_libs_path}/bos" in scheme_text and not (home_property and libs_property):
180
+ for line in property_text.splitlines():
181
+ match = re.match(r"\s*(systemProp\.cosmic_home|systemProp\.cosmic_libs_path)\s*=\s*(.+?)\s*$", line)
182
+ if match:
183
+ target = Path(match[2].strip('"')).expanduser()
184
+ target = target if target.is_absolute() else root / target
185
+ if match[1] == "systemProp.cosmic_home" and "${cosmic_home}/mservice-cosmic/lib" in scheme_text:
186
+ directory = target / "mservice-cosmic" / "lib" / "bos"
187
+ elif match[1] == "systemProp.cosmic_libs_path" and not os.environ.get("COSMIC_HOME"):
188
+ directory = target / "bos"
189
+ else:
190
+ continue
191
+ if directory.is_dir() and any(directory.glob("*.jar")):
192
+ sdk_paths.append({"path": str(directory.resolve()), "source": [str(properties), str(scheme)], "property": match[1]})
193
+ return {"status": "ready", "gradleRoot": str(root), "module": module,
194
+ "wrapperPath": str(root / ("gradlew.bat" if os.name == "nt" else "gradlew")),
195
+ "javaHome": config["JAVA_HOME"], "sdkDirectories": sdk_paths,
196
+ "sdkPathEvidence": "Matched one target Gradle property and config.gradle library expression; ambiguous or unavailable configurations remain unresolved, and Gradle was not executed"}
197
+
198
+
92
199
  def validate_source(source_path: Path, project_root: Path, class_name: str) -> list[dict[str, Any]]:
93
200
  java_root = (project_root / "src" / "main" / "java").resolve()
94
201
  source = source_path.resolve()
@@ -192,7 +299,8 @@ def parse_compiler_errors(output: str, target_source: Path | None = None) -> lis
192
299
  def compile_source(
193
300
  project_root: Path, source_path: Path, class_name: str, timeout_sec: int,
194
301
  *, app_root: Path, plugin_type: str, form_number: str,
195
- operation_keys: list[str], description: str,
302
+ operation_keys: list[str], description: str, module_runs: dict | None = None,
303
+ time_fields: set[str] | None = None,
196
304
  ) -> dict[str, Any]:
197
305
  # Before the source is read: this used to sit just before the Gradle call, so a description one
198
306
  # character too long was reported only after all the source gates had run.
@@ -208,138 +316,54 @@ def compile_source(
208
316
  "message": "source contains unresolved placeholders; resolve the dependency before compiling",
209
317
  "issues": issues,
210
318
  }
319
+ source_hash = hashlib.sha256(source_path.read_bytes()).hexdigest()
211
320
  source_text = source_path.read_text(encoding="utf-8-sig")
212
- # javac would reject a repeated signature, but this check also runs when appconfig sets
213
- # compile=false, which is how a class carrying two afterCreateNewData methods reached
214
- # registration with nothing having ever looked at it.
215
- duplicates = duplicate_methods(source_text)
216
- if duplicates:
217
- return {
218
- "status": False,
219
- "errorCode": "PLUGIN_SOURCE_DUPLICATE_METHOD",
220
- "phase": "source_validation",
221
- "message": (
222
- f"{len(duplicates)} method(s) are declared twice in {class_name}; merge the new "
223
- "logic into the existing declaration instead of adding a second one"
224
- ),
225
- "issues": duplicates,
226
- }
227
- # A remembered return type is caught here rather than by javac, so the diagnostic can name the
228
- # registered signature, and so the check still runs when compile=false. That configuration used
229
- # to let `DynamicObjectCollection objs = BusinessDataServiceHelper.load(...)` reach
230
- # registration with nothing having compiled it at all.
231
- mismatches = return_type_issues(source_text)
232
- if mismatches:
321
+ archive_path = app_root.resolve() / "archive.json"
322
+ if archive_path.is_file():
323
+ previous = json.loads(archive_path.read_text(encoding="utf-8-sig"))
324
+ for entry in previous.get("javas", []):
325
+ if (str(entry.get("className", "")).rsplit(".", 1)[-1] == class_name
326
+ and Path(str(entry.get("filePath") or "")).resolve() == source_path.resolve()):
327
+ write_archive(archive_path, javas=[{**entry, "compileStatus": "pending"}])
328
+ # Independent source checks share one response so a repair can address them together.
329
+ checks = [
330
+ ("PLUGIN_SOURCE_DUPLICATE_METHOD", duplicate_methods),
331
+ ("PLUGIN_SOURCE_RETURN_TYPE_MISMATCH", return_type_issues),
332
+ ("PLUGIN_SOURCE_BASE_CLASS_MISMATCH", base_class_issues),
333
+ ("PLUGIN_SOURCE_OVERRIDE_SIGNATURE_MISMATCH", override_signature_issues),
334
+ ("PLUGIN_SOURCE_QFILTER_STRING_OPERATOR", filter_operator_issues),
335
+ ("PLUGIN_SOURCE_NONEXISTENT_MEMBER", forbidden_member_issues),
336
+ ("PLUGIN_SOURCE_UNVERIFIED_API", unverified_import_issues),
337
+ ("PLUGIN_SOURCE_UNVERIFIED_API", unverified_member_issues),
338
+ ("PLUGIN_SOURCE_LIFECYCLE_MISUSE", scene_misuse_issues),
339
+ ]
340
+ findings = [{**issue, "errorCode": code} for code, check in checks for issue in check(source_text)]
341
+ warnings = [{**issue, "errorCode": "PLUGIN_SOURCE_FIELD_ACCESSOR_MISMATCH"}
342
+ for issue in time_accessor_issues(source_text, time_fields or set())]
343
+ if plugin_type == "operation":
344
+ warnings += operation_dispatch_warnings(source_text, operation_keys)
345
+ if findings:
233
346
  return {
234
- "status": False,
235
- "errorCode": "PLUGIN_SOURCE_RETURN_TYPE_MISMATCH",
236
- "phase": "source_validation",
237
- "message": (
238
- f"{len(mismatches)} platform call(s) in {class_name} are assigned to a type the "
239
- "registered signature does not return; use the return type named in each issue"
240
- ),
241
- "issues": mismatches,
242
- }
243
- # `"not in"` is byte-identical to QCP.not_in, so javac compiles it, the database runs it, and
244
- # the registration read-back never looks at source. This is the only place that can require the
245
- # constant, which is what makes a typo such as "notin" a compile error rather than wrong rows.
246
- # The `extends` declaration is checked against the type registry's base-import table before
247
- # anything else: a case-only misspelling of a registered base class is a remembered name,
248
- # not a platform class, so javac reports at best a bare cannot-find-symbol that names no
249
- # replacement — and with compile=false nothing else looks at it before registration.
250
- bases = base_class_issues(source_text)
251
- if bases:
252
- return {
253
- "status": False,
254
- "errorCode": "PLUGIN_SOURCE_BASE_CLASS_MISMATCH",
255
- "phase": "source_validation",
256
- "message": (
257
- f"{len(bases)} base-class declaration(s) in {class_name} name a class that is not "
258
- "a registered plugin base class; use the registered base import named in each issue"
259
- ),
260
- "issues": bases,
261
- }
262
- # An @Override whose parameter types none of the base-class chain's registered signatures
263
- # accept. The remembered parameter class is not a platform class, so javac reports at best a
264
- # bare cannot-find-symbol that names no registered signature, and compile=false skips javac
265
- # entirely — this is the only gate that names the signature to copy.
266
- overrides = override_signature_issues(source_text)
267
- if overrides:
268
- return {
269
- "status": False,
270
- "errorCode": "PLUGIN_SOURCE_OVERRIDE_SIGNATURE_MISMATCH",
271
- "phase": "source_validation",
272
- "message": (
273
- f"{len(overrides)} @Override method(s) in {class_name} declare parameters that "
274
- "none of the base class's registered signatures accept; copy the registered "
275
- "signature named in each issue"
276
- ),
277
- "issues": overrides,
278
- }
279
- operators = filter_operator_issues(source_text)
280
- if operators:
281
- return {
282
- "status": False,
283
- "errorCode": "PLUGIN_SOURCE_QFILTER_STRING_OPERATOR",
284
- "phase": "source_validation",
285
- "message": (
286
- f"{len(operators)} QFilter comparison operator(s) in {class_name} are written as "
287
- "string literals; use the QCP constant named in each issue"
288
- ),
289
- "issues": operators,
290
- }
291
- # javac reports this one too, as "cannot find symbol" without saying what the registered name
292
- # is. Naming it here is the smaller half of the reason; the larger is that with compile=false
293
- # javac never runs, and `getModel().getEntryCount()` then reaches registration as a class that
294
- # cannot load.
295
- missing = forbidden_member_issues(source_text)
296
- if missing:
297
- return {
298
- "status": False,
299
- "errorCode": "PLUGIN_SOURCE_NONEXISTENT_MEMBER",
300
- "phase": "source_validation",
301
- "message": (
302
- f"{len(missing)} call(s) in {class_name} name a platform member that does not "
303
- "exist; use the registered replacement named in each issue"
304
- ),
305
- "issues": missing,
306
- }
307
- # The closed vocabulary over the contract pages: a remembered import (a spelling that
308
- # case-folds onto one registered class) and a remembered member (a name near-missing one on
309
- # the receiver's chain) are both javac symbol errors at best and registration defects at
310
- # worst, and neither names the registered spelling to copy. This is the only gate that reads
311
- # the whole page corpus, so it also stands in for the members the small table above misses.
312
- remembered = unverified_import_issues(source_text) + unverified_member_issues(source_text)
313
- if remembered:
314
- return {
315
- "status": False,
316
- "errorCode": "PLUGIN_SOURCE_UNVERIFIED_API",
317
- "phase": "source_validation",
318
- "message": (
319
- f"{len(remembered)} platform reference(s) in {class_name} name a remembered "
320
- "spelling instead of the registered API; write the import or signature each "
321
- "issue names"
322
- ),
323
- "issues": remembered,
324
- }
325
- # The only gate here that javac cannot stand in for at all: every call it reports is valid Java
326
- # against a real signature, and the platform loads and runs the class. The defect is the method
327
- # it sits in, so the failure is silent — a listener that never fires, a write the binding throws
328
- # away — and arrives as "the plugin does nothing" long after registration reported success.
329
- misplaced = scene_misuse_issues(source_text)
330
- if misplaced:
331
- return {
332
- "status": False,
333
- "errorCode": "PLUGIN_SOURCE_LIFECYCLE_MISUSE",
334
- "phase": "source_validation",
335
- "message": (
336
- f"{len(misplaced)} call(s) in {class_name} sit in a lifecycle method where they "
337
- "cannot take effect; move each one as described in its issue"
338
- ),
339
- "issues": misplaced,
347
+ "status": False, "errorCode": findings[0]["errorCode"], "phase": "source_validation",
348
+ "message": f"{len(findings)} source issue(s); repair the independent findings together",
349
+ "errorCodes": list(dict.fromkeys(item["errorCode"] for item in findings)),
350
+ "issues": findings,
351
+ "sourceWarnings": warnings,
340
352
  }
353
+ build_context = None
341
354
  def close_out(module: str, elapsed: float | None, compile_skipped: bool) -> dict[str, Any]:
342
355
  archive_path = app_root.resolve() / "archive.json"
356
+ if hashlib.sha256(source_path.read_bytes()).hexdigest() != source_hash:
357
+ raise CompileError("Source changed during compilation; validate and compile the current file", "PLUGIN_SOURCE_CHANGED")
358
+ package = re.search(r"\bpackage\s+([\w.]+)\s*;", stripped(source_text))
359
+ fqcn = (package[1] + "." if package else "") + class_name
360
+ compiled_class = project_root / "build" / "classes" / "java" / "main" / Path(*fqcn.split(".")).with_suffix(".class")
361
+ compiled_artifact = None
362
+ if not compile_skipped and compiled_class.is_file():
363
+ content = compiled_class.read_bytes()
364
+ if parse_class(content)["name"].replace("/", ".") == fqcn:
365
+ compiled_artifact = {"path": str(compiled_class.resolve()), "fqcn": fqcn,
366
+ "sha256": hashlib.sha256(content).hexdigest(), "outputLayout": "gradle-default"}
343
367
  try:
344
368
  write_archive(archive_path, javas=[{
345
369
  "className": class_name,
@@ -349,13 +373,15 @@ def compile_source(
349
373
  "operationKey": ",".join(
350
374
  dict.fromkeys(key.strip() for key in operation_keys if key.strip())),
351
375
  "filePath": str(source_path.resolve()),
376
+ "sourceHash": source_hash,
377
+ "compileStatus": "skipped-by-config" if compile_skipped else "passed",
352
378
  }])
353
379
  except Exception as exc:
354
380
  raise CompileError(
355
381
  f"cannot archive compiled plugin: {exc}", "PLUGIN_ARCHIVE_FAILED", "archive",
356
382
  ) from exc
357
383
  try:
358
- write_state(str(project_root.resolve()), class_name, "source_ready")
384
+ write_state(str(project_root.resolve()), class_name, "source_ready", emit=False)
359
385
  except Exception:
360
386
  # The archive entry registration reads is already written. Letting an unwritable .temp
361
387
  # raise here reported failure for a class register_plugins.py could then register.
@@ -369,7 +395,22 @@ def compile_source(
369
395
  "sourcePath": str(source_path.resolve()),
370
396
  "archivePath": str(archive_path),
371
397
  "compileSkipped": compile_skipped,
398
+ "compiledArtifact": compiled_artifact,
399
+ "fieldAccessorCheck": "metadata-hints" if time_fields is not None else "not-run-no-handoff",
400
+ "sourceWarnings": warnings,
401
+ "sourceReview": {"sourceHash": source_hash, "registeredOperationKeys": operation_keys,
402
+ "declaredOuterMethods": method_signatures(source_text),
403
+ "businessCoverage": "not-established-by-compilation"},
404
+ "loadingVerification": {
405
+ "runtimeVerified": False,
406
+ "status": "not-run",
407
+ "sourceHash": source_hash,
408
+ "compiledArtifactAvailable": compiled_artifact is not None,
409
+ "nextAction": "Verify the configured compile/deploy class artifacts and request-scoped runtime loading evidence before business tests; compilation alone does not activate code.",
410
+ },
372
411
  }
412
+ if build_context is not None:
413
+ result["buildContext"] = build_context
373
414
  if elapsed is not None:
374
415
  result["elapsedSec"] = elapsed
375
416
  if compile_skipped:
@@ -388,7 +429,7 @@ def compile_source(
388
429
  ]
389
430
  return result
390
431
 
391
- config = inspect_config(write_derived=True)
432
+ config = inspect_config(write_derived=False)
392
433
  config_status = config.get("status")
393
434
  if config_status == "disabled":
394
435
  # appconfig says compilation is not needed here. Treating that as a hard failure
@@ -401,12 +442,28 @@ def compile_source(
401
442
  module = resolve_module(gradle_root, project_root)
402
443
  wrapper = gradle_root / ("gradlew.bat" if os.name == "nt" else "gradlew")
403
444
  task = f"{module}:compileJava" if module else "compileJava"
445
+ build_context = {"gradleRoot": str(gradle_root.resolve()), "javaProjectRoot": str(project_root.resolve()),
446
+ "wrapperPath": str(wrapper.resolve()), "javaHome": str(Path(config["JAVA_HOME"]).resolve()),
447
+ "compileTask": task, "testTaskCandidate": f"{module}:test" if module else "test",
448
+ "testTaskEvidence": "Java task convention only; task availability and test results are not verified by this compile receipt"}
404
449
  env = os.environ.copy()
405
450
  env["JAVA_HOME"] = str(config["JAVA_HOME"])
406
451
  started = time.monotonic()
452
+ # Reuse a build only within one batch and only while its source/build inputs are unchanged.
453
+ inputs = sorted((project_root / "src").rglob("*.java"))
454
+ inputs += sorted((project_root / "src").rglob("*.kt"))
455
+ inputs += [project_root / name for name in ("build.gradle", "build.gradle.kts")]
456
+ inputs += [gradle_root / name for name in ("settings.gradle", "settings.gradle.kts", "build.gradle", "build.gradle.kts", "gradle.properties")]
457
+ fingerprint = hashlib.sha256()
458
+ for file in inputs:
459
+ fingerprint.update(str(file.resolve()).encode())
460
+ if file.is_file():
461
+ fingerprint.update(file.read_bytes())
462
+ run_key = (str(gradle_root.resolve()), task, env["JAVA_HOME"], fingerprint.hexdigest())
463
+ reused = module_runs is not None and run_key in module_runs
407
464
  try:
408
- completed = subprocess.run(
409
- [str(wrapper), task, "--rerun-tasks"],
465
+ completed = module_runs[run_key] if reused else subprocess.run(
466
+ [str(wrapper), task],
410
467
  cwd=gradle_root,
411
468
  env=env,
412
469
  text=True,
@@ -423,13 +480,19 @@ def compile_source(
423
480
  "module": module or ":",
424
481
  "message": f"narrow compilation exceeded {timeout_sec} seconds",
425
482
  "diagnostics": parse_compiler_errors(str(exc.stdout or ""), source_path),
483
+ "sourceWarnings": warnings,
426
484
  }
427
485
  elapsed = round(time.monotonic() - started, 2)
486
+ if module_runs is not None:
487
+ module_runs[run_key] = completed
428
488
  if completed.returncode != 0:
489
+ log_path = project_root / ".temp" / "plugin-compile" / (hashlib.sha256(completed.stdout.encode()).hexdigest() + ".log")
490
+ log_path.parent.mkdir(parents=True, exist_ok=True)
491
+ log_path.write_text(completed.stdout, encoding="utf-8")
429
492
  mine, others, dropped = collect_compiler_errors(
430
493
  completed.stdout, str(source_path.resolve()))
431
- diagnostics = mine[:DIAGNOSTIC_LIMIT]
432
- diagnostics += others[: DIAGNOSTIC_LIMIT - len(diagnostics)]
494
+ diagnostics = mine[:5]
495
+ diagnostics += others[: max(0, 5 - len(diagnostics))]
433
496
  # Counted before truncation: reporting the shown subset understated a class with more than
434
497
  # DIAGNOSTIC_LIMIT errors, and dropped the "plus N elsewhere" clause exactly when the module
435
498
  # carried the most unrelated noise.
@@ -443,8 +506,7 @@ def compile_source(
443
506
  # The module compiles as a unit, so a pre-existing failure elsewhere fails this run too.
444
507
  # Saying so keeps the repair from being aimed at a file this change never touched.
445
508
  message += (
446
- f", plus {elsewhere} in other source of module {module or ':'} that this change did "
447
- "not touch"
509
+ f", plus {elsewhere} in other source of the same module {module or ':'}"
448
510
  )
449
511
  if len(diagnostics) < len(mine) + elsewhere:
450
512
  message += f" ({len(diagnostics)} shown)"
@@ -455,31 +517,92 @@ def compile_source(
455
517
  "module": module or ":",
456
518
  "message": message,
457
519
  "diagnostics": diagnostics,
520
+ "diagnosticCount": len(mine) + elsewhere,
521
+ "logPath": str(log_path.resolve()),
522
+ "reusedModuleBuild": reused,
523
+ "recovery": "Repair the shared module errors before retrying. Do not archive or register failed sources.",
458
524
  "elapsedSec": elapsed,
525
+ "sourceWarnings": warnings,
459
526
  }
460
527
  return close_out(module=module or ":", elapsed=elapsed, compile_skipped=False)
461
528
 
462
529
 
463
530
  def main() -> int:
464
531
  parser = argparse.ArgumentParser(description="Narrowly compile one generated Cosmic Java plugin")
465
- parser.add_argument("--project-root", required=True, help="Existing Java module root")
466
- parser.add_argument("--app-root", required=True)
467
- parser.add_argument("--source-path", required=True)
468
- parser.add_argument("--class-name", required=True)
469
- parser.add_argument("--plugin-type", required=True)
470
- parser.add_argument("--form-number", required=True)
532
+ parser.add_argument("--project-root", help="Existing Java module root")
533
+ parser.add_argument("--app-root")
534
+ parser.add_argument("--handoff", help="Prepared context file; reuses its verified identities and short description")
535
+ parser.add_argument("--sources-file", help="JSON array of sourcePath/className/type/formNumber/operationKeys/description for one module")
536
+ parser.add_argument("--source-path")
537
+ parser.add_argument("--class-name")
538
+ parser.add_argument("--plugin-type")
539
+ parser.add_argument("--form-number")
471
540
  parser.add_argument("--operation-key", action="append", default=[])
472
- parser.add_argument("--description", required=True, help="Plugin purpose summary, 1-20 characters")
541
+ parser.add_argument("--description", help="Plugin purpose summary, 1-20 characters")
473
542
  parser.add_argument("--timeout-sec", type=int, default=300)
474
543
  args = parser.parse_args()
475
544
  try:
476
- result = compile_source(
477
- Path(args.project_root), Path(args.source_path), args.class_name,
478
- max(1, args.timeout_sec),
479
- app_root=Path(args.app_root), plugin_type=args.plugin_type,
480
- form_number=args.form_number, operation_keys=args.operation_key,
481
- description=args.description,
482
- )
545
+ time_fields = None
546
+ if args.handoff:
547
+ if any([args.project_root, args.app_root, args.sources_file, args.source_path, args.class_name,
548
+ args.plugin_type, args.form_number, args.description, args.operation_key]):
549
+ raise CompileError("Use --handoff without retyping source/context arguments", "PLUGIN_SOURCE_INVALID")
550
+ context = compile_handoff(args.handoff)
551
+ args.project_root, args.app_root = context["projectRoot"], context["appRoot"]
552
+ args.source_path, args.class_name = context["sourcePath"], context["className"]
553
+ args.plugin_type, args.form_number = context["type"], context["formNumber"]
554
+ args.operation_key, args.description = context.get("operationKeys", []), context["description"]
555
+ time_fields = time_fields_from_handoff(context)
556
+ if not args.project_root or not args.app_root:
557
+ raise CompileError("Supply project/app roots or --handoff", "PLUGIN_SOURCE_INVALID")
558
+ if args.sources_file:
559
+ if any([args.source_path, args.class_name, args.plugin_type, args.form_number, args.description, args.operation_key]):
560
+ raise CompileError("Use either --sources-file or single-source arguments", "PLUGIN_BATCH_INPUT_INVALID")
561
+ entries = json.loads(Path(args.sources_file).read_text(encoding="utf-8-sig"))
562
+ if not isinstance(entries, list) or not entries or any(
563
+ not isinstance(item, dict) or (not (isinstance(item.get("handoffPath"), str) and item["handoffPath"].strip()) and (not all(isinstance(item.get(key), str) and item[key].strip()
564
+ for key in ("sourcePath", "className", "type", "description"))
565
+ or not isinstance(item.get("formNumber"), str)
566
+ or not isinstance(item.get("operationKeys", []), list)
567
+ or any(not isinstance(key, str) for key in item.get("operationKeys", []))))
568
+ for item in entries
569
+ ):
570
+ raise CompileError("Batch requires nonempty source records", "PLUGIN_BATCH_INPUT_INVALID")
571
+ module_runs = {}
572
+ results = []
573
+ reported_logs = set()
574
+ for item in entries:
575
+ context = item
576
+ try:
577
+ context = compile_handoff(item["handoffPath"]) if item.get("handoffPath") else item
578
+ if item.get("handoffPath") and (
579
+ Path(context["projectRoot"]).resolve() != Path(args.project_root).resolve()
580
+ or Path(context["appRoot"]).resolve() != Path(args.app_root).resolve()):
581
+ raise CompileError("Batch handoff belongs to a different project or app", "PLUGIN_BATCH_INPUT_INVALID")
582
+ outcome = compile_source(
583
+ Path(args.project_root), Path(context["sourcePath"]), context["className"], max(1, args.timeout_sec),
584
+ app_root=Path(args.app_root), plugin_type=context["type"], form_number=context["formNumber"],
585
+ operation_keys=context.get("operationKeys", []), description=context["description"], module_runs=module_runs,
586
+ time_fields=time_fields_from_handoff(context) if item.get("handoffPath") else None,
587
+ )
588
+ except CompileError as exc:
589
+ outcome = {"status": False, "errorCode": exc.code, "phase": exc.phase, "message": str(exc)}
590
+ if outcome.get("logPath") in reported_logs:
591
+ outcome.pop("diagnostics", None)
592
+ if outcome.get("logPath"):
593
+ reported_logs.add(outcome["logPath"])
594
+ results.append({"className": context.get("className"), **outcome})
595
+ result = {"status": all(item.get("status") is True for item in results), "results": results}
596
+ else:
597
+ if not all([args.source_path, args.class_name, args.plugin_type, args.form_number, args.description]):
598
+ raise CompileError("Supply single-source arguments or --sources-file", "PLUGIN_SOURCE_INVALID")
599
+ result = compile_source(
600
+ Path(args.project_root), Path(args.source_path), args.class_name,
601
+ max(1, args.timeout_sec),
602
+ app_root=Path(args.app_root), plugin_type=args.plugin_type,
603
+ form_number=args.form_number, operation_keys=args.operation_key,
604
+ description=args.description, time_fields=time_fields,
605
+ )
483
606
  print(json.dumps(result, ensure_ascii=False))
484
607
  return 0 if result.get("status") is True else 2
485
608
  except CompileError as exc:
@@ -0,0 +1,40 @@
1
+ """Bind the installed command source and interpreter, without credentials."""
2
+ from functools import lru_cache
3
+ import hashlib
4
+ import json
5
+ from pathlib import Path
6
+ import sys
7
+
8
+
9
+ @lru_cache(maxsize=1)
10
+ def execution_binding():
11
+ root = Path(__file__).resolve().parents[1]
12
+ digest = hashlib.sha256((root / "SKILL.md").read_bytes())
13
+ for directory in (root / "scripts", root / "skills", root / "schemas"):
14
+ for source in sorted(directory.rglob("*")):
15
+ if not source.is_file() or source.suffix not in {".py", ".json"} or "test" in source.parts:
16
+ continue
17
+ digest.update(source.relative_to(root).as_posix().encode())
18
+ digest.update(source.read_bytes())
19
+ return {"rootDir": str(root), "revision": digest.hexdigest(),
20
+ "python": str(Path(sys.executable).resolve()),
21
+ "cosmicApi": str(root / "skills" / "cosmic-meta-api" / "scripts" / "cosmic_api.py")}
22
+
23
+
24
+ def validate_execution_binding(app_root):
25
+ context = Path(app_root) / "_runtime" / "context.json"
26
+ if not context.is_file():
27
+ return
28
+ stored = json.loads(context.read_text(encoding="utf-8-sig"))
29
+ current = execution_binding()
30
+ if stored.get("environment"):
31
+ from kd_env_loader import load_environment_identity
32
+ if stored["environment"] != load_environment_identity():
33
+ raise ValueError("ENVIRONMENT_BINDING_MISMATCH: bootstrap the selected application in the current environment first")
34
+ binding = stored.get("executionBinding") or {}
35
+ source = binding.get("rootDir") or stored.get("rootDir")
36
+ if source and Path(source).resolve() != Path(current["rootDir"]):
37
+ raise ValueError("SKILL_SOURCE_MISMATCH: prior source " + str(source)
38
+ + "; rebind with the loaded installed source " + current["rootDir"])
39
+ if binding and binding != current:
40
+ raise ValueError("SKILL_REVISION_MISMATCH: bootstrap the selected installed source again before execution")