@shundoo-ai/dsh-cosmic 1.0.19 → 1.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (245) hide show
  1. package/cordis.patch.yml +5 -5
  2. package/package.json +1 -1
  3. package/skills/cosmic-build/CHANGELOG.md +4 -0
  4. package/skills/cosmic-build/LOCAL-PATCHES.md +91 -40
  5. package/skills/cosmic-build/SKILL.md +46 -13
  6. package/skills/cosmic-build/assets/platform-report-filters/TypedReportFilterFields.java +169 -0
  7. package/skills/cosmic-build/assets/platform-report-filters/platform.patch +98 -0
  8. package/skills/cosmic-build/assets/report-algo-benchmark/ReportAlgoBenchmark.java +48 -0
  9. package/skills/cosmic-build/assets/report-algo-benchmark/ReportBenchmarkLogFactory.java +7 -0
  10. package/skills/cosmic-build/examples/java-rules/CosmicEntryQuery.java +26 -0
  11. package/skills/cosmic-build/examples/java-rules/CosmicReportAggregation.java +35 -0
  12. package/skills/cosmic-build/examples/java-rules/CosmicReportFilters.java +77 -0
  13. package/skills/cosmic-build/examples/java-rules/CosmicTimeAdapter.java +25 -0
  14. package/skills/cosmic-build/examples/java-rules/CosmicTimeAdapterCheck.java +26 -0
  15. package/skills/cosmic-build/knowledge/cosmic/platform/api/algo/GroupbyDataSet.md +47 -0
  16. package/skills/cosmic-build/knowledge/cosmic/platform/api/entity/report/FilterInfo.md +12 -12
  17. package/skills/cosmic-build/knowledge/cosmic/platform/api/entity/report/FilterItemInfo.md +2 -0
  18. package/skills/cosmic-build/knowledge/cosmic/platform/form-plugin.md +2 -4
  19. package/skills/cosmic-build/knowledge/cosmic/platform/java-type-contracts.md +11 -2
  20. package/skills/cosmic-build/knowledge/cosmic/platform/list-plugin.md +18 -14
  21. package/skills/cosmic-build/knowledge/cosmic/platform/operation-plugin.md +4 -4
  22. package/skills/cosmic-build/knowledge/cosmic/platform/reportlistdata-plugin.md +17 -9
  23. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/operation/cross-entity-aggregate-writeback.md +2 -0
  24. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/operation/pre-post-operation.md +15 -7
  25. package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-algo.md +24 -68
  26. package/skills/cosmic-build/knowledge/cosmic/platform/workflow-plugin.md +2 -7
  27. package/skills/cosmic-build/references/application-context.md +35 -1
  28. package/skills/cosmic-build/references/common-reference.md +1 -1
  29. package/skills/cosmic-build/references/compile-guide.md +55 -68
  30. package/skills/cosmic-build/references/delivery.md +59 -7
  31. package/skills/cosmic-build/references/execution-contract.md +38 -0
  32. package/skills/cosmic-build/references/form-discovery.md +7 -1
  33. package/skills/cosmic-build/references/java-plugin-delivery.md +32 -162
  34. package/skills/cosmic-build/references/operation-dispatch.md +17 -0
  35. package/skills/cosmic-build/references/plugin-handoff-input.md +131 -0
  36. package/skills/cosmic-build/references/plugin-runtime-routing.md +1 -1
  37. package/skills/cosmic-build/references/report-filter-contract.md +78 -0
  38. package/skills/cosmic-build/references/sdk-inspection.md +23 -0
  39. package/skills/cosmic-build/schemas/data-model-authoring.schema.json +2 -3
  40. package/skills/cosmic-build/scripts/_http_client.py +55 -55
  41. package/skills/cosmic-build/scripts/appconfig_load.py +2 -2
  42. package/skills/cosmic-build/scripts/apply_report_filter_extension.py +39 -0
  43. package/skills/cosmic-build/scripts/bootstrap_cosmic_context.py +37 -2
  44. package/skills/cosmic-build/scripts/compile_plugin_source.py +279 -156
  45. package/skills/cosmic-build/scripts/execution_binding.py +40 -0
  46. package/skills/cosmic-build/scripts/inspect_platform_sdk.py +169 -0
  47. package/skills/cosmic-build/scripts/kd_api.py +23 -22
  48. package/skills/cosmic-build/scripts/kd_auth.py +1 -1
  49. package/skills/cosmic-build/scripts/kd_env_loader.py +19 -10
  50. package/skills/cosmic-build/scripts/kd_token_cache.py +2 -2
  51. package/skills/cosmic-build/scripts/kingscript_sdk_sync.py +350 -0
  52. package/skills/cosmic-build/scripts/kingscript_source_gate.py +11 -8
  53. package/skills/cosmic-build/scripts/platform_api_contract.py +246 -11
  54. package/skills/cosmic-build/scripts/plugin_corpus/build_index.py +403 -0
  55. package/skills/cosmic-build/scripts/plugin_corpus/jvm_classfile.py +281 -0
  56. package/skills/cosmic-build/scripts/plugin_corpus/measure_api_usage.py +446 -0
  57. package/skills/cosmic-build/scripts/plugin_source_gate.py +48 -7
  58. package/skills/cosmic-build/scripts/plugin_target_sdk.py +45 -0
  59. package/skills/cosmic-build/scripts/prepare_plugin_handoff.py +493 -47
  60. package/skills/cosmic-build/scripts/preview_url.py +35 -6
  61. package/skills/cosmic-build/scripts/query_plugin_handoff.py +250 -0
  62. package/skills/cosmic-build/scripts/register_plugins.py +28 -2
  63. package/skills/cosmic-build/scripts/render_output_cards.py +42 -9
  64. package/skills/cosmic-build/scripts/report_algo_benchmark.py +46 -0
  65. package/skills/cosmic-build/scripts/run.py +2 -2
  66. package/skills/cosmic-build/scripts/sync_generated_locale_assets.py +43 -0
  67. package/skills/cosmic-build/scripts/test/fixtures/ReportSdkCheck.java +64 -0
  68. package/skills/cosmic-build/scripts/test/fixtures/java_context_booking_native.json +541 -0
  69. package/skills/cosmic-build/scripts/test/test_add_rule_business_contract.py +85 -14
  70. package/skills/cosmic-build/scripts/test/test_batch5_drift_governance.py +12 -3
  71. package/skills/cosmic-build/scripts/test/test_build_meta_order.py +0 -16
  72. package/skills/cosmic-build/scripts/test/test_cli_continuation.py +87 -0
  73. package/skills/cosmic-build/scripts/test/test_compile_plugin_source.py +224 -3
  74. package/skills/cosmic-build/scripts/test/test_control_authoring.py +123 -4
  75. package/skills/cosmic-build/scripts/test/test_cosmic_sdk_examples.py +141 -0
  76. package/skills/cosmic-build/scripts/test/test_data_model_contract.py +30 -5
  77. package/skills/cosmic-build/scripts/test/test_delivery_stage_boundary.py +2 -10
  78. package/skills/cosmic-build/scripts/test/test_dym_sync_background.py +11 -0
  79. package/skills/cosmic-build/scripts/test/test_dynamic_form_metadata_only_contract.py +59 -1
  80. package/skills/cosmic-build/scripts/test/test_guide_skeleton_contract.py +87 -43
  81. package/skills/cosmic-build/scripts/test/test_inspect_platform_sdk.py +71 -0
  82. package/skills/cosmic-build/scripts/test/test_kd_api.py +67 -19
  83. package/skills/cosmic-build/scripts/test/test_kingscript_sdk_sync.py +221 -0
  84. package/skills/cosmic-build/scripts/test/test_kingscript_source_gate.py +10 -0
  85. package/skills/cosmic-build/scripts/test/test_list_configuration_closure.py +18 -0
  86. package/skills/cosmic-build/scripts/test/test_long_session_regressions.py +116 -0
  87. package/skills/cosmic-build/scripts/test/test_mcp_cli.py +524 -0
  88. package/skills/cosmic-build/scripts/test/test_modeling_input_recovery.py +141 -0
  89. package/skills/cosmic-build/scripts/test/test_open_world_cli_catalog.py +36 -2
  90. package/skills/cosmic-build/scripts/test/test_platform_api_contract.py +161 -1
  91. package/skills/cosmic-build/scripts/test/test_plugin_business_contract.py +91 -0
  92. package/skills/cosmic-build/scripts/test/test_plugin_corpus.py +195 -0
  93. package/skills/cosmic-build/scripts/test/test_plugin_handoff.py +346 -1
  94. package/skills/cosmic-build/scripts/test/test_plugin_target_sdk.py +70 -0
  95. package/skills/cosmic-build/scripts/test/test_preview_url.py +44 -1
  96. package/skills/cosmic-build/scripts/test/test_property_batch.py +22 -0
  97. package/skills/cosmic-build/scripts/test/test_property_input_discovery.py +13 -11
  98. package/skills/cosmic-build/scripts/test/test_query_plugin_handoff.py +160 -0
  99. package/skills/cosmic-build/scripts/test/test_register_plugins.py +31 -1
  100. package/skills/cosmic-build/scripts/test/test_render_output_cards.py +50 -4
  101. package/skills/cosmic-build/scripts/test/test_report_business_contract.py +27 -0
  102. package/skills/cosmic-build/scripts/test/test_report_doc_contract_sync.py +5 -4
  103. package/skills/cosmic-build/scripts/test/test_report_sdk_optimization.py +36 -0
  104. package/skills/cosmic-build/scripts/test/test_rule_batch_evidence.py +1 -1
  105. package/skills/cosmic-build/scripts/test/test_rule_domain_contract.py +20 -0
  106. package/skills/cosmic-build/scripts/test/test_rule_function_formula.py +16 -6
  107. package/skills/cosmic-build/scripts/test/test_skill_entry_contract.py +1 -0
  108. package/skills/cosmic-build/scripts/test/test_test_environment.py +45 -0
  109. package/skills/cosmic-build/scripts/test/test_verify_plugin_loading.py +79 -0
  110. package/skills/cosmic-build/scripts/test_environment.py +49 -0
  111. package/skills/cosmic-build/scripts/verify_plugin_loading.py +71 -0
  112. package/skills/cosmic-build/skills/cosmic-coding-impl/SKILL.md +86 -162
  113. package/skills/cosmic-build/skills/cosmic-coding-script/ATTRIBUTION.md +1 -1
  114. package/skills/cosmic-build/skills/cosmic-coding-script/SKILL.md +45 -14
  115. package/skills/cosmic-build/skills/cosmic-coding-script/references/kwc-controller-authoring.md +6 -0
  116. package/skills/cosmic-build/skills/cosmic-coding-script/references/sdk/README.md +29 -19
  117. package/skills/cosmic-build/skills/cosmic-coding-script/references/sdk/docs//350/204/232/346/234/254SDK.md +3 -4
  118. package/skills/cosmic-build/skills/cosmic-coding-script/references/sdk/indexes/package-index.md +1 -1
  119. package/skills/cosmic-build/skills/cosmic-coding-script/references/sdk/strategy.md +27 -18
  120. package/skills/cosmic-build/skills/cosmic-meta-api/SKILL.md +11 -17
  121. package/skills/cosmic-build/skills/cosmic-meta-api/apis/ai_meta.md +1 -1
  122. package/skills/cosmic-build/skills/cosmic-meta-api/apis/app_menu.md +1 -1
  123. package/skills/cosmic-build/skills/cosmic-meta-api/apis/meta_log.md +1 -1
  124. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/control-authoring.json +45 -4
  125. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/form-control-traits.json +1 -1
  126. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/layout-ui.json +18 -0
  127. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/menu-publish.json +8 -2
  128. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/property-batch.json +7 -0
  129. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/property-input-index.json +3 -3
  130. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/report-write-contracts.json +1 -0
  131. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/rule-domain.json +37 -5
  132. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/rule-event-contract.json +24 -3
  133. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/run-build-meta.json +61 -12
  134. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/dropdown-menu.schema.json +69 -0
  135. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/report-write.schema.json +2 -1
  136. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/rule-set.schema.json +1 -0
  137. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/rule-write.schema.json +14 -7
  138. package/skills/cosmic-build/skills/cosmic-meta-api/docs/api-summary.md +2 -2
  139. package/skills/cosmic-build/skills/cosmic-meta-api/modules/app-action/SKILL.md +6 -3
  140. package/skills/cosmic-build/skills/cosmic-meta-api/modules/develop/SKILL.md +1 -2
  141. package/skills/cosmic-build/skills/cosmic-meta-api/modules/export/SKILL.md +5 -6
  142. package/skills/cosmic-build/skills/cosmic-meta-api/modules/init/SKILL.md +13 -36
  143. package/skills/cosmic-build/skills/cosmic-meta-api/modules/pdm/SKILL.md +8 -9
  144. package/skills/cosmic-build/skills/cosmic-meta-api/modules/pdm/scripts/config.json.example +5 -15
  145. package/skills/cosmic-build/skills/cosmic-meta-api/modules/reset/SKILL.md +6 -6
  146. package/skills/cosmic-build/skills/cosmic-meta-api/modules/usage/SKILL.md +20 -148
  147. package/skills/cosmic-build/skills/cosmic-meta-api/modules/verify/SKILL.md +1 -1
  148. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/build_receipts.py +171 -0
  149. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_apps.py +25 -6
  150. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_controls.py +55 -21
  151. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_fields.py +10 -3
  152. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_formactions.py +63 -6
  153. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_forms.py +59 -2
  154. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_listui.py +8 -0
  155. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_metaops.py +36 -1
  156. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_plugins.py +115 -90
  157. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_properties.py +666 -4
  158. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_rules.py +1415 -87
  159. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/commands.py +6 -1
  160. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/config.py +28 -15
  161. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/execution_errors.py +40 -0
  162. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/formatters.py +32 -5
  163. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/property_policy_contract.py +6 -0
  164. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/report_ops.py +113 -17
  165. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/reset.py +3 -3
  166. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/rule_contract.py +107 -26
  167. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/build_meta_order.py +50 -49
  168. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/cosmic_api.py +856 -150
  169. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/field_decorator.py +47 -14
  170. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_build_receipt_resume.py +374 -0
  171. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_cli_failure_contract.py +24 -0
  172. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_creation_identity_boundary.py +4 -2
  173. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_dropdown_menu_contract.py +382 -0
  174. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_existing_form_ui.py +0 -25
  175. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_field_decorator.py +50 -6
  176. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_functional_group_resolution.py +83 -16
  177. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_identity_template_resolution.py +103 -0
  178. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_plugin_contract.py +28 -2
  179. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_readback_derivation.py +22 -0
  180. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_typed_filters.py +65 -0
  181. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_rule_write_closure.py +1041 -15
  182. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_batch_post_build_recovery.py +65 -0
  183. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_batch_verification_evidence.py +17 -0
  184. package/skills/cosmic-build/skills/cosmic-meta-knowledge/generation/api-param-checklist.md +1 -1
  185. package/skills/cosmic-build/skills/cosmic-meta-knowledge/generation/rule-modeling-guide.md +7 -4
  186. package/skills/cosmic-build/skills/cosmic-meta-modeling/SKILL.md +61 -13
  187. package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/app-menu/SKILL.md +8 -0
  188. package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/formtemplate/SKILL.md +5 -0
  189. package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/formtemplate/dynamic-form-artifact-schema.md +5 -0
  190. package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/SKILL.md +21 -9
  191. package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-entity-graphs.md +1 -1
  192. package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-storage-constraints.md +5 -3
  193. package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-ui-composition.md +6 -3
  194. package/skills/cosmic-build/skills/cosmic-meta-modeling/references/creation-recovery.md +13 -0
  195. package/skills/cosmic-build/skills/cosmic-meta-modeling/references/parse-core.md +3 -0
  196. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/data_model_contract.py +6 -1
  197. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/data_model_emit.py +135 -87
  198. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/field_type_registry.py +11 -1
  199. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/isv_resolver.py +1 -3
  200. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/report_validator.py +29 -0
  201. package/skills/cosmic-build/skills/cosmic-meta-modifier/SKILL.md +4 -2
  202. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/context/SKILL.md +2 -2
  203. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/execute/SKILL.md +1 -1
  204. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/operation/SKILL.md +5 -4
  205. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/operation/operation-write.md +6 -2
  206. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/operation/validation.md +2 -0
  207. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/property/SKILL.md +20 -5
  208. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/property/complex-apis.md +1 -1
  209. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/property/policy-grammar.md +4 -1
  210. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/report-op/SKILL.md +9 -0
  211. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/SKILL.md +61 -4
  212. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/raw-rule-authoring.md +3 -1
  213. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/rule-actions.md +2 -2
  214. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/rule-reference.md +6 -6
  215. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/state.md +1 -1
  216. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/unit/SKILL.md +6 -3
  217. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/verify/usage-guide.md +4 -4
  218. package/skills/cosmic-build/skills/cosmic-meta-modifier/references/list-filter-modification.md +4 -1
  219. package/skills/cosmic-build/skills/cosmic-meta-ui/SKILL.md +9 -1
  220. package/skills/cosmic-build/skills/cosmic-meta-ui/modules/button/SKILL.md +3 -0
  221. package/skills/cosmic-build/skills/mcp-server/SKILL.md +210 -0
  222. package/skills/cosmic-build/skills/mcp-server/scripts/mcp_cli.py +431 -0
  223. package/skills/cosmic-build/skills/openapi-gen/SKILL.md +3 -3
  224. package/skills/cosmic-build/skills/openapi-gen/scripts/api/gen_runner.py +2 -2
  225. package/skills/cosmic-dev-best-practices/references/bill-dev-convention.md +19 -9
  226. package/skills/cosmic-dev-best-practices/references/cosmic-meta-api.md +14 -11
  227. package/skills/cosmic-dev-best-practices/references/design-examples/basedata-design-example.md +1 -1
  228. package/skills/cosmic-dev-best-practices/references/design-examples/bill-design-example.md +1 -1
  229. package/skills/cosmic-dev-best-practices/references/elements/AssistantField.md +1 -1
  230. package/skills/cosmic-dev-best-practices/references/elements/BasedataField.md +2 -2
  231. package/skills/cosmic-dev-best-practices/references/elements/BasedataPropField.md +3 -3
  232. package/skills/cosmic-dev-best-practices/references/elements/CreaterField.md +2 -2
  233. package/skills/cosmic-dev-best-practices/references/elements/CurrencyField.md +2 -2
  234. package/skills/cosmic-dev-best-practices/references/elements/CustomerField.md +2 -2
  235. package/skills/cosmic-dev-best-practices/references/elements/FlexField.md +1 -1
  236. package/skills/cosmic-dev-best-practices/references/elements/MaterielField.md +2 -2
  237. package/skills/cosmic-dev-best-practices/references/elements/OrgField.md +2 -2
  238. package/skills/cosmic-dev-best-practices/references/elements/SupplierField.md +2 -2
  239. package/skills/cosmic-dev-best-practices/references/elements/UnitField.md +1 -1
  240. package/skills/cosmic-dev-best-practices/references/elements/UserField.md +3 -3
  241. package/skills/cosmic-dev-best-practices/references/elements/common-field-property-conventions.md +16 -11
  242. package/skills/cosmic-dev-best-practices/references/layout-control-convention.md +5 -4
  243. package/skills/erp-analysis/references/delivery-contract.md +4 -2
  244. package/skills/erp-analysis/templates/AGENTS.template.md +1 -1
  245. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/.dsh/cosmic/.kd/config.json +0 -21
