@shundoo-ai/dsh-cosmic 1.0.21 → 1.0.22
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/package.json +1 -1
- package/skills/cosmic-build/LOCAL-PATCHES.md +43 -14
- package/skills/cosmic-build/SKILL.md +24 -9
- package/skills/cosmic-build/examples/java-rules/CosmicReportAggregation.java +26 -17
- package/skills/cosmic-build/examples/java-rules/CosmicReportFilters.java +96 -2
- package/skills/cosmic-build/knowledge/cosmic/arch/security/security_standard.md +4 -4
- package/skills/cosmic-build/knowledge/cosmic/platform/allimport.md +2 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/core/dynamic-object-collection.md +6 -4
- package/skills/cosmic-build/knowledge/cosmic/platform/core/model.md +9 -9
- package/skills/cosmic-build/knowledge/cosmic/platform/core/view.md +5 -5
- package/skills/cosmic-build/knowledge/cosmic/platform/form-plugin.md +8 -4
- package/skills/cosmic-build/knowledge/cosmic/platform/list-plugin.md +5 -3
- package/skills/cosmic-build/knowledge/cosmic/platform/print-plugin-guide.md +1 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/report-plugin.md +269 -57
- package/skills/cosmic-build/knowledge/cosmic/platform/reportlistdata-plugin.md +130 -23
- package/skills/cosmic-build/knowledge/cosmic/platform/reporttreedata-plugin.md +48 -28
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/_index.md +12 -3
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/common/attachment-name-process.md +17 -13
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/common/dynamic-update-field.md +4 -4
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/filter-grid/filter-grid-set-value.md +1 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-basedatafield.md +2 -3
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-container-init.md +0 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-popup.md +0 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-value-parse.md +5 -6
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/list-show-form.md +1 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/query-field-dynamic.md +0 -7
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/business-data/delete-service.md +24 -29
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/ksql/db-batch.md +4 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/ksql/db-direct-access.md +4 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/qfilter/entity-object-access.md +8 -3
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/qfilter/qfilter-compose.md +6 -5
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/after-bind-data-set-visible.md +5 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/click-return-data.md +2 -2
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/closed-call-back-set-value.md +2 -2
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/register-listener.md +1 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/set-filter-tab-org.md +3 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/dispatch-service/microservice-dev.md +18 -11
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/dist-lock/dist-lock-basic.md +3 -6
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/mq/mq-subscribe.md +5 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/thread-pool/async-operation.md +10 -6
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/thread-pool/big-task-async.md +4 -2
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/thread-pool/schedule-task.md +9 -7
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/operation/operation-interactive.md +33 -34
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/operation/operation-plugin-events.md +30 -22
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-async-query.md +133 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-batch-query.md +26 -22
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-chart.md +112 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-dataset-assembly.md +200 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-head-filter.md +177 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-hyperlink-drill.md +216 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-list-data-query.md +12 -12
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-merge-column.md +44 -6
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-print-data.md +163 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-row-data-toolbar.md +188 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-subtotal-total.md +139 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-tree-data-query.md +108 -12
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-tree-list.md +181 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/system-service/report-plugin-interface.md +3 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-basedata-cascade-confirm.md +14 -11
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-before-item-validate.md +1 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-build-filter.md +1 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-multi-level-filter.md +11 -11
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-refresh-node.md +22 -15
- package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-algo.md +2 -2
- package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-entity-model.md +3 -4
- package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-exception.md +9 -5
- package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-orm-access.md +4 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-request-context.md +7 -3
- package/skills/cosmic-build/knowledge/cosmic/platform/standards.md +28 -3
- package/skills/cosmic-build/references/compile-guide.md +36 -1
- package/skills/cosmic-build/references/dcs-code-rules.md +180 -0
- package/skills/cosmic-build/references/delivery.md +16 -2
- package/skills/cosmic-build/references/execution-contract.md +15 -3
- package/skills/cosmic-build/references/java-plugin-delivery.md +21 -11
- package/skills/cosmic-build/references/plugin-handoff-input.md +82 -24
- package/skills/cosmic-build/references/plugin-runtime-routing.md +23 -6
- package/skills/cosmic-build/references/report-filter-contract.md +53 -15
- package/skills/cosmic-build/references/sdk-inspection.md +4 -1
- package/skills/cosmic-build/references/type-registry.json +26 -17
- package/skills/cosmic-build/schemas/data-model-authoring.schema.json +1 -1
- package/skills/cosmic-build/scripts/_http_client.py +3 -0
- package/skills/cosmic-build/scripts/compile_plugin_source.py +251 -42
- package/skills/cosmic-build/scripts/configure_list.py +112 -0
- package/skills/cosmic-build/scripts/dcs-rule-catalog.json +799 -0
- package/skills/cosmic-build/scripts/dcs_code_gate.py +1976 -0
- package/skills/cosmic-build/scripts/execution_binding.py +25 -0
- package/skills/cosmic-build/scripts/platform_api_contract.py +352 -7
- package/skills/cosmic-build/scripts/plugin_source_gate.py +9 -1
- package/skills/cosmic-build/scripts/plugin_target_sdk.py +12 -8
- package/skills/cosmic-build/scripts/prepare_plugin_handoff.py +488 -88
- package/skills/cosmic-build/scripts/query_plugin_handoff.py +207 -20
- package/skills/cosmic-build/scripts/register_plugins.py +27 -10
- package/skills/cosmic-build/scripts/render_output_cards.py +57 -20
- package/skills/cosmic-build/scripts/resolve_output_path.py +60 -26
- package/skills/cosmic-build/scripts/run_java_tests.py +162 -0
- package/skills/cosmic-build/scripts/sync_generated_locale_assets.py +28 -0
- package/skills/cosmic-build/scripts/test/fixtures/ReportSdkCheck.java +1 -22
- package/skills/cosmic-build/scripts/test/fixtures/list_column_property_declarations.json +887 -0
- package/skills/cosmic-build/scripts/test/fixtures/operation-entry-field-prepare.java +78 -0
- package/skills/cosmic-build/scripts/test/test_add_field_required_contract.py +12 -0
- package/skills/cosmic-build/scripts/test/test_batch5_drift_governance.py +23 -2
- package/skills/cosmic-build/scripts/test/test_compile_plugin_source.py +287 -4
- package/skills/cosmic-build/scripts/test/test_configure_list_delivery.py +132 -0
- package/skills/cosmic-build/scripts/test/test_cosmic_sdk_examples.py +58 -1
- package/skills/cosmic-build/scripts/test/test_data_model_contract.py +19 -0
- package/skills/cosmic-build/scripts/test/test_dcs_code_gate.py +901 -0
- package/skills/cosmic-build/scripts/test/test_delivery_stage_boundary.py +1 -1
- package/skills/cosmic-build/scripts/test/test_direct_cli_architecture.py +2 -0
- package/skills/cosmic-build/scripts/test/test_http_client.py +6 -0
- package/skills/cosmic-build/scripts/test/test_java_toolchain_feedback.py +206 -0
- package/skills/cosmic-build/scripts/test/test_kd_api.py +21 -0
- package/skills/cosmic-build/scripts/test/test_list_configuration_closure.py +184 -0
- package/skills/cosmic-build/scripts/test/test_mcp_cli.py +139 -2
- package/skills/cosmic-build/scripts/test/test_menu_creation_closure.py +16 -0
- package/skills/cosmic-build/scripts/test/test_modeling_input_recovery.py +55 -1
- package/skills/cosmic-build/scripts/test/test_open_world_cli_catalog.py +1 -1
- package/skills/cosmic-build/scripts/test/test_operation_field_preparation.py +84 -0
- package/skills/cosmic-build/scripts/test/test_platform_api_contract.py +322 -1
- package/skills/cosmic-build/scripts/test/test_plugin_handoff.py +951 -4
- package/skills/cosmic-build/scripts/test/test_plugin_source_gate.py +30 -2
- package/skills/cosmic-build/scripts/test/test_plugin_target_sdk.py +21 -0
- package/skills/cosmic-build/scripts/test/test_property_reroute.py +165 -0
- package/skills/cosmic-build/scripts/test/test_property_schema_first.py +35 -0
- package/skills/cosmic-build/scripts/test/test_query_plugin_handoff.py +221 -0
- package/skills/cosmic-build/scripts/test/test_register_plugins.py +76 -4
- package/skills/cosmic-build/scripts/test/test_render_output_cards.py +75 -3
- package/skills/cosmic-build/scripts/test/test_report_business_contract.py +27 -5
- package/skills/cosmic-build/scripts/test/test_report_doc_contract_sync.py +103 -11
- package/skills/cosmic-build/scripts/test/test_report_plugin_source_gate.py +46 -16
- package/skills/cosmic-build/scripts/test/test_report_properties.py +74 -15
- package/skills/cosmic-build/scripts/test/test_report_query_data_source.py +3 -1
- package/skills/cosmic-build/scripts/test/test_report_sdk_optimization.py +1 -2
- package/skills/cosmic-build/scripts/test/test_test_environment.py +76 -1
- package/skills/cosmic-build/scripts/test_environment.py +67 -1
- package/skills/cosmic-build/skills/cosmic-coding-impl/SKILL.md +52 -10
- package/skills/cosmic-build/skills/cosmic-meta-api/apis/ai_meta.md +16 -3
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/report-write-contracts.json +59 -26
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/rule-domain.json +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/report-write.schema.json +75 -18
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/pdm/scripts/pdm_auto.py +39 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/pdm/scripts/pdm_client.py +97 -10
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/verify/SKILL.md +10 -12
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_apps.py +3 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_formactions.py +38 -29
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_listui.py +400 -233
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_metaops.py +87 -45
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_properties.py +42 -15
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_rules.py +135 -5
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/config.py +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/execution_errors.py +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/formatters.py +17 -11
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/property_reroute.py +15 -5
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/report_ops.py +1429 -238
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/validators.py +56 -28
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/cosmic_api.py +380 -134
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/field_decorator.py +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/meta_export.py +12 -11
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/meta_verifier.py +42 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_add_field_placement_closure.py +8 -8
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_build_configuration_readback.py +94 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_build_receipt_resume.py +47 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_metadata_unchanged_fail_closed.py +87 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_dym_evidence.py +23 -23
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_entity_integrity.py +279 -4
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_field_guard.py +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_plugin_contract.py +509 -32
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_readback_derivation.py +178 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_recreate_plan.py +125 -10
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_serve_contract.py +208 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_typed_filters.py +259 -50
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_rule_inventory_perf.py +29 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_rule_write_closure.py +20 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_batch_post_build_recovery.py +18 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_batch_verification_evidence.py +35 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_identity_guard.py +25 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_time_range_rule.py +98 -0
- package/skills/cosmic-build/skills/cosmic-meta-knowledge/domain-model/report-model.md +20 -9
- package/skills/cosmic-build/skills/cosmic-meta-knowledge/generation/report-modeling-guide.md +55 -25
- package/skills/cosmic-build/skills/cosmic-meta-knowledge/property-guides/report-column-properties.md +15 -39
- package/skills/cosmic-build/skills/cosmic-meta-modeling/SKILL.md +19 -17
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/SKILL.md +82 -38
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/examples/golden-example-report-batch.md +64 -19
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/examples/golden-example-report-plugin.md +32 -20
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/examples/golden-example-report-tree.md +78 -31
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/report-artifact-schema.json +2 -1
- package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-companion-fields.md +3 -0
- package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-list-configuration.md +2 -0
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/data_model_contract.py +3 -1
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/data_model_emit.py +27 -4
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/report_validator.py +99 -44
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/test_report_examples.py +31 -4
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/test_report_validator.py +129 -29
- package/skills/cosmic-build/skills/cosmic-meta-modifier/SKILL.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 +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/report-op/SKILL.md +104 -25
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/SKILL.md +9 -0
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/verify/usage-guide.md +2 -2
- package/skills/cosmic-build/skills/cosmic-meta-modifier/references/list-filter-modification.md +39 -12
- package/skills/cosmic-build/skills/mcp-server/SKILL.md +39 -24
- package/skills/cosmic-build/skills/mcp-server/scripts/mcp_cli.py +20 -6
- package/skills/cosmic-dev-best-practices/SKILL.md +1 -1
- package/skills/cosmic-dev-best-practices/references/README.md +2 -2
- package/skills/cosmic-dev-best-practices/references/basedata-dev-convention.md +2 -2
- package/skills/cosmic-dev-best-practices/references/bill-dev-convention.md +4 -4
- package/skills/cosmic-dev-best-practices/references/design-document-convention.md +2 -2
- package/skills/cosmic-dev-best-practices/references/design-examples/basedata-design-example.md +4 -4
- package/skills/cosmic-dev-best-practices/references/design-examples/bill-design-example.md +13 -13
- package/skills/cosmic-dev-best-practices/references/elements/CustomerField.md +1 -1
- package/skills/cosmic-dev-best-practices/references/elements/README.md +1 -1
- package/skills/cosmic-dev-best-practices/references/elements/SupplierField.md +1 -1
- package/skills/cosmic-dev-best-practices/references/elements/common-field-property-conventions.md +109 -13
- package/skills/cosmic-dev-best-practices/references/layout-control-convention.md +1 -1
- package/skills/erp-analysis/references/delivery-contract.md +2 -1
- package/skills/erp-analysis/templates/AGENTS.template.md +3 -1
- package/skills/cosmic-build/assets/platform-report-filters/TypedReportFilterFields.java +0 -169
- package/skills/cosmic-build/assets/platform-report-filters/platform.patch +0 -98
- package/skills/cosmic-build/scripts/apply_report_filter_extension.py +0 -39
|
@@ -21,6 +21,31 @@ def execution_binding():
|
|
|
21
21
|
"cosmicApi": str(root / "skills" / "cosmic-meta-api" / "scripts" / "cosmic_api.py")}
|
|
22
22
|
|
|
23
23
|
|
|
24
|
+
def bound_entrypoint(app_root, relative):
|
|
25
|
+
"""Reuse the selected runtime across this installation's locale entries.
|
|
26
|
+
|
|
27
|
+
Do not treat translated executables as equivalent or follow arbitrary paths
|
|
28
|
+
from context. The selected executable still checks its own revision/env.
|
|
29
|
+
"""
|
|
30
|
+
context = Path(app_root) / "_runtime" / "context.json"
|
|
31
|
+
if not context.is_file():
|
|
32
|
+
return None
|
|
33
|
+
stored = json.loads(context.read_text(encoding="utf-8-sig"))
|
|
34
|
+
source = (stored.get("executionBinding") or {}).get("rootDir")
|
|
35
|
+
if not source:
|
|
36
|
+
return None
|
|
37
|
+
current = Path(__file__).resolve().parents[1]
|
|
38
|
+
selected = Path(source).resolve()
|
|
39
|
+
package = lambda root: root.parent.parent if root.parent.name == ".locales" else root
|
|
40
|
+
if current == selected or package(current) != package(selected):
|
|
41
|
+
return None
|
|
42
|
+
target = (selected / relative).resolve()
|
|
43
|
+
target.relative_to(selected)
|
|
44
|
+
if not target.is_file():
|
|
45
|
+
raise ValueError("SKILL_BOUND_ENTRY_MISSING: selected installed source lacks " + relative)
|
|
46
|
+
return target
|
|
47
|
+
|
|
48
|
+
|
|
24
49
|
def validate_execution_binding(app_root):
|
|
25
50
|
context = Path(app_root) / "_runtime" / "context.json"
|
|
26
51
|
if not context.is_file():
|
|
@@ -147,7 +147,7 @@ _SIGNATURE_RE = re.compile(
|
|
|
147
147
|
# "继承自 IEntryOperate(分录行操作……)") cannot reach the sentence's period through the names
|
|
148
148
|
# charset, so the whole match used to fail and the class looked like it had no supertypes at all.
|
|
149
149
|
_SUPERTYPE_RE = re.compile(
|
|
150
|
-
r"(?:
|
|
150
|
+
r"(?:[Ii]nherits from|[Ee]xtends|implements|继承自|实现)\s*"
|
|
151
151
|
r"(?P<names>[\w.,、,\s]+?)"
|
|
152
152
|
r"(?=[.。()();;]|$)")
|
|
153
153
|
_TYPE_TOKEN_RE = re.compile(r"[A-Z][A-Za-z0-9_.$]*")
|
|
@@ -310,7 +310,7 @@ def describe_api(selector: str, members: list[str] | None = None) -> dict:
|
|
|
310
310
|
"evidenceScope": "Registered declarations and documented supertypes; undocumented members are not proof of SDK absence."}
|
|
311
311
|
|
|
312
312
|
|
|
313
|
-
def plugin_coding_evidence(plugin_type: str, scenes: list[str] | None = None) -> dict:
|
|
313
|
+
def plugin_coding_evidence(plugin_type: str, scenes: list[str] | None = None, *, core_logic: list[str] | None = None, hit_modules: list[str] | None = None) -> dict:
|
|
314
314
|
"""Resolve coding references from the existing type and API registries."""
|
|
315
315
|
registry = json.loads(TYPE_REGISTRY_PAGE.read_text(encoding="utf-8-sig"))
|
|
316
316
|
definition = next((item for item in registry["types"] if item["type"] == plugin_type), None)
|
|
@@ -330,6 +330,8 @@ def plugin_coding_evidence(plugin_type: str, scenes: list[str] | None = None) ->
|
|
|
330
330
|
"arguments": ["--class", "<receiver class>", "--sdk-dir", "<bound target SDK directory>",
|
|
331
331
|
"--member", "<required member>"]},
|
|
332
332
|
}
|
|
333
|
+
evidence["specializedReferences"] = {name: str(root / registry["specializedStandards"][name])
|
|
334
|
+
for name in hit_modules or [] if name in registry.get("specializedStandards", {})}
|
|
333
335
|
if plugin_type == "operation":
|
|
334
336
|
evidence["operationDispatch"] = {
|
|
335
337
|
"transactionEvent": {"class": "kd.bos.entity.plugin.args.OperationArgs", "accessor": "event.getOperationKey()",
|
|
@@ -397,11 +399,28 @@ def plugin_coding_evidence(plugin_type: str, scenes: list[str] | None = None) ->
|
|
|
397
399
|
for owner, member in re.findall(r"\b([A-Z][A-Za-z0-9_]*)\.([a-z][A-Za-z0-9_]*)\s*\(", content):
|
|
398
400
|
if _page_by_simple_name(owner):
|
|
399
401
|
apis.setdefault(owner, set()).add(member)
|
|
402
|
+
# Only exact class/member names already supplied by the task select contracts.
|
|
403
|
+
for name in dict.fromkeys(re.findall(r"[A-Za-z_$][A-Za-z0-9_$]*(?:\.[A-Za-z_$][A-Za-z0-9_$]*)*", "\n".join(core_logic or []))):
|
|
404
|
+
owner, separator, member = name.rpartition(".")
|
|
405
|
+
page = _page(name) if name.startswith("kd.bos.") else _page_by_simple_name(name)
|
|
406
|
+
if page:
|
|
407
|
+
apis.setdefault(page["fqcn"], set())
|
|
408
|
+
continue
|
|
409
|
+
page = (_page(owner) if owner.startswith("kd.bos.") else _page_by_simple_name(owner)) if separator else None
|
|
410
|
+
if page:
|
|
411
|
+
apis.setdefault(page["fqcn"], set()).add(member)
|
|
400
412
|
if selected:
|
|
401
413
|
evidence["scenes"] = selected
|
|
414
|
+
if apis:
|
|
415
|
+
canonical = {}
|
|
416
|
+
for owner, members in apis.items():
|
|
417
|
+
contract = describe_api(owner)
|
|
418
|
+
if contract.get("status") is not True:
|
|
419
|
+
continue
|
|
420
|
+
name = contract["class"]
|
|
421
|
+
canonical[name] = (canonical.get(name, set()) | members) if name not in canonical or (canonical[name] and members) else set()
|
|
402
422
|
evidence["apiContracts"] = [describe_api(owner, sorted(members))
|
|
403
|
-
for owner, members in sorted(
|
|
404
|
-
and _page_by_simple_name(owner)["fqcn"] != base]
|
|
423
|
+
for owner, members in sorted(canonical.items()) if owner != base]
|
|
405
424
|
for contract in evidence["apiContracts"]:
|
|
406
425
|
contract["members"] = [{key: value for key, value in item.items()
|
|
407
426
|
if key != "notes" or item["deprecated"]}
|
|
@@ -586,6 +605,94 @@ def _arguments(code: str, open_paren: int) -> list[str] | None:
|
|
|
586
605
|
return None
|
|
587
606
|
|
|
588
607
|
|
|
608
|
+
def operation_call_findings(source_text: str, entity_numbers: tuple[str, ...] = ()) -> tuple[list[dict], list[dict]]:
|
|
609
|
+
"""检查操作调用的参数角色;仅阻断已有实体证据的颠倒,不把缺少文档当作 API 不存在。"""
|
|
610
|
+
code = stripped(source_text)
|
|
611
|
+
source = commentless(source_text)
|
|
612
|
+
imports = _imports(code)
|
|
613
|
+
helpers = {
|
|
614
|
+
"kd.bos.servicehelper.operation.OperationServiceHelper": "executeOperate",
|
|
615
|
+
"kd.bos.servicehelper.operation.DeleteServiceHelper": "deleteOperate",
|
|
616
|
+
}
|
|
617
|
+
calls = []
|
|
618
|
+
for match in re.finditer(
|
|
619
|
+
r"(?<![\w$.])(?P<owner>kd\.bos\.[\w$.]+|[A-Z][\w$]*)\s*\.\s*(?P<member>\w+)\s*\(", code):
|
|
620
|
+
owner = match["owner"] if match["owner"].startswith(PLATFORM_PACKAGE) else imports.get(match["owner"])
|
|
621
|
+
if owner not in helpers and owner != "kd.bos.servicehelper.BusinessDataServiceHelper":
|
|
622
|
+
continue
|
|
623
|
+
arguments = _arguments(source, match.end() - 1)
|
|
624
|
+
if arguments is not None:
|
|
625
|
+
calls.append((owner, match["member"], arguments, match.start()))
|
|
626
|
+
# 静态导入须解析到同一 SDK 类;本地同名方法会遮蔽它,不能按平台调用检查。
|
|
627
|
+
local_methods = {body["name"] for body in method_bodies(source_text)}
|
|
628
|
+
for match in re.finditer(r"\bimport\s+static\s+(kd\.bos\.[\w$.]+)\.(\w+)\s*;", code):
|
|
629
|
+
if helpers.get(match[1]) != match[2] or match[2] in local_methods:
|
|
630
|
+
continue
|
|
631
|
+
for call in re.finditer(r"(?<![\w$.])" + re.escape(match[2]) + r"\s*\(", code):
|
|
632
|
+
arguments = _arguments(source, call.end() - 1)
|
|
633
|
+
if arguments is not None:
|
|
634
|
+
calls.append((match[1], match[2], arguments, call.start()))
|
|
635
|
+
|
|
636
|
+
constants = {}
|
|
637
|
+
for match in re.finditer(r'\bfinal\s+String\s+([\w$]+)\s*=\s*("(?:[^"\\]|\\.)*")\s*;', source):
|
|
638
|
+
# 同名局部变量、字段或参数存在时,不跨作用域猜测常量值。
|
|
639
|
+
if code[match.start():match.start() + 5] == "final" and len(re.findall(
|
|
640
|
+
r"\bString\s+" + re.escape(match[1]) + r"\b", code)) == 1:
|
|
641
|
+
constants[match[1]] = match[2]
|
|
642
|
+
|
|
643
|
+
def value(expression):
|
|
644
|
+
token = expression.strip()
|
|
645
|
+
while token.startswith("(") and _call_end(token, 0) == len(token):
|
|
646
|
+
token = token[1:-1].strip()
|
|
647
|
+
token = constants.get(token, token)
|
|
648
|
+
if not re.fullmatch(r'"(?:[^"\\]|\\.)*"', token):
|
|
649
|
+
return None
|
|
650
|
+
try:
|
|
651
|
+
return json.loads(token)
|
|
652
|
+
except ValueError:
|
|
653
|
+
return None
|
|
654
|
+
|
|
655
|
+
entities = set(entity_numbers)
|
|
656
|
+
# loadSingle 的第二个参数是实体名或类型;能解析为字符串时提供实体角色证据。
|
|
657
|
+
for owner, member, arguments, _ in calls:
|
|
658
|
+
if owner == "kd.bos.servicehelper.BusinessDataServiceHelper" and member == "loadSingle" and len(arguments) >= 2:
|
|
659
|
+
entity = value(arguments[1])
|
|
660
|
+
if entity is not None:
|
|
661
|
+
entities.add(entity)
|
|
662
|
+
issues = []
|
|
663
|
+
warnings = []
|
|
664
|
+
# 仅识别文档示例已有的标准操作;自定义操作不从变量名、前缀或相似名字推断。
|
|
665
|
+
standard_keys = {"save", "submit", "audit", "unaudit", "delete"}
|
|
666
|
+
for owner, member, arguments, offset in calls:
|
|
667
|
+
if helpers.get(owner) != member:
|
|
668
|
+
continue
|
|
669
|
+
page = _page(owner)
|
|
670
|
+
signatures = page["signatures"].get(member, set()) if page else set()
|
|
671
|
+
if not signatures:
|
|
672
|
+
continue
|
|
673
|
+
finding = {"line": code.count("\n", 0, offset) + 1, "call": owner + "." + member,
|
|
674
|
+
"reference": str(page["path"]),
|
|
675
|
+
"registeredSignatures": sorted(member + "(" + ", ".join(params) + ")" for params in signatures)}
|
|
676
|
+
if len(arguments) not in {len(params) for params in signatures}:
|
|
677
|
+
warnings.append({**finding, "severity": "warning", "errorCode": "PLUGIN_SOURCE_OPERATION_OVERLOAD_UNVERIFIED",
|
|
678
|
+
"argumentCount": len(arguments),
|
|
679
|
+
"message": "操作调用的参数数量不匹配已收录签名。executeOperate 的已收录重载需要第四个 OperateOption;不能套用其他 helper 的重载。未收录不等于不存在,使用绑定 SDK 查证并编译;compile=false 不证明该调用合法。"})
|
|
680
|
+
# deleteOperate(entityName, pks, option) 是已收录的合法三参数重载,无操作键参数。
|
|
681
|
+
if owner.endswith("DeleteServiceHelper") and len(arguments) == 3 or len(arguments) < 2:
|
|
682
|
+
continue
|
|
683
|
+
operation, entity = value(arguments[0]), value(arguments[1])
|
|
684
|
+
if entity not in standard_keys or operation is None or operation == entity or operation in standard_keys:
|
|
685
|
+
continue
|
|
686
|
+
message = "操作调用应为 (operationKey, entityNumber, ids/dataEntities, option),不能先传实体编码再传操作编码;两个参数都是 String,javac 无法验证角色顺序。"
|
|
687
|
+
if operation in entities:
|
|
688
|
+
issues.append({**finding, "errorCode": "PLUGIN_SOURCE_OPERATION_ARGUMENT_ORDER",
|
|
689
|
+
"operationArgument": arguments[0], "entityArgument": arguments[1], "message": message})
|
|
690
|
+
continue
|
|
691
|
+
warnings.append({**finding, "severity": "warning", "errorCode": "PLUGIN_SOURCE_OPERATION_ARGUMENT_ORDER_UNVERIFIED",
|
|
692
|
+
"message": message + " 当前缺少首参的实体证据,按目标表单操作定义核实,不自动交换参数。"})
|
|
693
|
+
return issues, warnings
|
|
694
|
+
|
|
695
|
+
|
|
589
696
|
def filter_operator_issues(source_text: str) -> list[dict]:
|
|
590
697
|
"""QFilter comparison operators written as a string literal instead of a QCP constant."""
|
|
591
698
|
constants = _qcp_constants()
|
|
@@ -850,6 +957,220 @@ def scene_misuse_issues(source_text: str) -> list[dict]:
|
|
|
850
957
|
return issues
|
|
851
958
|
|
|
852
959
|
|
|
960
|
+
# Report form callbacks the platform delivers only to a plugin that added itself as the control's
|
|
961
|
+
# listener. Verified against the 8.0 SDK bytecode: AbstractFormPlugin.registerListener is empty and
|
|
962
|
+
# AbstractReportFormPlugin does not override it; AbstractGrid's hyperlink and cell listener lists
|
|
963
|
+
# and Toolbar's item listener list are filled by their add* methods alone, and neither ReportList
|
|
964
|
+
# nor ReportView calls one for the plugin. An override without its add* call compiles, registers
|
|
965
|
+
# and opens, then never fires: the drill-down link or the toolbar button does nothing, and nothing
|
|
966
|
+
# is logged.
|
|
967
|
+
_REPORT_FORM_BASE = "kd.bos.report.plugin.AbstractReportFormPlugin"
|
|
968
|
+
_REPORT_DATA_BASE = "kd.bos.entity.report.AbstractReportListDataPlugin"
|
|
969
|
+
_REPORT_LISTENER_RULES = (
|
|
970
|
+
{
|
|
971
|
+
"callbacks": ("hyperLinkClick",),
|
|
972
|
+
"registration": re.compile(r"\baddHyperClickListener\s*\("),
|
|
973
|
+
"add": "addHyperClickListener",
|
|
974
|
+
"example": 'ReportList reportList = this.getControl("reportlistap"); '
|
|
975
|
+
"reportList.addHyperClickListener(this);",
|
|
976
|
+
},
|
|
977
|
+
{
|
|
978
|
+
"callbacks": ("itemClick", "beforeItemClick"),
|
|
979
|
+
"registration": re.compile(r"\baddItemClickListeners?\s*\("),
|
|
980
|
+
"add": "addItemClickListeners",
|
|
981
|
+
"example": 'this.addItemClickListeners("toolbarap");',
|
|
982
|
+
},
|
|
983
|
+
{
|
|
984
|
+
"callbacks": ("cellClick", "cellDoubleClick"),
|
|
985
|
+
"registration": re.compile(r"\baddCellClickListener\s*\("),
|
|
986
|
+
"add": "addCellClickListener",
|
|
987
|
+
"example": 'ReportList reportList = this.getControl("reportlistap"); '
|
|
988
|
+
"reportList.addCellClickListener(this);",
|
|
989
|
+
},
|
|
990
|
+
)
|
|
991
|
+
|
|
992
|
+
|
|
993
|
+
def _outermost_base_fqcn(code: str) -> str:
|
|
994
|
+
declared = _outermost_base_class(code)
|
|
995
|
+
return _resolved_base_import(_imports(code), declared[1]) if declared else ""
|
|
996
|
+
|
|
997
|
+
|
|
998
|
+
def report_listener_issues(source_text: str) -> list[dict]:
|
|
999
|
+
"""A report form callback overridden by a plugin that never adds itself as its listener.
|
|
1000
|
+
|
|
1001
|
+
Judged only for a class that directly extends AbstractReportFormPlugin: a project base class
|
|
1002
|
+
may register on the subclass's behalf, and a listener passed as a lambda or an anonymous class
|
|
1003
|
+
is not an override at all. An add* call anywhere in the class counts — one placed in
|
|
1004
|
+
initialize() is already rejected by the lifecycle rule.
|
|
1005
|
+
"""
|
|
1006
|
+
code = stripped(source_text)
|
|
1007
|
+
if PLATFORM_PACKAGE not in code or _outermost_base_fqcn(code) != _REPORT_FORM_BASE:
|
|
1008
|
+
return []
|
|
1009
|
+
bodies = {body["name"]: body for body in method_bodies(source_text)}
|
|
1010
|
+
issues: list[dict] = []
|
|
1011
|
+
for rule in _REPORT_LISTENER_RULES:
|
|
1012
|
+
if rule["registration"].search(code):
|
|
1013
|
+
continue
|
|
1014
|
+
for name in rule["callbacks"]:
|
|
1015
|
+
if name not in bodies:
|
|
1016
|
+
continue
|
|
1017
|
+
issues.append({
|
|
1018
|
+
"line": code[: bodies[name]["start"]].count("\n") + 1,
|
|
1019
|
+
"callback": name,
|
|
1020
|
+
"requiredCall": rule["add"],
|
|
1021
|
+
"message": (
|
|
1022
|
+
f"{name}() is never called: the platform delivers it only to a plugin that "
|
|
1023
|
+
f"added itself as the control's listener, and this class never calls "
|
|
1024
|
+
f"{rule['add']}. Add the call in registerListener(EventObject), for example "
|
|
1025
|
+
f"{rule['example']}"
|
|
1026
|
+
),
|
|
1027
|
+
})
|
|
1028
|
+
return issues
|
|
1029
|
+
|
|
1030
|
+
|
|
1031
|
+
_QUERY_DECLARATION_RE = re.compile(
|
|
1032
|
+
r"\bquery\s*\(\s*(?:final\s+)?(?:[\w$]+\s*\.\s*)*ReportQueryParam\s+[\w$]+\s*,\s*"
|
|
1033
|
+
r"(?:final\s+)?(?:java\s*\.\s*lang\s*\.\s*)?Object\s+(?P<selected>[\w$]+)\s*\)")
|
|
1034
|
+
_BATCH_READ_RE = re.compile(r"\b(?:getCurrentBatchRows|byBatchInfo)\s*\(")
|
|
1035
|
+
_RETURNS_VALUE_RE = re.compile(r"\breturn\s+(?!null\s*;)")
|
|
1036
|
+
|
|
1037
|
+
|
|
1038
|
+
def report_batch_issues(source_text: str) -> list[dict]:
|
|
1039
|
+
"""A batched report data plugin whose query() returns the whole result for every batch.
|
|
1040
|
+
|
|
1041
|
+
A non-null queryBatchBy() result switches batching on: the platform splits its basis rows by
|
|
1042
|
+
countPerBatch and calls query() once per batch, handing over the current batch as selectedObj
|
|
1043
|
+
and as queryParam.byBatchInfo().getCurrentBatchRows(). A query() that reads neither returns
|
|
1044
|
+
the full result each time, and the report shows every row once per batch. A byBatchInfo()
|
|
1045
|
+
call anywhere outside queryBatchBy() counts as reading the batch, so a helper method that
|
|
1046
|
+
narrows the query is accepted without being followed.
|
|
1047
|
+
"""
|
|
1048
|
+
code = stripped(source_text)
|
|
1049
|
+
if PLATFORM_PACKAGE not in code or _outermost_base_fqcn(code) != _REPORT_DATA_BASE:
|
|
1050
|
+
return []
|
|
1051
|
+
bodies = method_bodies(source_text)
|
|
1052
|
+
batch = next((body for body in bodies if body["name"] == "queryBatchBy"), None)
|
|
1053
|
+
if batch is None or not _RETURNS_VALUE_RE.search(code[batch["start"]:batch["end"]]):
|
|
1054
|
+
return []
|
|
1055
|
+
if _BATCH_READ_RE.search(code[:batch["start"]] + code[batch["end"]:]):
|
|
1056
|
+
return []
|
|
1057
|
+
declaration = _QUERY_DECLARATION_RE.search(code)
|
|
1058
|
+
query = min((body for body in bodies
|
|
1059
|
+
if declaration and body["name"] == "query" and body["start"] > declaration.end()),
|
|
1060
|
+
key=lambda body: body["start"], default=None)
|
|
1061
|
+
if query is None:
|
|
1062
|
+
return []
|
|
1063
|
+
selected = declaration.group("selected")
|
|
1064
|
+
if re.search(rf"\b{re.escape(selected)}\b", code[query["start"]:query["end"]]):
|
|
1065
|
+
return []
|
|
1066
|
+
return [{
|
|
1067
|
+
"line": code[: query["start"]].count("\n") + 1,
|
|
1068
|
+
"method": "query",
|
|
1069
|
+
"message": (
|
|
1070
|
+
"queryBatchBy() returns batch basis rows, so the platform calls query() once per "
|
|
1071
|
+
f"batch, but query() reads neither its {selected} argument nor "
|
|
1072
|
+
"queryParam.byBatchInfo().getCurrentBatchRows(): every batch returns the whole result "
|
|
1073
|
+
"and the report shows each row once per batch. Narrow query() to the current batch, "
|
|
1074
|
+
"for example with an in filter on the basis values getCurrentBatchRows() returns, or "
|
|
1075
|
+
"return null from queryBatchBy() when the report is not batched"
|
|
1076
|
+
),
|
|
1077
|
+
}]
|
|
1078
|
+
|
|
1079
|
+
|
|
1080
|
+
# Names report examples written against a later release use that the 8.0 SDK has nowhere: none of
|
|
1081
|
+
# the 326,774 classes in the 1,016 jars of an 8.0 lib/bos + lib/trd install mentions them, as a
|
|
1082
|
+
# declaration or as a call, so no 8.0 receiver can carry them. They are warnings rather than
|
|
1083
|
+
# findings because a later target SDK may add them, and the target's own javac settles it when
|
|
1084
|
+
# compilation runs; with compilation skipped on an 8.0 target the class registers and then fails
|
|
1085
|
+
# to load. A source that declares the name itself is left alone.
|
|
1086
|
+
_SDK_ABSENT_NAMES = {
|
|
1087
|
+
"setAsyncDataCount": (
|
|
1088
|
+
"8.0 has no asynchronous row count; ReportList.setAsyncTotal(boolean) defers the total "
|
|
1089
|
+
"line only"),
|
|
1090
|
+
"setCol2ndResultMode": (
|
|
1091
|
+
"8.0 TreeReportListEvent offers setTreeReportList(boolean) and setTreeExpandColId(String) "
|
|
1092
|
+
"only, and no flat header-filter result mode"),
|
|
1093
|
+
"TreeDataMode": "8.0 has no TreeDataMode type",
|
|
1094
|
+
"getFilterQFilters": "use FilterInfo.getQFilters()",
|
|
1095
|
+
}
|
|
1096
|
+
_ABSENT_NAME_RE = re.compile(r"\b(?P<name>" + "|".join(_SDK_ABSENT_NAMES) + r")\b")
|
|
1097
|
+
# FilterInfo declares no getCustomFilter in 8.0; the custom filter list is ReportQueryParam's.
|
|
1098
|
+
_FILTER_CUSTOM_FILTER_RE = re.compile(r"\bgetFilter\s*\(\s*\)\s*\.\s*getCustomFilter\s*\(")
|
|
1099
|
+
|
|
1100
|
+
|
|
1101
|
+
def sdk_absent_name_warnings(source_text: str) -> list[dict]:
|
|
1102
|
+
"""Report API names the 8.0 SDK does not have, each with what 8.0 offers instead."""
|
|
1103
|
+
code = stripped(source_text)
|
|
1104
|
+
if PLATFORM_PACKAGE not in code:
|
|
1105
|
+
return []
|
|
1106
|
+
own = set(re.findall(r"\b(?:class|interface|enum)\s+([\w$]+)", code))
|
|
1107
|
+
own |= {body["name"] for body in method_bodies(source_text)}
|
|
1108
|
+
warnings: list[dict] = []
|
|
1109
|
+
for match in _ABSENT_NAME_RE.finditer(code):
|
|
1110
|
+
name = match.group("name")
|
|
1111
|
+
if name in own:
|
|
1112
|
+
continue
|
|
1113
|
+
warnings.append({
|
|
1114
|
+
"line": code[: match.start()].count("\n") + 1,
|
|
1115
|
+
"name": name,
|
|
1116
|
+
"severity": "warning",
|
|
1117
|
+
"errorCode": "PLUGIN_SOURCE_ABSENT_FROM_8_0_SDK",
|
|
1118
|
+
"message": (
|
|
1119
|
+
f"{name} is absent from the 8.0 SDK: {_SDK_ABSENT_NAMES[name]}. On an 8.0 target "
|
|
1120
|
+
"the source fails to compile, or with compilation skipped the class fails to "
|
|
1121
|
+
"load; keep it only after references/sdk-inspection.md finds it on the target SDK"
|
|
1122
|
+
),
|
|
1123
|
+
})
|
|
1124
|
+
if "kd.bos.entity.report." in code:
|
|
1125
|
+
for match in _FILTER_CUSTOM_FILTER_RE.finditer(code):
|
|
1126
|
+
warnings.append({
|
|
1127
|
+
"line": code[: match.start()].count("\n") + 1,
|
|
1128
|
+
"name": "FilterInfo.getCustomFilter",
|
|
1129
|
+
"severity": "warning",
|
|
1130
|
+
"errorCode": "PLUGIN_SOURCE_ABSENT_FROM_8_0_SDK",
|
|
1131
|
+
"message": (
|
|
1132
|
+
"FilterInfo declares no getCustomFilter in the 8.0 SDK; the custom filter list "
|
|
1133
|
+
"is queryParam.getCustomFilter(), on ReportQueryParam"
|
|
1134
|
+
),
|
|
1135
|
+
})
|
|
1136
|
+
warnings.sort(key=lambda item: item["line"])
|
|
1137
|
+
return warnings
|
|
1138
|
+
|
|
1139
|
+
|
|
1140
|
+
_SOURCE_CAST_RE = re.compile(
|
|
1141
|
+
r"\(\s*(?P<type>(?!Object\b)[A-Z][\w$]*)\s*\)\s*\(?\s*[\w$]+\s*\.\s*getSource\s*\(\s*\)")
|
|
1142
|
+
|
|
1143
|
+
|
|
1144
|
+
def report_source_cast_warnings(source_text: str) -> list[dict]:
|
|
1145
|
+
"""A packageData() that casts the event source without checking its type first.
|
|
1146
|
+
|
|
1147
|
+
packageData fires once for every cell, and the report examples guard the cast with
|
|
1148
|
+
`instanceof ReportColumn`; an unguarded cast throws ClassCastException on the first cell whose
|
|
1149
|
+
source is another type, which fails the whole page rather than one cell.
|
|
1150
|
+
"""
|
|
1151
|
+
code = stripped(source_text)
|
|
1152
|
+
if PLATFORM_PACKAGE not in code or _outermost_base_fqcn(code) != _REPORT_FORM_BASE:
|
|
1153
|
+
return []
|
|
1154
|
+
warnings: list[dict] = []
|
|
1155
|
+
for body in method_bodies(source_text):
|
|
1156
|
+
segment = code[body["start"]:body["end"]]
|
|
1157
|
+
if body["name"] != "packageData" or re.search(r"\binstanceof\b", segment):
|
|
1158
|
+
continue
|
|
1159
|
+
cast = _SOURCE_CAST_RE.search(segment)
|
|
1160
|
+
if cast:
|
|
1161
|
+
warnings.append({
|
|
1162
|
+
"line": code[: body["start"] + cast.start()].count("\n") + 1,
|
|
1163
|
+
"severity": "warning",
|
|
1164
|
+
"errorCode": "PLUGIN_REPORT_SOURCE_CAST_UNGUARDED",
|
|
1165
|
+
"message": (
|
|
1166
|
+
f"packageData() casts getSource() to {cast.group('type')} without an "
|
|
1167
|
+
"instanceof check; it fires for every cell, so test the type first, as the "
|
|
1168
|
+
"report examples do with `if (source instanceof ReportColumn)`"
|
|
1169
|
+
),
|
|
1170
|
+
})
|
|
1171
|
+
return warnings
|
|
1172
|
+
|
|
1173
|
+
|
|
853
1174
|
def _class_bodies(code: str) -> list[dict]:
|
|
854
1175
|
"""Locate named and anonymous class scopes in comment/literal-free Java."""
|
|
855
1176
|
pattern = re.compile(
|
|
@@ -1407,9 +1728,21 @@ def main() -> int:
|
|
|
1407
1728
|
description="Check platform API contracts in one Java plugin source")
|
|
1408
1729
|
mode = parser.add_mutually_exclusive_group(required=True)
|
|
1409
1730
|
mode.add_argument("--source-path")
|
|
1731
|
+
mode.add_argument("--describe-plugin-type", help="Exact runtime plugin type; returns its guide and base contract")
|
|
1410
1732
|
mode.add_argument("--describe-class", action="append", help="Exact SDK class name or fully qualified name; repeat to batch lookups")
|
|
1411
1733
|
parser.add_argument("--member", action="append", help="Limit API evidence to named members")
|
|
1412
1734
|
args = parser.parse_args()
|
|
1735
|
+
if args.describe_plugin_type:
|
|
1736
|
+
registry = json.loads(TYPE_REGISTRY_PAGE.read_text(encoding="utf-8-sig"))
|
|
1737
|
+
entry = next((item for item in registry.get("types", []) if item.get("type") == args.describe_plugin_type), None)
|
|
1738
|
+
if not entry:
|
|
1739
|
+
print(json.dumps({"status": False, "errorCode": "PLUGIN_TYPE_UNSUPPORTED", "type": args.describe_plugin_type}))
|
|
1740
|
+
return 2
|
|
1741
|
+
root = TYPE_REGISTRY_PAGE.parent.parent
|
|
1742
|
+
print(json.dumps({"status": True, "type": entry["type"], "codingEntry": str(root / "skills/cosmic-coding-impl/SKILL.md"), "baseClass": entry.get("baseImport"),
|
|
1743
|
+
"guide": str(root / entry["guide"]) if entry.get("guide") else None,
|
|
1744
|
+
"sceneIndex": str(root / "knowledge/cosmic/platform/scenes/_index.md")}, ensure_ascii=False))
|
|
1745
|
+
return 0
|
|
1413
1746
|
if args.describe_class:
|
|
1414
1747
|
results = [describe_api(selector, args.member) for selector in args.describe_class]
|
|
1415
1748
|
print(json.dumps({"status": all(item["status"] for item in results), "classes": results}, ensure_ascii=False))
|
|
@@ -1423,16 +1756,21 @@ def main() -> int:
|
|
|
1423
1756
|
}, ensure_ascii=False))
|
|
1424
1757
|
return 2
|
|
1425
1758
|
source_text = source.read_text(encoding="utf-8-sig", errors="replace")
|
|
1759
|
+
operation_issues, operation_warnings = operation_call_findings(source_text)
|
|
1426
1760
|
mismatches = return_type_issues(source_text)
|
|
1427
1761
|
operators = filter_operator_issues(source_text)
|
|
1428
1762
|
missing = forbidden_member_issues(source_text)
|
|
1429
1763
|
misplaced = scene_misuse_issues(source_text)
|
|
1764
|
+
unregistered = report_listener_issues(source_text)
|
|
1765
|
+
unbatched = report_batch_issues(source_text)
|
|
1430
1766
|
bases = base_class_issues(source_text)
|
|
1431
1767
|
overrides = override_signature_issues(source_text)
|
|
1432
1768
|
remembered_imports = unverified_import_issues(source_text)
|
|
1433
1769
|
remembered_members = unverified_member_issues(source_text)
|
|
1434
1770
|
error_code = "0"
|
|
1435
|
-
if
|
|
1771
|
+
if operation_issues:
|
|
1772
|
+
error_code = operation_issues[0]["errorCode"]
|
|
1773
|
+
elif mismatches:
|
|
1436
1774
|
error_code = "PLUGIN_SOURCE_RETURN_TYPE_MISMATCH"
|
|
1437
1775
|
elif operators:
|
|
1438
1776
|
error_code = "PLUGIN_SOURCE_QFILTER_STRING_OPERATOR"
|
|
@@ -1440,6 +1778,10 @@ def main() -> int:
|
|
|
1440
1778
|
error_code = "PLUGIN_SOURCE_NONEXISTENT_MEMBER"
|
|
1441
1779
|
elif misplaced:
|
|
1442
1780
|
error_code = "PLUGIN_SOURCE_LIFECYCLE_MISUSE"
|
|
1781
|
+
elif unregistered:
|
|
1782
|
+
error_code = "PLUGIN_SOURCE_LISTENER_UNREGISTERED"
|
|
1783
|
+
elif unbatched:
|
|
1784
|
+
error_code = "PLUGIN_SOURCE_BATCH_NOT_NARROWED"
|
|
1443
1785
|
elif bases:
|
|
1444
1786
|
error_code = "PLUGIN_SOURCE_BASE_CLASS_MISMATCH"
|
|
1445
1787
|
elif overrides:
|
|
@@ -1451,8 +1793,11 @@ def main() -> int:
|
|
|
1451
1793
|
"errorCode": error_code,
|
|
1452
1794
|
"sourcePath": str(source.resolve()),
|
|
1453
1795
|
"issues": (
|
|
1454
|
-
mismatches + operators + missing + misplaced +
|
|
1455
|
-
+ remembered_imports + remembered_members
|
|
1796
|
+
operation_issues + mismatches + operators + missing + misplaced + unregistered + unbatched + bases
|
|
1797
|
+
+ overrides + remembered_imports + remembered_members
|
|
1798
|
+
),
|
|
1799
|
+
"sourceWarnings": (
|
|
1800
|
+
operation_warnings + sdk_absent_name_warnings(source_text) + report_source_cast_warnings(source_text)
|
|
1456
1801
|
),
|
|
1457
1802
|
}, ensure_ascii=False))
|
|
1458
1803
|
return 0 if error_code == "0" else 2
|
|
@@ -81,7 +81,8 @@ def validate_registration_entry(entry, target_type, class_name=None):
|
|
|
81
81
|
if not source.is_absolute() or not source.is_file():
|
|
82
82
|
raise ValueError("PLUGIN_LOCAL_SOURCE_UNVERIFIED: resolve the archive source path before registration")
|
|
83
83
|
from java_source import stripped
|
|
84
|
-
|
|
84
|
+
source_text = source.read_text(encoding="utf-8-sig")
|
|
85
|
+
text = stripped(source_text)
|
|
85
86
|
declaration = re.search(r"\bclass\s+" + re.escape(class_name.rsplit(".", 1)[-1])
|
|
86
87
|
+ r"\b([^{}]*)\{", text)
|
|
87
88
|
if declaration is None:
|
|
@@ -98,6 +99,13 @@ def validate_registration_entry(entry, target_type, class_name=None):
|
|
|
98
99
|
actual = hashlib.sha256(source.read_bytes()).hexdigest()
|
|
99
100
|
if entry.get("sourceHash") != actual:
|
|
100
101
|
raise ValueError("PLUGIN_SOURCE_CHANGED: source differs from its compile result; compile the current source again")
|
|
102
|
+
# 旧版本的成功回执不包含参数角色门禁;注册前重验,避免沿用历史误放行的源码。
|
|
103
|
+
from platform_api_contract import operation_call_findings
|
|
104
|
+
issues, warnings = operation_call_findings(source_text, (str(entry.get("formNumber") or ""),))
|
|
105
|
+
blockers = issues + ([item for item in warnings if item["errorCode"] == "PLUGIN_SOURCE_OPERATION_OVERLOAD_UNVERIFIED"]
|
|
106
|
+
if entry["compileStatus"] == "skipped-by-config" else [])
|
|
107
|
+
if blockers:
|
|
108
|
+
raise ValueError(blockers[0]["errorCode"] + ": " + blockers[0]["message"])
|
|
101
109
|
return {"compileStatus": entry["compileStatus"], "sourceHash": actual}
|
|
102
110
|
|
|
103
111
|
|
|
@@ -6,30 +6,34 @@ from inspect_platform_sdk import inspect_sdk
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class TargetSdkError(ValueError):
|
|
9
|
-
def __init__(self, code, message):
|
|
9
|
+
def __init__(self, code, message, *, details=None):
|
|
10
10
|
super().__init__(message)
|
|
11
11
|
self.error_code = code
|
|
12
|
+
self.details = details or {}
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
def verify_target_sdk(project_root, coding, usage=None):
|
|
16
|
+
if usage is not None and (not isinstance(usage, list) or any(not isinstance(item, dict)
|
|
17
|
+
or not isinstance(item.get('class'), str) or not item['class']
|
|
18
|
+
or set(item) - {'class', 'members'} or not isinstance(item.get('members', []), list)
|
|
19
|
+
or any(not isinstance(member, str) or not member for member in item.get('members', [])) for item in usage)):
|
|
20
|
+
raise TargetSdkError('PLUGIN_SDK_USAGE_INVALID', 'sdkUsage requires exact class and member selectors')
|
|
15
21
|
environment = inspect_compile_environment(project_root)
|
|
16
22
|
if environment.get('status') == 'disabled':
|
|
17
23
|
return {'status': 'not-run', 'compile': False, 'projectRoot': str(Path(project_root).resolve()),
|
|
18
24
|
'evidenceScope': 'Source-only configuration: target SDK, compilation and runtime are unverified'}
|
|
19
25
|
if environment.get('status') != 'ready':
|
|
20
26
|
raise TargetSdkError(environment.get('code') or 'PLUGIN_SDK_ENVIRONMENT_UNVERIFIED',
|
|
21
|
-
environment.get('message') or 'Target compile environment is unverified'
|
|
27
|
+
environment.get('message') or 'Target compile environment is unverified',
|
|
28
|
+
details={'buildEnvironment': environment})
|
|
22
29
|
jars = sorted({str(file.resolve()) for directory in environment.get('sdkDirectories', [])
|
|
23
30
|
for file in Path(directory['path']).glob('*.jar')})
|
|
24
31
|
if not jars:
|
|
25
32
|
raise TargetSdkError('PLUGIN_TARGET_SDK_UNRESOLVED', 'The bound module does not establish target SDK libraries')
|
|
26
|
-
|
|
27
|
-
|
|
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')
|
|
33
|
+
# Guide signatures include unrelated lifecycle types; verify the actual base and
|
|
34
|
+
# selected API receivers. Explicit sdkUsage verifies needed event declarations.
|
|
31
35
|
classes = sorted({coding.get('baseContract', {}).get('class'),
|
|
32
|
-
*[item.get('class') for item in coding.get('
|
|
36
|
+
*[item.get('class') for item in coding.get('apiContracts', [])]} - {None})
|
|
33
37
|
verified = [inspect_sdk(jars, name, mode='locate') for name in classes if name.startswith('kd.bos.')]
|
|
34
38
|
contracts = [inspect_sdk(jars, item['class'], item.get('members')) for item in usage or []]
|
|
35
39
|
for result in [*verified, *contracts]:
|