@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.
- package/cordis.patch.yml +5 -5
- package/package.json +1 -1
- package/skills/cosmic-build/CHANGELOG.md +4 -0
- package/skills/cosmic-build/LOCAL-PATCHES.md +91 -40
- package/skills/cosmic-build/SKILL.md +46 -13
- package/skills/cosmic-build/assets/platform-report-filters/TypedReportFilterFields.java +169 -0
- package/skills/cosmic-build/assets/platform-report-filters/platform.patch +98 -0
- package/skills/cosmic-build/assets/report-algo-benchmark/ReportAlgoBenchmark.java +48 -0
- package/skills/cosmic-build/assets/report-algo-benchmark/ReportBenchmarkLogFactory.java +7 -0
- package/skills/cosmic-build/examples/java-rules/CosmicEntryQuery.java +26 -0
- package/skills/cosmic-build/examples/java-rules/CosmicReportAggregation.java +35 -0
- package/skills/cosmic-build/examples/java-rules/CosmicReportFilters.java +77 -0
- package/skills/cosmic-build/examples/java-rules/CosmicTimeAdapter.java +25 -0
- package/skills/cosmic-build/examples/java-rules/CosmicTimeAdapterCheck.java +26 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/api/algo/GroupbyDataSet.md +47 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/api/entity/report/FilterInfo.md +12 -12
- package/skills/cosmic-build/knowledge/cosmic/platform/api/entity/report/FilterItemInfo.md +2 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/form-plugin.md +2 -4
- package/skills/cosmic-build/knowledge/cosmic/platform/java-type-contracts.md +11 -2
- package/skills/cosmic-build/knowledge/cosmic/platform/list-plugin.md +18 -14
- package/skills/cosmic-build/knowledge/cosmic/platform/operation-plugin.md +4 -4
- package/skills/cosmic-build/knowledge/cosmic/platform/reportlistdata-plugin.md +17 -9
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/operation/cross-entity-aggregate-writeback.md +2 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/operation/pre-post-operation.md +15 -7
- package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-algo.md +24 -68
- package/skills/cosmic-build/knowledge/cosmic/platform/workflow-plugin.md +2 -7
- package/skills/cosmic-build/references/application-context.md +35 -1
- package/skills/cosmic-build/references/common-reference.md +1 -1
- package/skills/cosmic-build/references/compile-guide.md +55 -68
- package/skills/cosmic-build/references/delivery.md +59 -7
- package/skills/cosmic-build/references/execution-contract.md +38 -0
- package/skills/cosmic-build/references/form-discovery.md +7 -1
- package/skills/cosmic-build/references/java-plugin-delivery.md +32 -162
- package/skills/cosmic-build/references/operation-dispatch.md +17 -0
- package/skills/cosmic-build/references/plugin-handoff-input.md +131 -0
- package/skills/cosmic-build/references/plugin-runtime-routing.md +1 -1
- package/skills/cosmic-build/references/report-filter-contract.md +78 -0
- package/skills/cosmic-build/references/sdk-inspection.md +23 -0
- package/skills/cosmic-build/schemas/data-model-authoring.schema.json +2 -3
- package/skills/cosmic-build/scripts/_http_client.py +55 -55
- package/skills/cosmic-build/scripts/appconfig_load.py +2 -2
- package/skills/cosmic-build/scripts/apply_report_filter_extension.py +39 -0
- package/skills/cosmic-build/scripts/bootstrap_cosmic_context.py +37 -2
- package/skills/cosmic-build/scripts/compile_plugin_source.py +279 -156
- package/skills/cosmic-build/scripts/execution_binding.py +40 -0
- package/skills/cosmic-build/scripts/inspect_platform_sdk.py +169 -0
- package/skills/cosmic-build/scripts/kd_api.py +23 -22
- package/skills/cosmic-build/scripts/kd_auth.py +1 -1
- package/skills/cosmic-build/scripts/kd_env_loader.py +19 -10
- package/skills/cosmic-build/scripts/kd_token_cache.py +2 -2
- package/skills/cosmic-build/scripts/kingscript_sdk_sync.py +350 -0
- package/skills/cosmic-build/scripts/kingscript_source_gate.py +11 -8
- package/skills/cosmic-build/scripts/platform_api_contract.py +246 -11
- package/skills/cosmic-build/scripts/plugin_corpus/build_index.py +403 -0
- package/skills/cosmic-build/scripts/plugin_corpus/jvm_classfile.py +281 -0
- package/skills/cosmic-build/scripts/plugin_corpus/measure_api_usage.py +446 -0
- package/skills/cosmic-build/scripts/plugin_source_gate.py +48 -7
- package/skills/cosmic-build/scripts/plugin_target_sdk.py +45 -0
- package/skills/cosmic-build/scripts/prepare_plugin_handoff.py +493 -47
- package/skills/cosmic-build/scripts/preview_url.py +35 -6
- package/skills/cosmic-build/scripts/query_plugin_handoff.py +250 -0
- package/skills/cosmic-build/scripts/register_plugins.py +28 -2
- package/skills/cosmic-build/scripts/render_output_cards.py +42 -9
- package/skills/cosmic-build/scripts/report_algo_benchmark.py +46 -0
- package/skills/cosmic-build/scripts/run.py +2 -2
- package/skills/cosmic-build/scripts/sync_generated_locale_assets.py +43 -0
- package/skills/cosmic-build/scripts/test/fixtures/ReportSdkCheck.java +64 -0
- package/skills/cosmic-build/scripts/test/fixtures/java_context_booking_native.json +541 -0
- package/skills/cosmic-build/scripts/test/test_add_rule_business_contract.py +85 -14
- package/skills/cosmic-build/scripts/test/test_batch5_drift_governance.py +12 -3
- package/skills/cosmic-build/scripts/test/test_build_meta_order.py +0 -16
- package/skills/cosmic-build/scripts/test/test_cli_continuation.py +87 -0
- package/skills/cosmic-build/scripts/test/test_compile_plugin_source.py +224 -3
- package/skills/cosmic-build/scripts/test/test_control_authoring.py +123 -4
- package/skills/cosmic-build/scripts/test/test_cosmic_sdk_examples.py +141 -0
- package/skills/cosmic-build/scripts/test/test_data_model_contract.py +30 -5
- package/skills/cosmic-build/scripts/test/test_delivery_stage_boundary.py +2 -10
- package/skills/cosmic-build/scripts/test/test_dym_sync_background.py +11 -0
- package/skills/cosmic-build/scripts/test/test_dynamic_form_metadata_only_contract.py +59 -1
- package/skills/cosmic-build/scripts/test/test_guide_skeleton_contract.py +87 -43
- package/skills/cosmic-build/scripts/test/test_inspect_platform_sdk.py +71 -0
- package/skills/cosmic-build/scripts/test/test_kd_api.py +67 -19
- package/skills/cosmic-build/scripts/test/test_kingscript_sdk_sync.py +221 -0
- package/skills/cosmic-build/scripts/test/test_kingscript_source_gate.py +10 -0
- package/skills/cosmic-build/scripts/test/test_list_configuration_closure.py +18 -0
- package/skills/cosmic-build/scripts/test/test_long_session_regressions.py +116 -0
- package/skills/cosmic-build/scripts/test/test_mcp_cli.py +524 -0
- package/skills/cosmic-build/scripts/test/test_modeling_input_recovery.py +141 -0
- package/skills/cosmic-build/scripts/test/test_open_world_cli_catalog.py +36 -2
- package/skills/cosmic-build/scripts/test/test_platform_api_contract.py +161 -1
- package/skills/cosmic-build/scripts/test/test_plugin_business_contract.py +91 -0
- package/skills/cosmic-build/scripts/test/test_plugin_corpus.py +195 -0
- package/skills/cosmic-build/scripts/test/test_plugin_handoff.py +346 -1
- package/skills/cosmic-build/scripts/test/test_plugin_target_sdk.py +70 -0
- package/skills/cosmic-build/scripts/test/test_preview_url.py +44 -1
- package/skills/cosmic-build/scripts/test/test_property_batch.py +22 -0
- package/skills/cosmic-build/scripts/test/test_property_input_discovery.py +13 -11
- package/skills/cosmic-build/scripts/test/test_query_plugin_handoff.py +160 -0
- package/skills/cosmic-build/scripts/test/test_register_plugins.py +31 -1
- package/skills/cosmic-build/scripts/test/test_render_output_cards.py +50 -4
- package/skills/cosmic-build/scripts/test/test_report_business_contract.py +27 -0
- package/skills/cosmic-build/scripts/test/test_report_doc_contract_sync.py +5 -4
- package/skills/cosmic-build/scripts/test/test_report_sdk_optimization.py +36 -0
- package/skills/cosmic-build/scripts/test/test_rule_batch_evidence.py +1 -1
- package/skills/cosmic-build/scripts/test/test_rule_domain_contract.py +20 -0
- package/skills/cosmic-build/scripts/test/test_rule_function_formula.py +16 -6
- package/skills/cosmic-build/scripts/test/test_skill_entry_contract.py +1 -0
- package/skills/cosmic-build/scripts/test/test_test_environment.py +45 -0
- package/skills/cosmic-build/scripts/test/test_verify_plugin_loading.py +79 -0
- package/skills/cosmic-build/scripts/test_environment.py +49 -0
- package/skills/cosmic-build/scripts/verify_plugin_loading.py +71 -0
- package/skills/cosmic-build/skills/cosmic-coding-impl/SKILL.md +86 -162
- package/skills/cosmic-build/skills/cosmic-coding-script/ATTRIBUTION.md +1 -1
- package/skills/cosmic-build/skills/cosmic-coding-script/SKILL.md +45 -14
- package/skills/cosmic-build/skills/cosmic-coding-script/references/kwc-controller-authoring.md +6 -0
- package/skills/cosmic-build/skills/cosmic-coding-script/references/sdk/README.md +29 -19
- package/skills/cosmic-build/skills/cosmic-coding-script/references/sdk/docs//350/204/232/346/234/254SDK.md +3 -4
- package/skills/cosmic-build/skills/cosmic-coding-script/references/sdk/indexes/package-index.md +1 -1
- package/skills/cosmic-build/skills/cosmic-coding-script/references/sdk/strategy.md +27 -18
- package/skills/cosmic-build/skills/cosmic-meta-api/SKILL.md +11 -17
- package/skills/cosmic-build/skills/cosmic-meta-api/apis/ai_meta.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/apis/app_menu.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/apis/meta_log.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/control-authoring.json +45 -4
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/form-control-traits.json +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/layout-ui.json +18 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/menu-publish.json +8 -2
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/property-batch.json +7 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/property-input-index.json +3 -3
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/report-write-contracts.json +1 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/rule-domain.json +37 -5
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/rule-event-contract.json +24 -3
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/run-build-meta.json +61 -12
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/dropdown-menu.schema.json +69 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/report-write.schema.json +2 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/rule-set.schema.json +1 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/rule-write.schema.json +14 -7
- package/skills/cosmic-build/skills/cosmic-meta-api/docs/api-summary.md +2 -2
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/app-action/SKILL.md +6 -3
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/develop/SKILL.md +1 -2
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/export/SKILL.md +5 -6
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/init/SKILL.md +13 -36
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/pdm/SKILL.md +8 -9
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/pdm/scripts/config.json.example +5 -15
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/reset/SKILL.md +6 -6
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/usage/SKILL.md +20 -148
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/verify/SKILL.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/build_receipts.py +171 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_apps.py +25 -6
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_controls.py +55 -21
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_fields.py +10 -3
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_formactions.py +63 -6
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_forms.py +59 -2
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_listui.py +8 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_metaops.py +36 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_plugins.py +115 -90
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_properties.py +666 -4
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_rules.py +1415 -87
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/commands.py +6 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/config.py +28 -15
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/execution_errors.py +40 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/formatters.py +32 -5
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/property_policy_contract.py +6 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/report_ops.py +113 -17
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/reset.py +3 -3
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/rule_contract.py +107 -26
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/build_meta_order.py +50 -49
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/cosmic_api.py +856 -150
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/field_decorator.py +47 -14
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_build_receipt_resume.py +374 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_cli_failure_contract.py +24 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_creation_identity_boundary.py +4 -2
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_dropdown_menu_contract.py +382 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_existing_form_ui.py +0 -25
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_field_decorator.py +50 -6
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_functional_group_resolution.py +83 -16
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_identity_template_resolution.py +103 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_plugin_contract.py +28 -2
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_readback_derivation.py +22 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_typed_filters.py +65 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_rule_write_closure.py +1041 -15
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_batch_post_build_recovery.py +65 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_batch_verification_evidence.py +17 -0
- package/skills/cosmic-build/skills/cosmic-meta-knowledge/generation/api-param-checklist.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-knowledge/generation/rule-modeling-guide.md +7 -4
- package/skills/cosmic-build/skills/cosmic-meta-modeling/SKILL.md +61 -13
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/app-menu/SKILL.md +8 -0
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/formtemplate/SKILL.md +5 -0
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/formtemplate/dynamic-form-artifact-schema.md +5 -0
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/SKILL.md +21 -9
- package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-entity-graphs.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-storage-constraints.md +5 -3
- package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-ui-composition.md +6 -3
- package/skills/cosmic-build/skills/cosmic-meta-modeling/references/creation-recovery.md +13 -0
- package/skills/cosmic-build/skills/cosmic-meta-modeling/references/parse-core.md +3 -0
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/data_model_contract.py +6 -1
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/data_model_emit.py +135 -87
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/field_type_registry.py +11 -1
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/isv_resolver.py +1 -3
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/report_validator.py +29 -0
- package/skills/cosmic-build/skills/cosmic-meta-modifier/SKILL.md +4 -2
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/context/SKILL.md +2 -2
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/execute/SKILL.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/operation/SKILL.md +5 -4
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/operation/operation-write.md +6 -2
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/operation/validation.md +2 -0
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/property/SKILL.md +20 -5
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/property/complex-apis.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/property/policy-grammar.md +4 -1
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/report-op/SKILL.md +9 -0
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/SKILL.md +61 -4
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/raw-rule-authoring.md +3 -1
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/rule-actions.md +2 -2
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/rule-reference.md +6 -6
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/state.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/unit/SKILL.md +6 -3
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/verify/usage-guide.md +4 -4
- package/skills/cosmic-build/skills/cosmic-meta-modifier/references/list-filter-modification.md +4 -1
- package/skills/cosmic-build/skills/cosmic-meta-ui/SKILL.md +9 -1
- package/skills/cosmic-build/skills/cosmic-meta-ui/modules/button/SKILL.md +3 -0
- package/skills/cosmic-build/skills/mcp-server/SKILL.md +210 -0
- package/skills/cosmic-build/skills/mcp-server/scripts/mcp_cli.py +431 -0
- package/skills/cosmic-build/skills/openapi-gen/SKILL.md +3 -3
- package/skills/cosmic-build/skills/openapi-gen/scripts/api/gen_runner.py +2 -2
- package/skills/cosmic-dev-best-practices/references/bill-dev-convention.md +19 -9
- package/skills/cosmic-dev-best-practices/references/cosmic-meta-api.md +14 -11
- package/skills/cosmic-dev-best-practices/references/design-examples/basedata-design-example.md +1 -1
- package/skills/cosmic-dev-best-practices/references/design-examples/bill-design-example.md +1 -1
- package/skills/cosmic-dev-best-practices/references/elements/AssistantField.md +1 -1
- package/skills/cosmic-dev-best-practices/references/elements/BasedataField.md +2 -2
- package/skills/cosmic-dev-best-practices/references/elements/BasedataPropField.md +3 -3
- package/skills/cosmic-dev-best-practices/references/elements/CreaterField.md +2 -2
- package/skills/cosmic-dev-best-practices/references/elements/CurrencyField.md +2 -2
- package/skills/cosmic-dev-best-practices/references/elements/CustomerField.md +2 -2
- package/skills/cosmic-dev-best-practices/references/elements/FlexField.md +1 -1
- package/skills/cosmic-dev-best-practices/references/elements/MaterielField.md +2 -2
- package/skills/cosmic-dev-best-practices/references/elements/OrgField.md +2 -2
- package/skills/cosmic-dev-best-practices/references/elements/SupplierField.md +2 -2
- package/skills/cosmic-dev-best-practices/references/elements/UnitField.md +1 -1
- package/skills/cosmic-dev-best-practices/references/elements/UserField.md +3 -3
- package/skills/cosmic-dev-best-practices/references/elements/common-field-property-conventions.md +16 -11
- package/skills/cosmic-dev-best-practices/references/layout-control-convention.md +5 -4
- package/skills/erp-analysis/references/delivery-contract.md +4 -2
- package/skills/erp-analysis/templates/AGENTS.template.md +1 -1
- 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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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": "
|
|
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'}
|