@@ -0,0 +1,446 @@
1
+ #!/usr/bin/env python3
2
+ """Measure how business plugins in the L0 index use the platform API.
3
+
4
+ Answers the two P1 questions of the plugin corpus plan from the index built by
5
+ ``build_index.py``:
6
+
7
+ 1. Of the platform members business plugins reference, how many are public SDK surface?
8
+ 2. Of those public members, how many does the API knowledge (``knowledge/cosmic/platform/api``)
9
+ document? It also lists the most referenced public members the pages do not document, and
10
+ the most referenced non-public ones.
11
+
12
+ Population: classes in reference-indexed layers whose class hierarchy reaches a plugin base
13
+ class registered in ``references/type-registry.json`` (the sole type/base mapping), or that
14
+ carry a registered class annotation, plus their nested and anonymous classes. ``--extra-root``
15
+ adds further base classes for an ad-hoc question without changing the registry.
16
+
17
+ Evidence scope:
18
+ - A reference is a constant-pool entry naming a member. It counts classes that name the member,
19
+ not call sites, and it says nothing about runtime behaviour.
20
+ - A reference is resolved to its declaring class through the indexed hierarchy. A class defined
21
+ more than once with different content is resolved by layer priority and reported as ambiguous.
22
+ - Public means the declaring class carries ``@SdkPublic``, ``@SdkPlugin``, ``@SdkService`` or
23
+ ``@SdkSPI``, and the member does not carry ``@SdkInternal``. A member inherited from an unmarked
24
+ platform class through a public subclass is reported separately as ``public-inherited``.
25
+ - A page documents a member when the declaring class's page lists that name with the same
26
+ parameter types. Pages carry no constructors, so constructors are reported on their own.
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ import argparse
32
+ import json
33
+ import re
34
+ import sqlite3
35
+ import sys
36
+ import time
37
+ from collections import Counter, defaultdict
38
+ from pathlib import Path
39
+
40
+ sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
41
+
42
+ import jvm_classfile # noqa: E402
43
+ import platform_api_contract as api # noqa: E402
44
+ from build_index import DB_NAME # noqa: E402
45
+
46
+ PUBLIC_MARKERS = {"kd.sdk.annotation.SdkPublic", "kd.sdk.annotation.SdkPlugin",
47
+ "kd.sdk.annotation.SdkService", "kd.sdk.annotation.SdkSPI"}
48
+ INTERNAL_MARKER = "kd.sdk.annotation.SdkInternal"
49
+ DEPRECATED_MARKERS = {"kd.sdk.annotation.SdkDeprecated", "java.lang.Deprecated"}
50
+ DEFAULT_PLATFORM_LAYERS = ("bos",)
51
+ # A class defined in several layers resolves to the first layer here that defines it. The
52
+ # project's compile classpath (config.gradle) is trd, bos, biz and cus; a patch directory is an
53
+ # overlay outside it, so it never shadows the bos definition.
54
+ LAYER_PRIORITY = ("bos", "biz", "cus", "trd", "patch")
55
+ _TYPE_VARIABLE_RE = re.compile(r"^[A-Z][A-Z0-9]?(\[\])*$")
56
+
57
+
58
+ class MeasureError(RuntimeError):
59
+ def __init__(self, code: str, message: str):
60
+ super().__init__(message)
61
+ self.code = code
62
+
63
+
64
+ def _registry_roots(extra_roots: list[str]) -> tuple[dict[str, str], dict[str, str]]:
65
+ """Base class fqcn -> registry type, and class annotation fqcn -> registry type."""
66
+ registry = json.loads(api.TYPE_REGISTRY_PAGE.read_text(encoding="utf-8-sig"))
67
+ bases: dict[str, str] = {}
68
+ annotations: dict[str, str] = {}
69
+ for entry in registry.get("types", []):
70
+ target = annotations if str(entry.get("baseClass") or "").startswith("@") else bases
71
+ for fqcn in str(entry.get("baseImport") or "").split(","):
72
+ if fqcn.strip():
73
+ target.setdefault(fqcn.strip(), entry["type"])
74
+ for fqcn in extra_roots:
75
+ bases.setdefault(fqcn, "extra-root")
76
+ return bases, annotations
77
+
78
+
79
+ def _page_signatures() -> dict[str, dict[str, set[tuple[str, ...]]]]:
80
+ """Platform class fqcn (binary name) -> member name -> documented parameter type tuples.
81
+
82
+ Uses the same front matter and signature parsing as the source checks, so a member counts as
83
+ documented here exactly when those checks would find it on the page.
84
+ """
85
+ pages: dict[str, dict[str, set[tuple[str, ...]]]] = {}
86
+ for path in api.API_ROOT.rglob("*.md"):
87
+ text = path.read_text(encoding="utf-8-sig", errors="replace")
88
+ front = api._FRONT_MATTER_RE.search(text)
89
+ if not front:
90
+ continue
91
+ meta = {match.group("key"): match.group("value").strip()
92
+ for match in api._FRONT_MATTER_FIELD_RE.finditer(front.group("body"))}
93
+ simple, package = meta.get("api_class") or "", meta.get("api_package") or ""
94
+ if not simple or not package.startswith(api.PLATFORM_PACKAGE.rstrip(".")):
95
+ continue
96
+ # A nested class page names its outer class as the package and prefixes the class name.
97
+ outer, _, nested = simple.partition(".")
98
+ fqcn = (f"{package}${nested.replace('.', '$')}" if nested and outer == package.rpartition(".")[2]
99
+ else f"{package}.{simple}")
100
+ members = pages.setdefault(fqcn, {})
101
+ for match in api._SIGNATURE_RE.finditer(text):
102
+ members.setdefault(match.group("name"), set()).add(tuple(api._param_types(match.group("params"))))
103
+ return pages
104
+
105
+
106
+ def _parameters_match(documented: tuple[str, ...], actual: list[str]) -> bool:
107
+ if len(documented) != len(actual):
108
+ return False
109
+ for page_type, java_type in zip(documented, actual):
110
+ if page_type == java_type:
111
+ continue
112
+ # A generic type variable on the page erases to a reference type in the descriptor.
113
+ if _TYPE_VARIABLE_RE.match(page_type) and page_type.count("[]") == java_type.count("[]") \
114
+ and java_type.rstrip("[]") not in jvm_classfile._PRIMITIVES.values():
115
+ continue
116
+ return False
117
+ return True
118
+
119
+
120
+ class Index:
121
+ """Read-only view over the L0 index with hierarchy and member resolution."""
122
+
123
+ def __init__(self, db_path: Path):
124
+ if not db_path.is_file():
125
+ raise MeasureError("CORPUS_INDEX_MISSING", f"No L0 index at {db_path}; run build_index.py first")
126
+ self.db = sqlite3.connect(f"file:{db_path}?mode=ro", uri=True)
127
+ self.text = dict(self.db.execute("SELECT id, value FROM strings"))
128
+ self.ids = {value: key for key, value in self.text.items()}
129
+ rank = {layer: position for position, layer in enumerate(LAYER_PRIORITY)}
130
+ candidates: dict[int, list[tuple]] = defaultdict(list)
131
+ for row in self.db.execute(
132
+ "SELECT c.id, c.name_id, c.super_id, c.access, c.annotations, c.sha256, j.layer, j.refs_indexed "
133
+ "FROM classes c JOIN jars j ON j.id = c.jar_id"):
134
+ candidates[row[1]].append(row)
135
+ interfaces: dict[int, list[int]] = defaultdict(list)
136
+ for class_id, iface_id in self.db.execute("SELECT class_id, iface_id FROM interfaces"):
137
+ interfaces[class_id].append(iface_id)
138
+ # name id -> (class id, super id, interface ids, access, annotations, layer, refs_indexed, ambiguous)
139
+ self.classes: dict[int, tuple] = {}
140
+ for name_id, rows in candidates.items():
141
+ rows.sort(key=lambda row: (rank.get(row[6], len(rank)), row[0]))
142
+ chosen = rows[0]
143
+ ambiguous = len({row[5] for row in rows}) > 1
144
+ self.classes[name_id] = (chosen[0], chosen[2], interfaces.get(chosen[0], []), chosen[3],
145
+ frozenset((chosen[4] or "").split()), chosen[6], chosen[7], ambiguous)
146
+ self.interfaces = interfaces
147
+ self._members: dict[int, dict[tuple[str, int, int], frozenset[str]]] = {}
148
+ self._resolved: dict[tuple, tuple] = {}
149
+
150
+ def members(self, class_id: int) -> dict[tuple[str, int, int], frozenset[str]]:
151
+ cached = self._members.get(class_id)
152
+ if cached is None:
153
+ cached = {(kind, name_id, desc_id): frozenset((annotations or "").split())
154
+ for kind, name_id, desc_id, annotations in self.db.execute(
155
+ "SELECT kind, name_id, desc_id, annotations FROM members WHERE class_id=?", (class_id,))}
156
+ self._members[class_id] = cached
157
+ return cached
158
+
159
+ def resolve(self, owner_id: int, kind: str, name_id: int, desc_id: int) -> tuple:
160
+ """(declaring name id or None, chain from the owner to it, ambiguous, member annotations).
161
+
162
+ Methods are looked up in the class, then its superclass chain, then its interfaces;
163
+ fields in the class, its interfaces, then its superclass, as the JVM resolves them.
164
+ """
165
+ key = (owner_id, kind, name_id, desc_id)
166
+ known = self._resolved.get(key)
167
+ if known is not None:
168
+ return known
169
+ member_kind = "F" if kind == "F" else "M"
170
+ seen: set[int] = set()
171
+
172
+ def search(name: int) -> tuple[int, tuple[int, ...], bool, frozenset[str]] | None:
173
+ if name in seen:
174
+ return None
175
+ seen.add(name)
176
+ entry = self.classes.get(name)
177
+ if entry is None:
178
+ return None
179
+ found = self.members(entry[0]).get((member_kind, name_id, desc_id))
180
+ if found is not None:
181
+ return name, (name,), entry[7], found
182
+ parents = ([entry[1]] if entry[1] else []) + list(entry[2])
183
+ if member_kind == "F":
184
+ parents = list(entry[2]) + ([entry[1]] if entry[1] else [])
185
+ for parent in parents:
186
+ result = search(parent)
187
+ if result:
188
+ return result[0], (name, *result[1]), entry[7] or result[2], result[3]
189
+ return None
190
+
191
+ result = search(owner_id)
192
+ value = result if result else (None, (), False, frozenset())
193
+ self._resolved[key] = value
194
+ return value
195
+
196
+ def nearest_base(self, name_id: int, bases: dict[int, str]) -> int | None:
197
+ """The registered base reached in the fewest inheritance steps, superclass before interfaces."""
198
+ frontier, seen = [name_id], {name_id}
199
+ while frontier:
200
+ following = []
201
+ for name in frontier:
202
+ entry = self.classes.get(name)
203
+ if not entry:
204
+ continue
205
+ for parent in ([entry[1]] if entry[1] else []) + list(entry[2]):
206
+ if parent in bases:
207
+ return parent
208
+ if parent not in seen:
209
+ seen.add(parent)
210
+ following.append(parent)
211
+ frontier = following
212
+ return None
213
+
214
+
215
+ def _signature(index: Index, owner: str, kind: str, name_id: int, desc_id: int) -> str:
216
+ name, descriptor = index.text[name_id], index.text[desc_id]
217
+ params, returned = jvm_classfile.descriptor_types(descriptor)
218
+ if kind == "F":
219
+ return f"{returned} {owner}.{name}"
220
+ if name == "<init>":
221
+ return f"new {owner}({', '.join(params)})"
222
+ return f"{returned} {owner}.{name}({', '.join(params)})"
223
+
224
+
225
+ def measure(workspace: str, top: int, platform_layers: list[str], extra_roots: list[str]) -> dict:
226
+ started = time.monotonic()
227
+ root = Path(workspace).expanduser().resolve()
228
+ index = Index(root / DB_NAME)
229
+ platform = set(platform_layers)
230
+ base_types, annotation_types = _registry_roots(extra_roots)
231
+ base_ids = {index.ids[fqcn]: kind for fqcn, kind in base_types.items() if fqcn in index.ids}
232
+ missing_bases = sorted(fqcn for fqcn in base_types if fqcn not in index.ids)
233
+
234
+ plugins: dict[int, str] = {}
235
+ for name_id, entry in index.classes.items():
236
+ if not entry[6]:
237
+ continue
238
+ base = index.nearest_base(name_id, base_ids)
239
+ if base is not None:
240
+ plugins[name_id] = base_ids[base]
241
+ continue
242
+ marker = next((annotation_types[item] for item in sorted(entry[4]) if item in annotation_types), None)
243
+ if marker:
244
+ plugins[name_id] = marker
245
+ top_level = {index.text[name_id]: name_id for name_id in plugins}
246
+ population: dict[int, int] = {} # class id -> top-level plugin name id
247
+ for name_id, entry in index.classes.items():
248
+ if not entry[6]:
249
+ continue
250
+ outer = index.text[name_id].split("$", 1)[0]
251
+ if outer in top_level:
252
+ population[entry[0]] = top_level[outer]
253
+
254
+ by_type = Counter(plugins.values())
255
+ pairs = Counter() # target layer -> (class, member) references
256
+ labels = Counter()
257
+ coverage = Counter()
258
+ member_classes: dict[tuple, set[int]] = defaultdict(set)
259
+ member_info: dict[tuple, dict] = {}
260
+ plugin_flags: dict[int, set[str]] = defaultdict(set)
261
+ ambiguous_refs = 0
262
+ java_refs = sum(count or 0 for class_id, count in index.db.execute("SELECT id, java_refs FROM classes")
263
+ if class_id in population)
264
+ pages = _page_signatures()
265
+ for class_id, kind, owner_id, name_id, desc_id in index.db.execute(
266
+ "SELECT class_id, kind, owner_id, name_id, desc_id FROM refs WHERE kind IN ('M', 'I', 'F')"):
267
+ plugin = population.get(class_id)
268
+ if plugin is None:
269
+ continue
270
+ declaring, path, ambiguous, member_annotations = index.resolve(owner_id, kind, name_id, desc_id)
271
+ if declaring is None:
272
+ pairs["unresolved"] += 1
273
+ continue
274
+ entry = index.classes[declaring]
275
+ layer = entry[5]
276
+ if layer not in platform:
277
+ pairs[layer] += 1
278
+ continue
279
+ pairs["platform"] += 1
280
+ ambiguous_refs += 1 if ambiguous else 0
281
+ markers = entry[4]
282
+ chain_platform = [step for step in path if index.classes[step][5] in platform]
283
+ if INTERNAL_MARKER in member_annotations:
284
+ label = "internal"
285
+ elif markers & PUBLIC_MARKERS:
286
+ label = "public"
287
+ elif any(index.classes[step][4] & PUBLIC_MARKERS for step in chain_platform[:-1]):
288
+ label = "public-inherited"
289
+ elif INTERNAL_MARKER in markers:
290
+ label = "internal"
291
+ else:
292
+ label = "unmarked"
293
+ labels[label] += 1
294
+ plugin_flags[plugin].add(label)
295
+ member_name = index.text[name_id]
296
+ owner = index.text[declaring]
297
+ if label.startswith("public"):
298
+ if kind == "F":
299
+ status = "field"
300
+ elif member_name == "<init>":
301
+ status = "constructor"
302
+ else:
303
+ # Follow the chain the way describe-class does: any documented class between the
304
+ # referenced type and the declaring class may list the member.
305
+ chain_pages = [pages[index.text[step]] for step in chain_platform if index.text[step] in pages]
306
+ listed = [option for page in chain_pages for option in page.get(member_name, ())]
307
+ params = [jvm_classfile.simple_type(item)
308
+ for item in jvm_classfile.descriptor_types(index.text[desc_id])[0]]
309
+ if not chain_pages:
310
+ status = "no-page"
311
+ elif any(_parameters_match(option, params) for option in listed):
312
+ status = "documented"
313
+ else:
314
+ status = "overload-missing" if listed else "member-missing"
315
+ coverage[status] += 1
316
+ else:
317
+ status = None
318
+ key = (declaring, kind == "F", name_id, desc_id)
319
+ member_classes[key].add(class_id)
320
+ info = member_info.get(key)
321
+ if info is None:
322
+ member_info[key] = {"signature": _signature(index, owner.replace("$", "."), kind, name_id, desc_id),
323
+ "label": label, "documentation": status,
324
+ "deprecated": bool((member_annotations | markers) & DEPRECATED_MARKERS)}
325
+ elif status == "documented":
326
+ # Documented through any receiver type counts; another chain may miss the page.
327
+ info["documentation"] = status
328
+
329
+ def ranked(select) -> list[dict]:
330
+ rows = []
331
+ for key, classes in member_classes.items():
332
+ info = member_info[key]
333
+ if select(info):
334
+ plugin_names = sorted({index.text[population[class_id]] for class_id in classes})
335
+ rows.append({**info, "referencingClasses": len(classes), "referencingPlugins": len(plugin_names),
336
+ "examplePlugins": plugin_names[:3]})
337
+ rows.sort(key=lambda row: (-row["referencingPlugins"], -row["referencingClasses"], row["signature"]))
338
+ return rows[:top]
339
+
340
+ distinct = Counter(info["label"] for info in member_info.values())
341
+ distinct_coverage = Counter(info["documentation"] for info in member_info.values() if info["documentation"])
342
+ public_total = sum(coverage.values())
343
+ public_methods = public_total - coverage["constructor"] - coverage["field"]
344
+ platform_total = pairs["platform"]
345
+ fully_public = sum(1 for plugin in plugins if plugin_flags.get(plugin) and
346
+ plugin_flags[plugin] <= {"public", "public-inherited"})
347
+ with_platform = sum(1 for plugin in plugins if plugin_flags.get(plugin))
348
+
349
+ def share(part: int, whole: int) -> float | None:
350
+ return round(part / whole, 4) if whole else None
351
+
352
+ return {
353
+ "status": True,
354
+ "database": str(root / DB_NAME),
355
+ "evidenceScope": [
356
+ "Constant-pool references: counts classes that name a member, not call sites or runtime use.",
357
+ "Declaring class resolved through the indexed hierarchy; ambiguous duplicates are counted.",
358
+ "Public = declaring class marked SdkPublic/SdkPlugin/SdkService/SdkSPI and member not SdkInternal.",
359
+ "Documented = the declaring class's API page lists the name with the same parameter types.",
360
+ ],
361
+ "population": {
362
+ "pluginClasses": len(plugins), "classesIncludingNested": len(population),
363
+ "pluginClassesByRegistryType": dict(by_type.most_common()),
364
+ "registryBasesMissingFromIndex": missing_bases,
365
+ "platformLayers": sorted(platform),
366
+ },
367
+ "references": {
368
+ "byTargetLayer": {**dict(pairs.most_common()), "java(not stored)": java_refs},
369
+ "platformByLabel": dict(labels.most_common()),
370
+ "platformDistinctMembersByLabel": dict(distinct.most_common()),
371
+ "ambiguousPlatformReferences": ambiguous_refs,
372
+ },
373
+ "measurements": {
374
+ "publicShareOfPlatformReferences": share(labels["public"] + labels["public-inherited"], platform_total),
375
+ "publicShareStrict": share(labels["public"], platform_total),
376
+ "documentedShareOfPublicMethodReferences": share(coverage["documented"], public_methods),
377
+ "publicReferenceDocumentation": dict(coverage.most_common()),
378
+ "publicDistinctMemberDocumentation": dict(distinct_coverage.most_common()),
379
+ "pluginsUsingOnlyPublicPlatformMembers": fully_public,
380
+ "pluginsWithPlatformReferences": with_platform,
381
+ "pluginsUsingOnlyPublicShare": share(fully_public, with_platform),
382
+ },
383
+ "topUndocumentedPublicMembers": ranked(lambda info: info["documentation"] in
384
+ ("no-page", "member-missing", "overload-missing")),
385
+ "topPublicConstructors": ranked(lambda info: info["documentation"] == "constructor"),
386
+ "topPublicFields": ranked(lambda info: info["documentation"] == "field"),
387
+ "topNonPublicMembers": ranked(lambda info: info["label"] in ("internal", "unmarked")),
388
+ "elapsedSec": round(time.monotonic() - started, 1),
389
+ }
390
+
391
+
392
+ def write_markdown(result: dict, path: Path) -> None:
393
+ measurements = result["measurements"]
394
+ references = result["references"]
395
+ population = result["population"]
396
+
397
+ def pct(value: float | None) -> str:
398
+ return "n/a" if value is None else f"{value * 100:.1f}%"
399
+
400
+ lines = [
401
+ "# L0 plugin corpus: platform API usage", "",
402
+ f"- Database: `{result['database']}`",
403
+ f"- Plugin classes: {population['pluginClasses']} ({population['classesIncludingNested']} with nested classes)",
404
+ f"- Platform references (class-member pairs): {references['byTargetLayer'].get('platform', 0)}",
405
+ f"- Public share of platform references: {pct(measurements['publicShareOfPlatformReferences'])}"
406
+ f" (strict, excluding public-inherited: {pct(measurements['publicShareStrict'])})",
407
+ f"- Documented share of public method references: {pct(measurements['documentedShareOfPublicMethodReferences'])}"
408
+ " (constructors and fields counted separately; API pages carry neither)",
409
+ f"- Plugins whose platform references are all public: {measurements['pluginsUsingOnlyPublicPlatformMembers']}"
410
+ f" of {measurements['pluginsWithPlatformReferences']} ({pct(measurements['pluginsUsingOnlyPublicShare'])})",
411
+ "", "Evidence scope:", "", *[f"- {item}" for item in result["evidenceScope"]], "",
412
+ ]
413
+ for title, key in (("Most referenced public members without matching API documentation", "topUndocumentedPublicMembers"),
414
+ ("Most referenced public constructors (pages carry none)", "topPublicConstructors"),
415
+ ("Most referenced public fields and enum constants (pages carry none)", "topPublicFields"),
416
+ ("Most referenced non-public platform members", "topNonPublicMembers")):
417
+ lines += [f"## {title}", "", "| Plugins | Classes | Label | Documentation | Member |", "| --- | --- | --- | --- | --- |"]
418
+ lines += [f"| {row['referencingPlugins']} | {row['referencingClasses']} | {row['label']} | "
419
+ f"{row['documentation'] or '-'} | `{row['signature']}` |" for row in result[key]]
420
+ lines.append("")
421
+ path.write_text("\n".join(lines), encoding="utf-8")
422
+
423
+
424
+ def main(argv: list[str] | None = None) -> int:
425
+ parser = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
426
+ parser.add_argument("--workspace", required=True, help="Corpus workspace holding the L0 index")
427
+ parser.add_argument("--top", type=int, default=50, help="Rows per ranked list")
428
+ parser.add_argument("--platform-layer", action="append", help="Layer treated as platform (default: bos)")
429
+ parser.add_argument("--extra-root", action="append", default=[], help="Additional population base class fqcn")
430
+ args = parser.parse_args(argv)
431
+ try:
432
+ result = measure(args.workspace, args.top, args.platform_layer or list(DEFAULT_PLATFORM_LAYERS), args.extra_root)
433
+ except MeasureError as exc:
434
+ print(json.dumps({"status": False, "errorCode": exc.code, "message": str(exc)}, ensure_ascii=False))
435
+ return 2
436
+ output = Path(args.workspace).expanduser().resolve() / "measurements"
437
+ output.mkdir(exist_ok=True)
438
+ (output / "p1-api-usage.json").write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
439
+ write_markdown(result, output / "p1-api-usage.md")
440
+ print(json.dumps({key: result[key] for key in ("status", "population", "references", "measurements", "elapsedSec")},
441
+ ensure_ascii=False, indent=2))
442
+ return 0
443
+
444
+
445
+ if __name__ == "__main__":
446
+ sys.exit(main())
@@ -11,6 +11,8 @@ from __future__ import annotations
11
11
  import argparse
12
12
  import json
13
13
  import os
14
+ import hashlib
15
+ import re
14
16
  from pathlib import Path
15
17
  from typing import Optional
16
18
 
@@ -27,6 +29,7 @@ def write_state(
27
29
  class_name: str,
28
30
  state: str,
29
31
  session_id: Optional[str] = None,
32
+ emit: bool = True,
30
33
  ) -> None:
31
34
  if state not in STATES:
32
35
  print(json.dumps({"error": f"invalid state: {state}, must be {'|'.join(STATES)}"}))
@@ -36,12 +39,13 @@ def write_state(
36
39
  Path(path).parent.mkdir(parents=True, exist_ok=True)
37
40
  content = state if not session_id else f"{state}:{session_id}"
38
41
  Path(path).write_text(content, encoding="utf-8")
39
- print(json.dumps({
40
- "status": "ok",
41
- "file": path,
42
- "state": state,
43
- "session_id": session_id,
44
- }))
42
+ if emit:
43
+ print(json.dumps({
44
+ "status": "ok",
45
+ "file": path,
46
+ "state": state,
47
+ "session_id": session_id,
48
+ }))
45
49
 
46
50
 
47
51
  def read_state(project_root: str, class_name: str) -> None:
@@ -53,13 +57,50 @@ def read_state(project_root: str, class_name: str) -> None:
53
57
  content = path.read_text(encoding="utf-8").strip()
54
58
  raw_state = content.split(":", 1)[0]
55
59
  if raw_state == "source_ready":
56
- print(json.dumps({"state": raw_state, "verdict": "compile_backed_pass"}))
60
+ print(json.dumps({"state": raw_state, "verdict": "source_ready_only"}))
57
61
  elif raw_state == "coding":
58
62
  print(json.dumps({"state": raw_state, "verdict": "source_pending", "raw": content}))
59
63
  else:
60
64
  print(json.dumps({"state": raw_state, "verdict": "unknown"}))
61
65
 
62
66
 
67
+
68
+ def validate_registration_entry(entry, target_type, class_name=None):
69
+ """Check the declared source and compile outcome, shared by registration entry points."""
70
+ class_name = class_name or str(entry.get("className") or "")
71
+ registry = json.loads((Path(__file__).resolve().parents[1] / "references" / "type-registry.json")
72
+ .read_text(encoding="utf-8"))["types"]
73
+ definition = next((item for item in registry if item["type"] == entry.get("type")), None)
74
+ if definition is None:
75
+ raise ValueError("PLUGIN_LOCAL_TYPE_UNVERIFIED: archive type is not in the registry")
76
+ expected = definition.get("registrationTargetType")
77
+ if expected and expected != target_type:
78
+ raise ValueError("PLUGIN_REGISTRATION_TYPE_MISMATCH: archive expects " + expected
79
+ + ", requested " + target_type)
80
+ source = Path(str(entry.get("filePath") or ""))
81
+ if not source.is_absolute() or not source.is_file():
82
+ raise ValueError("PLUGIN_LOCAL_SOURCE_UNVERIFIED: resolve the archive source path before registration")
83
+ from java_source import stripped
84
+ text = stripped(source.read_text(encoding="utf-8-sig"))
85
+ declaration = re.search(r"\bclass\s+" + re.escape(class_name.rsplit(".", 1)[-1])
86
+ + r"\b([^{}]*)\{", text)
87
+ if declaration is None:
88
+ raise ValueError("PLUGIN_LOCAL_CLASS_UNVERIFIED: class declaration missing from archive source")
89
+ base = re.search(r"\bextends\s+([\w.]+)", declaration.group(1))
90
+ if definition.get("baseClass") and base is None and "implements" not in declaration.group(1):
91
+ raise ValueError("PLUGIN_SOURCE_BASE_CLASS_MISSING: plain Java class is not a registered plugin")
92
+ known = next((item for item in registry if base and item.get("baseClass") == base.group(1).rsplit(".", 1)[-1]), None)
93
+ if known and known.get("registrationTargetType") != target_type:
94
+ raise ValueError("PLUGIN_SOURCE_TARGET_MISMATCH: declared plugin base does not match registration target")
95
+
96
+ if entry.get("compileStatus") not in {"passed", "skipped-by-config"}:
97
+ raise ValueError("PLUGIN_SOURCE_NOT_READY: run compile_plugin_source.py; manual archive writes are not compile evidence")
98
+ actual = hashlib.sha256(source.read_bytes()).hexdigest()
99
+ if entry.get("sourceHash") != actual:
100
+ raise ValueError("PLUGIN_SOURCE_CHANGED: source differs from its compile result; compile the current source again")
101
+ return {"compileStatus": entry["compileStatus"], "sourceHash": actual}
102
+
103
+
63
104
  def main() -> None:
64
105
  parser = argparse.ArgumentParser(description="Java plugin source delivery gate")
65
106
  parser.add_argument(
@@ -0,0 +1,45 @@
1
+ """Verify the bound module and selected SDK declarations before source generation."""
2
+ from pathlib import Path
3
+
4
+ from compile_plugin_source import inspect_compile_environment
5
+ from inspect_platform_sdk import inspect_sdk
6
+
7
+
8
+ class TargetSdkError(ValueError):
9
+ def __init__(self, code, message):
10
+ super().__init__(message)
11
+ self.error_code = code
12
+
13
+
14
+ def verify_target_sdk(project_root, coding, usage=None):
15
+ environment = inspect_compile_environment(project_root)
16
+ if environment.get('status') == 'disabled':
17
+ return {'status': 'not-run', 'compile': False, 'projectRoot': str(Path(project_root).resolve()),
18
+ 'evidenceScope': 'Source-only configuration: target SDK, compilation and runtime are unverified'}
19
+ if environment.get('status') != 'ready':
20
+ raise TargetSdkError(environment.get('code') or 'PLUGIN_SDK_ENVIRONMENT_UNVERIFIED',
21
+ environment.get('message') or 'Target compile environment is unverified')
22
+ jars = sorted({str(file.resolve()) for directory in environment.get('sdkDirectories', [])
23
+ for file in Path(directory['path']).glob('*.jar')})
24
+ if not jars:
25
+ raise TargetSdkError('PLUGIN_TARGET_SDK_UNRESOLVED', 'The bound module does not establish target SDK libraries')
26
+ if usage is not None and (not isinstance(usage, list) or any(not isinstance(item, dict)
27
+ or not isinstance(item.get('class'), str) or not item['class']
28
+ or set(item) - {'class', 'members'} or not isinstance(item.get('members', []), list)
29
+ or any(not isinstance(member, str) or not member for member in item.get('members', [])) for item in usage)):
30
+ raise TargetSdkError('PLUGIN_SDK_USAGE_INVALID', 'sdkUsage requires exact class and member selectors')
31
+ classes = sorted({coding.get('baseContract', {}).get('class'),
32
+ *[item.get('class') for item in coding.get('signatureTypes', [])]} - {None})
33
+ verified = [inspect_sdk(jars, name, mode='locate') for name in classes if name.startswith('kd.bos.')]
34
+ contracts = [inspect_sdk(jars, item['class'], item.get('members')) for item in usage or []]
35
+ for result in [*verified, *contracts]:
36
+ if result.get('status') is not True:
37
+ raise TargetSdkError(result.get('errorCode') or 'PLUGIN_TARGET_SDK_UNVERIFIED', result.get('message') or 'Target SDK verification failed')
38
+ if result.get('missingDeclaredMembers'):
39
+ raise TargetSdkError('PLUGIN_SDK_MEMBER_UNVERIFIED',
40
+ f"{result['class']}: members not declared in the target SDK: {result['missingDeclaredMembers']}; verify an inherited declaration separately")
41
+ return {'status': 'verified', 'projectRoot': str(Path(project_root).resolve()),
42
+ 'gradleRoot': environment['gradleRoot'], 'module': environment['module'],
43
+ 'javaHome': environment['javaHome'], 'sdkDirectories': environment['sdkDirectories'],
44
+ 'classes': verified, 'contracts': contracts,
45
+ 'evidenceScope': 'Bound module, target SDK class identities and selected declarations; no compilation or runtime behavior is implied'}