@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,195 @@
|
|
|
1
|
+
"""Behavioral tests for the L0 plugin corpus tools (parser, index build, API usage measurement).
|
|
2
|
+
|
|
3
|
+
The fixtures are real class files compiled by javac into small bos/biz/trd JARs. Platform class
|
|
4
|
+
names deliberately match the type registry and API pages, so the measurement runs against the
|
|
5
|
+
same registry and pages it uses on the real corpus.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import shutil
|
|
11
|
+
import sqlite3
|
|
12
|
+
import subprocess
|
|
13
|
+
import sys
|
|
14
|
+
import zipfile
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
import pytest
|
|
18
|
+
|
|
19
|
+
SCRIPTS = Path(__file__).resolve().parents[1]
|
|
20
|
+
sys.path.insert(0, str(SCRIPTS))
|
|
21
|
+
sys.path.insert(0, str(SCRIPTS / "plugin_corpus"))
|
|
22
|
+
|
|
23
|
+
import build_index # noqa: E402
|
|
24
|
+
import jvm_classfile # noqa: E402
|
|
25
|
+
import measure_api_usage # noqa: E402
|
|
26
|
+
|
|
27
|
+
PLATFORM_SOURCES = {
|
|
28
|
+
"kd/sdk/annotation/SdkPlugin.java": """package kd.sdk.annotation;
|
|
29
|
+
import java.lang.annotation.*;
|
|
30
|
+
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE)
|
|
31
|
+
public @interface SdkPlugin { String name() default ""; }
|
|
32
|
+
""",
|
|
33
|
+
"kd/sdk/annotation/SdkInternal.java": """package kd.sdk.annotation;
|
|
34
|
+
import java.lang.annotation.*;
|
|
35
|
+
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD})
|
|
36
|
+
public @interface SdkInternal { String[] reasons() default {}; }
|
|
37
|
+
""",
|
|
38
|
+
"kd/bos/entity/plugin/AbstractOperationServicePlugIn.java": """package kd.bos.entity.plugin;
|
|
39
|
+
@kd.sdk.annotation.SdkPlugin(name = "operation")
|
|
40
|
+
public abstract class AbstractOperationServicePlugIn {
|
|
41
|
+
public Object[] getDataEntities() { return new Object[0]; }
|
|
42
|
+
public void onPreparePropertys(Object e) { }
|
|
43
|
+
@kd.sdk.annotation.SdkInternal(reasons = {"platform only"})
|
|
44
|
+
public void hidden() { }
|
|
45
|
+
}
|
|
46
|
+
""",
|
|
47
|
+
"kd/bos/servicehelper/Helper.java": """package kd.bos.servicehelper;
|
|
48
|
+
public class Helper { public static void run() { } }
|
|
49
|
+
""",
|
|
50
|
+
}
|
|
51
|
+
BUSINESS_SOURCES = {
|
|
52
|
+
"kd/fi/demo/DemoOp.java": """package kd.fi.demo;
|
|
53
|
+
import kd.bos.entity.plugin.AbstractOperationServicePlugIn;
|
|
54
|
+
import kd.bos.servicehelper.Helper;
|
|
55
|
+
public class DemoOp extends AbstractOperationServicePlugIn {
|
|
56
|
+
@Override
|
|
57
|
+
public void onPreparePropertys(Object e) {
|
|
58
|
+
super.onPreparePropertys(e);
|
|
59
|
+
Object[] rows = getDataEntities();
|
|
60
|
+
hidden();
|
|
61
|
+
Helper.run();
|
|
62
|
+
Runnable later = new Runnable() { public void run() { getDataEntities(); } };
|
|
63
|
+
later.run();
|
|
64
|
+
try { Class.forName("kd.fi.demo.Registered"); } catch (ClassNotFoundException ignored) { }
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
""",
|
|
68
|
+
"kd/fi/demo/Util.java": """package kd.fi.demo;
|
|
69
|
+
public class Util { public static void call() { kd.bos.servicehelper.Helper.run(); } }
|
|
70
|
+
""",
|
|
71
|
+
}
|
|
72
|
+
THIRD_PARTY_SOURCES = {"org/demo/Lib.java": "package org.demo;\npublic class Lib { public int size() { return 1; } }\n"}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _jar(tmp: Path, name: str, sources: dict[str, str], classpath: list[Path] | None = None) -> Path:
|
|
76
|
+
source_root = tmp / f"src-{name}"
|
|
77
|
+
classes = tmp / f"classes-{name}"
|
|
78
|
+
files = []
|
|
79
|
+
for relative, text in sources.items():
|
|
80
|
+
path = source_root / relative
|
|
81
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
82
|
+
path.write_text(text, encoding="utf-8")
|
|
83
|
+
files.append(str(path))
|
|
84
|
+
classes.mkdir()
|
|
85
|
+
command = ["javac", "-d", str(classes)]
|
|
86
|
+
if classpath:
|
|
87
|
+
command += ["-cp", ":".join(str(item) for item in classpath)]
|
|
88
|
+
subprocess.run(command + files, check=True, capture_output=True)
|
|
89
|
+
jar = tmp / f"{name}.jar"
|
|
90
|
+
with zipfile.ZipFile(jar, "w") as archive:
|
|
91
|
+
archive.writestr("META-INF/MANIFEST.MF", "Manifest-Version: 1.0\nImplementation-Version: 9.9\n")
|
|
92
|
+
for path in sorted(classes.rglob("*.class")):
|
|
93
|
+
archive.write(path, path.relative_to(classes).as_posix())
|
|
94
|
+
return jar
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@pytest.fixture
|
|
98
|
+
def library(tmp_path):
|
|
99
|
+
if not shutil.which("javac"):
|
|
100
|
+
pytest.skip("javac unavailable")
|
|
101
|
+
platform = _jar(tmp_path, "platform", PLATFORM_SOURCES)
|
|
102
|
+
business = _jar(tmp_path, "business", BUSINESS_SOURCES, [platform])
|
|
103
|
+
third = _jar(tmp_path, "lib", THIRD_PARTY_SOURCES)
|
|
104
|
+
root = tmp_path / "lib"
|
|
105
|
+
for layer, jar in (("bos", platform), ("biz", business), ("trd", third)):
|
|
106
|
+
(root / layer).mkdir(parents=True)
|
|
107
|
+
shutil.copy(jar, root / layer / jar.name)
|
|
108
|
+
return root
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _class_bytes(jar: Path, entry: str) -> bytes:
|
|
112
|
+
with zipfile.ZipFile(jar) as archive:
|
|
113
|
+
return archive.read(entry)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def test_class_file_reports_hierarchy_annotations_and_references(library):
|
|
117
|
+
parsed = jvm_classfile.parse_class(_class_bytes(library / "biz" / "business.jar", "kd/fi/demo/DemoOp.class"))
|
|
118
|
+
platform = jvm_classfile.parse_class(_class_bytes(
|
|
119
|
+
library / "bos" / "platform.jar", "kd/bos/entity/plugin/AbstractOperationServicePlugIn.class"))
|
|
120
|
+
|
|
121
|
+
assert parsed["name"] == "kd.fi.demo.DemoOp"
|
|
122
|
+
assert parsed["super"] == "kd.bos.entity.plugin.AbstractOperationServicePlugIn"
|
|
123
|
+
assert ("M", "kd.bos.entity.plugin.AbstractOperationServicePlugIn", "onPreparePropertys",
|
|
124
|
+
"(Ljava/lang/Object;)V") in parsed["memberRefs"]
|
|
125
|
+
assert ("M", "kd.bos.servicehelper.Helper", "run", "()V") in parsed["memberRefs"]
|
|
126
|
+
assert parsed["dynamicClassNames"] == ["kd.fi.demo.Registered"]
|
|
127
|
+
assert "kd.fi.demo.DemoOp$1" in parsed["classRefs"]
|
|
128
|
+
assert platform["annotations"] == ["kd.sdk.annotation.SdkPlugin"]
|
|
129
|
+
assert {name: annotations for name, _, _, annotations, _ in platform["methods"]}["hidden"] == (
|
|
130
|
+
"kd.sdk.annotation.SdkInternal",)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def test_descriptors_convert_to_the_parameter_types_pages_write():
|
|
134
|
+
params, returned = jvm_classfile.descriptor_types("(Ljava/lang/String;[ILkd/bos/algo/DataSet$Listener;)V")
|
|
135
|
+
|
|
136
|
+
assert params == ["java.lang.String", "int[]", "kd.bos.algo.DataSet$Listener"]
|
|
137
|
+
assert returned == "void"
|
|
138
|
+
assert [jvm_classfile.simple_type(item) for item in params] == ["String", "int[]", "Listener"]
|
|
139
|
+
with pytest.raises(jvm_classfile.ClassFormatError):
|
|
140
|
+
jvm_classfile.parse_class(b"not a class")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def test_index_rebuild_reuses_unchanged_jars_and_replaces_changed_ones(library, tmp_path):
|
|
144
|
+
workspace = tmp_path / "workspace"
|
|
145
|
+
first = build_index.build(str(library), [], str(workspace), ["biz"], workers=0)
|
|
146
|
+
|
|
147
|
+
assert first["jars"] == {"new": 3, "changed": 0, "unchanged": 0, "removed": 0}
|
|
148
|
+
assert {layer: data["classes"] for layer, data in first["layers"].items()} == {"biz": 3, "bos": 4, "trd": 1}
|
|
149
|
+
db = sqlite3.connect(str(workspace / build_index.DB_NAME))
|
|
150
|
+
refs_by_layer = dict(db.execute(
|
|
151
|
+
"SELECT j.layer, COUNT(r.class_id) FROM jars j JOIN classes c ON c.jar_id = j.id "
|
|
152
|
+
"LEFT JOIN refs r ON r.class_id = c.id GROUP BY j.layer"))
|
|
153
|
+
db.close()
|
|
154
|
+
assert refs_by_layer["biz"] > 0 and refs_by_layer["bos"] == 0 and refs_by_layer["trd"] == 0
|
|
155
|
+
|
|
156
|
+
assert build_index.build(str(library), [], str(workspace), ["biz"], workers=0)["jars"]["unchanged"] == 3
|
|
157
|
+
|
|
158
|
+
extra = dict(BUSINESS_SOURCES, **{"kd/fi/demo/More.java": "package kd.fi.demo;\npublic class More { }\n"})
|
|
159
|
+
rebuilt = _jar(tmp_path, "business2", extra, [library / "bos" / "platform.jar"])
|
|
160
|
+
shutil.copy(rebuilt, library / "biz" / "business.jar")
|
|
161
|
+
(library / "trd" / "lib.jar").unlink()
|
|
162
|
+
(library / "trd").rmdir()
|
|
163
|
+
second = build_index.build(str(library), [], str(workspace), ["biz"], workers=0)
|
|
164
|
+
|
|
165
|
+
assert second["jars"] == {"new": 0, "changed": 1, "unchanged": 1, "removed": 1}
|
|
166
|
+
assert second["layers"]["biz"]["classes"] == 4
|
|
167
|
+
assert "trd" not in second["layers"]
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def test_workspace_inside_the_skill_package_is_rejected(library):
|
|
171
|
+
with pytest.raises(build_index.CorpusIndexError) as error:
|
|
172
|
+
build_index.build(str(library), [], str(SCRIPTS / "plugin_corpus" / "workspace"), ["biz"], workers=0)
|
|
173
|
+
|
|
174
|
+
assert error.value.code == "CORPUS_WORKSPACE_INSIDE_REPOSITORY"
|
|
175
|
+
assert not (SCRIPTS / "plugin_corpus" / "workspace").exists()
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def test_measurement_labels_platform_references_and_checks_the_api_pages(library, tmp_path):
|
|
179
|
+
workspace = tmp_path / "workspace"
|
|
180
|
+
build_index.build(str(library), [], str(workspace), ["biz"], workers=0)
|
|
181
|
+
result = measure_api_usage.measure(str(workspace), top=10, platform_layers=["bos"], extra_roots=[])
|
|
182
|
+
|
|
183
|
+
assert result["population"]["pluginClasses"] == 1
|
|
184
|
+
assert result["population"]["pluginClassesByRegistryType"] == {"operation": 1}
|
|
185
|
+
assert result["population"]["classesIncludingNested"] == 2
|
|
186
|
+
members = {row["signature"]: row for key in ("topUndocumentedPublicMembers", "topNonPublicMembers",
|
|
187
|
+
"topPublicConstructors") for row in result[key]}
|
|
188
|
+
assert members["void kd.bos.entity.plugin.AbstractOperationServicePlugIn.hidden()"]["label"] == "internal"
|
|
189
|
+
assert members["void kd.bos.servicehelper.Helper.run()"]["label"] == "unmarked"
|
|
190
|
+
assert members["void kd.bos.entity.plugin.AbstractOperationServicePlugIn.onPreparePropertys(java.lang.Object)"][
|
|
191
|
+
"documentation"] == "member-missing"
|
|
192
|
+
# getDataEntities() is on the real AbstractOperationServicePlugIn page, from both DemoOp and DemoOp$1.
|
|
193
|
+
assert result["measurements"]["publicReferenceDocumentation"]["documented"] == 2
|
|
194
|
+
assert result["measurements"]["pluginsUsingOnlyPublicPlatformMembers"] == 0
|
|
195
|
+
assert result["measurements"]["pluginsWithPlatformReferences"] == 1
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import sys
|
|
3
|
+
import pytest
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
SCRIPTS = Path(__file__).resolve().parents[1]
|
|
7
8
|
sys.path.insert(0, str(SCRIPTS))
|
|
8
9
|
|
|
9
|
-
from prepare_plugin_handoff import HandoffError, prepare_handoff # noqa: E402
|
|
10
|
+
from prepare_plugin_handoff import HandoffError, prepare_handoff, manifest_artifacts # noqa: E402
|
|
10
11
|
from resolve_output_path import resolve_output_path # noqa: E402
|
|
11
12
|
|
|
12
13
|
|
|
@@ -31,6 +32,43 @@ def _artifact(form_name, form_key, entry_name, entry_key, fields):
|
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
|
|
35
|
+
def test_legacy_artifact_receipt_preserves_verified_peer_fields(tmp_path):
|
|
36
|
+
app_root = tmp_path / "app" / "sample_demo"
|
|
37
|
+
_write(app_root / "archive.json", {"appCode": "sample_demo"})
|
|
38
|
+
_java_project(tmp_path)
|
|
39
|
+
artifact = tmp_path / "emit" / "source.json"
|
|
40
|
+
_write(artifact, {"entities": [
|
|
41
|
+
{"id": "root", "displayName": "来源单", "type": "BillEntity", "entityKey": "sample_source", "fields": []},
|
|
42
|
+
]})
|
|
43
|
+
peer = tmp_path / "emit" / "peer.json"
|
|
44
|
+
_write(peer, {"entities": [
|
|
45
|
+
{"id": "root", "displayName": "关联资料", "type": "BaseEntity", "entityKey": "sample_peer",
|
|
46
|
+
"fields": [{"displayName": "余额", "fieldKey": "sample_balance", "dataType": "AmountField"}]},
|
|
47
|
+
]})
|
|
48
|
+
manifest = tmp_path / "emit" / "_emit_manifest.json"
|
|
49
|
+
_write(manifest, {"artifacts": [{"path": "source.json"}, {"path": str(peer)}]})
|
|
50
|
+
assert manifest_artifacts(str(manifest)) == [str(artifact), str(peer)]
|
|
51
|
+
result = tmp_path / "write.json"
|
|
52
|
+
_write(result, {"appRoot": str(app_root), "artifactPaths": [str(artifact), str(peer)], "results": [
|
|
53
|
+
{"artifact": path.name, "ok": True, "verification": {"status": "pass"},
|
|
54
|
+
"result": {"data": {"entityResults": [{"formNumber": number, "formName": name}]}}}
|
|
55
|
+
for path, number, name in [(artifact, "sample_source", "来源单"), (peer, "sample_peer", "关联资料")]
|
|
56
|
+
]})
|
|
57
|
+
operations = tmp_path / "operations.json"
|
|
58
|
+
_write(operations, {"status": True, "data": [{"operationKey": "process"}]})
|
|
59
|
+
intent = {"form": "来源单", "trigger": "处理前", "operationKeys": ["process"],
|
|
60
|
+
"coreLogic": ["检查关联资料余额"],
|
|
61
|
+
"specialized": {"unresolvedDependencies": [], "externalContracts": [{"entity": "关联资料", "fields": ["余额"]}]}}
|
|
62
|
+
handoff = prepare_handoff(app_root=str(app_root), project_root=str(tmp_path), plugin_type="operation",
|
|
63
|
+
artifact_paths=[str(artifact), str(peer)], run_result=json.loads(result.read_text()), intent=intent,
|
|
64
|
+
operation_result=json.loads(operations.read_text()))["handoff"]
|
|
65
|
+
assert handoff["specialized"]["externalContracts"][0]["verifiedFields"][0]["fieldKey"] == "sample_balance"
|
|
66
|
+
assert handoff["fqcn"].endswith("." + handoff["className"])
|
|
67
|
+
assert Path(handoff["sourcePath"]).name == handoff["className"] + ".java"
|
|
68
|
+
assert not Path(handoff["sourcePath"]).exists()
|
|
69
|
+
assert handoff["codingEvidence"]["baseContract"]["status"] is True
|
|
70
|
+
|
|
71
|
+
|
|
34
72
|
def test_resolve_output_path_uses_same_app_package_not_project_common_prefix(tmp_path):
|
|
35
73
|
app_root = tmp_path / "app" / "uhyl_demo"
|
|
36
74
|
_write(app_root / "archive.json", {"appCode": "uhyl_demo"})
|
|
@@ -592,6 +630,11 @@ def test_report_handoff_carries_filter_requirements_the_platform_dropped(tmp_pat
|
|
|
592
630
|
# comparison is, so query() has to apply between / in / = itself.
|
|
593
631
|
assert [row["requirement"] for row in enforcement] == ["compare", "mustInput"]
|
|
594
632
|
assert {row["filterKey"] for row in enforcement} == {"flt_period"}
|
|
633
|
+
values = payload["reportContract"]["filterValueContracts"]
|
|
634
|
+
assert values[0]["filterKey"] == "flt_period"
|
|
635
|
+
assert values[0]["valueChannel"] == "getValue"
|
|
636
|
+
assert values[0]["rangeItemKeys"] is None
|
|
637
|
+
assert values[0]["runtimeKeysVerified"] is False
|
|
595
638
|
|
|
596
639
|
|
|
597
640
|
def test_report_handoff_compiles_the_contract_instead_of_trusting_a_persisted_one(tmp_path):
|
|
@@ -871,6 +914,15 @@ def test_modify_carries_the_methods_the_target_class_already_declares(tmp_path):
|
|
|
871
914
|
assert payload["sourcePath"] == str(source.resolve())
|
|
872
915
|
|
|
873
916
|
|
|
917
|
+
def test_modify_fqcn_follows_existing_source_package(tmp_path):
|
|
918
|
+
source = _existing_plugin(tmp_path, "UhylTargetBillPlugin")
|
|
919
|
+
source.write_text(source.read_text().replace("package kd.bos.app.uhyl.demo.form;", "package custom.extension;"))
|
|
920
|
+
payload = _billform_handoff(tmp_path, _modify_intent(source))["handoff"]
|
|
921
|
+
assert payload["packagePath"] == "custom/extension"
|
|
922
|
+
assert payload["fqcn"] == "custom.extension.UhylTargetBillPlugin"
|
|
923
|
+
assert payload["sourcePath"] == str(source.resolve())
|
|
924
|
+
|
|
925
|
+
|
|
874
926
|
def test_patch_existing_is_accepted_as_modify_instead_of_losing_the_intent(tmp_path):
|
|
875
927
|
source = _existing_plugin(tmp_path, "UhylTargetBillPlugin")
|
|
876
928
|
|
|
@@ -1013,6 +1065,13 @@ def test_scalar_field_gets_its_java_type_and_no_accessor_advice(tmp_path):
|
|
|
1013
1065
|
assert "valueAccessor" not in contract
|
|
1014
1066
|
|
|
1015
1067
|
|
|
1068
|
+
def test_time_only_field_does_not_retype_datetime_field():
|
|
1069
|
+
from prepare_plugin_handoff import _field_java_type
|
|
1070
|
+
assert _field_java_type("TimeProp") == "Integer"
|
|
1071
|
+
assert _field_java_type("TimeField") == "Integer"
|
|
1072
|
+
assert _field_java_type("DateTimeField") == "java.util.Date"
|
|
1073
|
+
|
|
1074
|
+
|
|
1016
1075
|
def test_a_field_whose_type_the_artifact_omits_gets_no_contract(tmp_path):
|
|
1017
1076
|
"""A report column carries no dataType; an invented accessor would read as verified."""
|
|
1018
1077
|
app_root = tmp_path / "app" / "kdec_yzxz"
|
|
@@ -1036,3 +1095,289 @@ def test_a_field_whose_type_the_artifact_omits_gets_no_contract(tmp_path):
|
|
|
1036
1095
|
|
|
1037
1096
|
assert handoff["fields"] == {"kdec_remark": "kdec_remark"}
|
|
1038
1097
|
assert "fieldContracts" not in handoff
|
|
1098
|
+
|
|
1099
|
+
|
|
1100
|
+
def test_prepare_reads_only_missing_evidence_and_keeps_operation_state_contract(tmp_path, monkeypatch):
|
|
1101
|
+
import prepare_plugin_handoff as module
|
|
1102
|
+
_java_project(tmp_path)
|
|
1103
|
+
app_root = tmp_path / "app" / "sample_demo"
|
|
1104
|
+
_write(app_root / "archive.json", {"appCode": "sample_demo"})
|
|
1105
|
+
source = tmp_path / "source.json"
|
|
1106
|
+
peer = tmp_path / "peer.json"
|
|
1107
|
+
_write(source, {"entities": [{"displayName": "来源单", "type": "BillEntity", "entityKey": "sample_source", "fields": [
|
|
1108
|
+
{"id": "state-id", "displayName": "阶段", "fieldKey": "sample_phase", "dataType": "ComboField",
|
|
1109
|
+
"comboOptions": [{"label": "待处理", "value": "pending"}]}]}]})
|
|
1110
|
+
_write(peer, {"entities": [{"displayName": "资料", "type": "BaseEntity", "entityKey": "sample_peer", "fields": [
|
|
1111
|
+
{"displayName": "余额", "fieldKey": "sample_balance", "dataType": "AmountField"}]}]})
|
|
1112
|
+
receipt = {"appRoot": str(app_root), "results": [
|
|
1113
|
+
{"artifact": str(p), "ok": True, "verification": {"status": "pass"},
|
|
1114
|
+
"result": {"data": {"entityResults": [{"formName": n, "formNumber": k}]}}}
|
|
1115
|
+
for p, n, k in [(source, "来源单", "sample_source"), (peer, "资料", "sample_peer")]]}
|
|
1116
|
+
operations = {"status": True, "data": {"operations": [{"operationKey": "process", "operationName": "处理",
|
|
1117
|
+
"parameter": {"StatusFieldId": "state-id", "Value": "B"}}]}}
|
|
1118
|
+
schema = {"status": True, "data": {"formNumber": "sample_external", "formName": "外部资料", "entities": [
|
|
1119
|
+
{"entityKey": "sample_external", "entityName": "外部资料", "fields": [
|
|
1120
|
+
{"fieldKey": "sample_quantity", "fieldName": "数量", "fieldType": "IntegerField"}]}]}}
|
|
1121
|
+
responses = tmp_path / "responses.json"
|
|
1122
|
+
_write(responses, {"listOperations": operations, "getFormSchema": schema})
|
|
1123
|
+
api = tmp_path / "fixture_api.py"
|
|
1124
|
+
calls = tmp_path / "calls.jsonl"
|
|
1125
|
+
# Run a real child CLI; only its remote service response is replaced by local fixtures.
|
|
1126
|
+
api.write_text("import sys,json\nfrom pathlib import Path\n"
|
|
1127
|
+
f"responses=json.loads(Path({str(responses)!r}).read_text())\n"
|
|
1128
|
+
"command=next(v for v in sys.argv if v in responses)\n"
|
|
1129
|
+
f"with Path({str(calls)!r}).open('a') as f: f.write(json.dumps(sys.argv[1:])+'\\n')\n"
|
|
1130
|
+
"Path(sys.argv[sys.argv.index('--output')+1]).write_text(json.dumps(responses[command]))\n")
|
|
1131
|
+
monkeypatch.setattr(module, "COSMIC_API", api)
|
|
1132
|
+
intent = {"form": "来源单", "trigger": "处理前", "operationKeys": ["process"], "fields": {"phase": "阶段"},
|
|
1133
|
+
"coreLogic": ["检查关联资料余额和外部数量,按业务规则处理"],
|
|
1134
|
+
"scenes": ["operation/cross-entity-aggregate-writeback"],
|
|
1135
|
+
"specialized": {"unresolvedDependencies": [], "externalContracts": [
|
|
1136
|
+
{"entity": "资料", "fields": ["余额"]}, {"entity": "sample_external", "fields": ["数量"]}]}}
|
|
1137
|
+
operation_result, schemas = module.prepare_inputs(app_root=str(app_root), intent=intent, plugin_type="operation",
|
|
1138
|
+
artifact_paths=[str(source), str(peer)], run_result=receipt, operation_result=None, external_schemas=[])
|
|
1139
|
+
result = prepare_handoff(app_root=str(app_root), project_root=str(tmp_path), plugin_type="operation", intent=intent,
|
|
1140
|
+
artifact_paths=[str(source), str(peer)], run_result=receipt, operation_result=operation_result, external_schemas=schemas)
|
|
1141
|
+
recorded = [json.loads(line) for line in calls.read_text().splitlines()]
|
|
1142
|
+
assert len(recorded) == 2
|
|
1143
|
+
assert sum("listOperations" in args for args in recorded) == 1
|
|
1144
|
+
assert sum("getFormSchema" in args for args in recorded) == 1
|
|
1145
|
+
assert all(str(app_root.resolve()) == args[args.index("--app-root") + 1] for args in recorded)
|
|
1146
|
+
assert result["handoff"]["operationContracts"][0]["parameter"]["Value"] == "B"
|
|
1147
|
+
assert result["handoff"]["stateWarnings"][0]["code"] == "OPERATION_STATE_VALUE_MISMATCH"
|
|
1148
|
+
assert result["handoff"]["fieldContracts"][0]["comboOptions"][0]["value"] == "pending"
|
|
1149
|
+
assert len(result["handoff"]["description"]) <= 20
|
|
1150
|
+
assert result["handoff"]["codingEvidence"]["apiContracts"]
|
|
1151
|
+
|
|
1152
|
+
|
|
1153
|
+
def test_unverified_entity_diagnostics_include_all_missing_targets(tmp_path):
|
|
1154
|
+
import pytest
|
|
1155
|
+
with pytest.raises(HandoffError) as error:
|
|
1156
|
+
_external_handoff(tmp_path, [{"entity": "missing_one", "fields": ["id"]},
|
|
1157
|
+
{"entity": "missing_two", "fields": ["id"]}])
|
|
1158
|
+
assert "missing_one" in str(error.value) and "missing_two" in str(error.value)
|
|
1159
|
+
|
|
1160
|
+
|
|
1161
|
+
def test_failed_operation_readback_is_never_positive_evidence():
|
|
1162
|
+
import pytest
|
|
1163
|
+
from prepare_plugin_handoff import _verified_operation_keys
|
|
1164
|
+
with pytest.raises(HandoffError, match="did not succeed"):
|
|
1165
|
+
_verified_operation_keys({"status": True, "data": {"success": False, "operations": [{"operationKey": "process"}]}})
|
|
1166
|
+
|
|
1167
|
+
|
|
1168
|
+
def test_failed_evidence_child_does_not_accept_its_partial_json(tmp_path, monkeypatch):
|
|
1169
|
+
import pytest
|
|
1170
|
+
import prepare_plugin_handoff as module
|
|
1171
|
+
source = tmp_path / "source.json"
|
|
1172
|
+
_write(source, {"entities": [{"displayName": "来源单", "type": "BillEntity", "entityKey": "sample_source", "fields": []}]})
|
|
1173
|
+
api = tmp_path / "failed_api.py"
|
|
1174
|
+
api.write_text("import sys\nfrom pathlib import Path\n"
|
|
1175
|
+
"Path(sys.argv[sys.argv.index('--output')+1]).write_text('{\"status\":true}')\n"
|
|
1176
|
+
"sys.exit(1)\n")
|
|
1177
|
+
monkeypatch.setattr(module, "COSMIC_API", api)
|
|
1178
|
+
with pytest.raises(HandoffError) as error:
|
|
1179
|
+
module.prepare_inputs(app_root=str(tmp_path), intent={"form": "来源单", "operationKeys": ["process"], "coreLogic": ["校验资料"],
|
|
1180
|
+
"specialized": {"unresolvedDependencies": []}},
|
|
1181
|
+
plugin_type="operation", artifact_paths=[str(source)], run_result=None, operation_result=None, external_schemas=[])
|
|
1182
|
+
assert error.value.error_code == "PLUGIN_EVIDENCE_READ_FAILED"
|
|
1183
|
+
|
|
1184
|
+
|
|
1185
|
+
def test_recorded_runtime_graph_resolves_header_entries_and_subentries_without_artifacts(tmp_path):
|
|
1186
|
+
from prepare_plugin_handoff import runtime_catalog
|
|
1187
|
+
native = json.loads((Path(__file__).parent / "fixtures/java_context_booking_native.json").read_text())
|
|
1188
|
+
_java_project(tmp_path)
|
|
1189
|
+
app = tmp_path / "app" / "sample_app"
|
|
1190
|
+
_write(app / "archive.json", {"appCode": "sample_app"})
|
|
1191
|
+
intent = {"form": "定场单", "trigger": "保存时", "operationKeys": ["save"], "coreLogic": ["校验场地时段冲突"],
|
|
1192
|
+
"fields": {"date": {"form": "定场单", "entity": "header", "field": "定场日期"},
|
|
1193
|
+
"price": {"form": "定场单", "entity": "entries", "field": "单价"},
|
|
1194
|
+
"servicePrice": {"form": "定场单", "entity": "subentries", "field": "单价"}},
|
|
1195
|
+
"specialized": {"unresolvedDependencies": []}}
|
|
1196
|
+
result = prepare_handoff(app_root=str(app), project_root=str(tmp_path), plugin_type="operation", intent=intent,
|
|
1197
|
+
artifact_paths=[str(tmp_path / "absent-authoring-artifact.json")], runtime_metadata=[native],
|
|
1198
|
+
operation_result={"status": True, "data": [{"operationKey": "save"}]})
|
|
1199
|
+
handoff = result["handoff"]
|
|
1200
|
+
assert handoff["fields"]["date"] == "sample_booking_date"
|
|
1201
|
+
assert handoff["fields"]["price"] == "sample_unit_price"
|
|
1202
|
+
assert handoff["fields"]["servicePrice"] == "sample_svc_price"
|
|
1203
|
+
graph = handoff["formGraph"]["entities"]
|
|
1204
|
+
assert [entity["role"] for entity in graph] == ["header", "entries", "subentries"]
|
|
1205
|
+
assert graph[2]["parentEntityKey"] == graph[1]["entityKey"]
|
|
1206
|
+
# The runtime property differs from the authoring sub-entry key in the observed session.
|
|
1207
|
+
assert graph[2]["collectionProperty"] == "sample_booking_order_e"
|
|
1208
|
+
assert graph[2]["collectionPath"] == ["sample_bookin_order_line", "sample_booking_order_e"]
|
|
1209
|
+
start_time = next(field for field in graph[1]["fields"] if field["fieldKey"] == "sample_start_time")
|
|
1210
|
+
assert start_time["fieldType"] == "TimeProp"
|
|
1211
|
+
assert start_time["javaType"] == "Integer"
|
|
1212
|
+
assert start_time["valueAccessor"] == 'getInt("sample_start_time")'
|
|
1213
|
+
assert start_time["timeValueUnit"] == "seconds-after-midnight"
|
|
1214
|
+
_, _, raw_fields, _ = runtime_catalog([native])
|
|
1215
|
+
assert sum(len(entity["fields"]) for entity in graph) == len(raw_fields) == 37
|
|
1216
|
+
assert all(field["entityKey"] == entity["entityKey"] for entity in graph for field in entity["fields"])
|
|
1217
|
+
assert next(field for field in graph[2]["fields"] if field["fieldKey"] == "sample_svc_price")["fieldPath"] == \
|
|
1218
|
+
"sample_bookin_order_line.sample_booking_order_e.sample_svc_price"
|
|
1219
|
+
|
|
1220
|
+
|
|
1221
|
+
def test_runtime_context_without_field_declarations_still_has_the_complete_graph(tmp_path):
|
|
1222
|
+
native = json.loads((Path(__file__).parent / "fixtures/java_context_booking_native.json").read_text())
|
|
1223
|
+
_java_project(tmp_path)
|
|
1224
|
+
app = tmp_path / "app" / "sample_app"
|
|
1225
|
+
_write(app / "archive.json", {"appCode": "sample_app"})
|
|
1226
|
+
result = prepare_handoff(app_root=str(app), project_root=str(tmp_path), plugin_type="operation",
|
|
1227
|
+
intent={"form": "定场单", "trigger": "保存时", "operationKeys": ["save"], "coreLogic": ["校验场地状态"],
|
|
1228
|
+
"specialized": {"unresolvedDependencies": []}}, artifact_paths=[], runtime_metadata=[native],
|
|
1229
|
+
operation_result={"status": True, "data": [{"operationKey": "save"}]})
|
|
1230
|
+
assert len(result["handoff"]["formGraph"]["entities"]) == 3
|
|
1231
|
+
fields = [field for entity in result["handoff"]["formGraph"]["entities"] for field in entity["fields"]]
|
|
1232
|
+
assert any(field["fieldKey"] == "sample_court" for field in fields)
|
|
1233
|
+
assert any(field["fieldKey"] == "sample_member" for field in fields)
|
|
1234
|
+
assert any(field["fieldKey"] == "sample_status" for field in fields)
|
|
1235
|
+
assert result["handoff"]["fields"] == {} # No keyword-based selection is needed to preserve fields.
|
|
1236
|
+
|
|
1237
|
+
|
|
1238
|
+
def test_runtime_collection_property_is_not_assumed_to_be_the_item_entity_name():
|
|
1239
|
+
from prepare_plugin_handoff import runtime_catalog
|
|
1240
|
+
_, entities, fields, _ = runtime_catalog([{"Name": "sample_main", "Properties": [
|
|
1241
|
+
{"Name": "lines", "_Type_": "EntryProp", "ItemType": {"Name": "sample_line_type", "Properties": [
|
|
1242
|
+
{"Name": "qty", "_Type_": "DecimalProp"}]}}]}])
|
|
1243
|
+
assert entities[1]["entityKey"] == "sample_line_type"
|
|
1244
|
+
assert entities[1]["collectionProperty"] == "lines"
|
|
1245
|
+
assert fields[0]["fieldPath"] == "lines.qty"
|
|
1246
|
+
|
|
1247
|
+
|
|
1248
|
+
def test_runtime_collection_missing_item_metadata_cannot_be_ready():
|
|
1249
|
+
import pytest
|
|
1250
|
+
from prepare_plugin_handoff import runtime_catalog
|
|
1251
|
+
with pytest.raises(HandoffError) as error:
|
|
1252
|
+
runtime_catalog([{"Name": "sample_main", "Properties": [{"Name": "lines", "_Type_": "EntryProp"}]}])
|
|
1253
|
+
assert error.value.error_code == "PLUGIN_CONTEXT_INCOMPLETE"
|
|
1254
|
+
|
|
1255
|
+
|
|
1256
|
+
def test_duplicate_runtime_field_names_return_scoped_candidates(tmp_path):
|
|
1257
|
+
import pytest
|
|
1258
|
+
from prepare_plugin_handoff import runtime_catalog, _resolve_field
|
|
1259
|
+
native = json.loads((Path(__file__).parent / "fixtures/java_context_booking_native.json").read_text())
|
|
1260
|
+
_, _, fields, _ = runtime_catalog([native])
|
|
1261
|
+
with pytest.raises(HandoffError) as error:
|
|
1262
|
+
_resolve_field(fields, {"form": "定场单", "field": "单价"})
|
|
1263
|
+
assert "sample_unit_price" in str(error.value)
|
|
1264
|
+
assert "sample_svc_price" in str(error.value)
|
|
1265
|
+
|
|
1266
|
+
|
|
1267
|
+
def test_external_contract_requires_exact_live_field_identity():
|
|
1268
|
+
from prepare_plugin_handoff import _external_field
|
|
1269
|
+
scope = [{"entityKey": "sample_court", "entityName": "场地档案", "fields": {
|
|
1270
|
+
"sample_court_status": {"fieldName": "场地状态"},
|
|
1271
|
+
}}]
|
|
1272
|
+
assert _external_field(scope, "场地状态")[1] == "sample_court_status"
|
|
1273
|
+
assert _external_field(scope, "sample_court_status")[1] == "sample_court_status"
|
|
1274
|
+
with pytest.raises(HandoffError, match="sample_court_status"):
|
|
1275
|
+
_external_field(scope, "court_status")
|
|
1276
|
+
|
|
1277
|
+
|
|
1278
|
+
@pytest.mark.parametrize("with_receipt", [False, True])
|
|
1279
|
+
@pytest.mark.parametrize("full_output", [False, True])
|
|
1280
|
+
def test_independent_cli_reads_native_metadata_without_modeling_receipts(tmp_path, monkeypatch, capsys, with_receipt, full_output):
|
|
1281
|
+
import prepare_plugin_handoff as module
|
|
1282
|
+
native = json.loads((Path(__file__).parent / "fixtures/java_context_booking_native.json").read_text())
|
|
1283
|
+
_java_project(tmp_path)
|
|
1284
|
+
config = tmp_path / "appconfig.json"
|
|
1285
|
+
_write(config, {"compile": False})
|
|
1286
|
+
monkeypatch.setenv("KD_APPCONFIG", str(config))
|
|
1287
|
+
app = tmp_path / "app" / "sample_app"
|
|
1288
|
+
_write(app / "archive.json", {"appCode": "sample_app"})
|
|
1289
|
+
responses = tmp_path / "responses.json"
|
|
1290
|
+
_write(responses, {"getEntityType": {"sample_booking_order": native,
|
|
1291
|
+
"sample_court": {"status": True, "data": {"Name": "sample_court", "DisplayName": {"zh_CN": "场地档案"},
|
|
1292
|
+
"Properties": [{"Name": "sample_court_status", "DisplayName": {"zh_CN": "场地状态"}, "_Type_": "ComboProp"}]}},
|
|
1293
|
+
"sample_member": {"status": True, "data": {"Name": "sample_member", "DisplayName": {"zh_CN": "会员档案"},
|
|
1294
|
+
"Properties": [{"Name": "sample_balance", "DisplayName": {"zh_CN": "余额"}, "_Type_": "AmountProp"}]}}},
|
|
1295
|
+
"listOperations": {"status": True, "data": {"formNumber": "sample_booking_order", "operations": [{"operationKey": "save"}]}}})
|
|
1296
|
+
calls = tmp_path / "calls.jsonl"
|
|
1297
|
+
api = tmp_path / "fixture_api.py"
|
|
1298
|
+
api.write_text("import sys,json\nfrom pathlib import Path\n"
|
|
1299
|
+
f"responses=json.loads(Path({str(responses)!r}).read_text())\n"
|
|
1300
|
+
"command=next(v for v in sys.argv if v in responses)\n"
|
|
1301
|
+
"value=responses[command]\n"
|
|
1302
|
+
"if command=='getEntityType': value=value[sys.argv[sys.argv.index('--entityId')+1]]\n"
|
|
1303
|
+
f"with Path({str(calls)!r}).open('a') as f: f.write(json.dumps(sys.argv[1:])+'\\n')\n"
|
|
1304
|
+
"Path(sys.argv[sys.argv.index('--output')+1]).write_text(json.dumps(value))\n")
|
|
1305
|
+
monkeypatch.setattr(module, "COSMIC_API", api)
|
|
1306
|
+
intent = {"form": "定场单", "trigger": "保存时", "operationKeys": ["save"], "coreLogic": ["校验场地状态"],
|
|
1307
|
+
"fields": {"date": {"form": "定场单", "entity": "header", "field": "定场日期"}},
|
|
1308
|
+
"specialized": {"unresolvedDependencies": [], "externalContracts": [{"entity": "场地档案", "fields": ["场地状态"]}]}}
|
|
1309
|
+
args = ["prepare_plugin_handoff.py", "--app-root", str(app), "--java-project-root", str(tmp_path),
|
|
1310
|
+
"--type", "operation", "--intent-json", json.dumps(intent), "--prepare-source"]
|
|
1311
|
+
if with_receipt:
|
|
1312
|
+
receipt = tmp_path / "old-receipt.json"
|
|
1313
|
+
_write(receipt, {"appRoot": str(app), "artifactPaths": [str(tmp_path / "absent-artifact.json")],
|
|
1314
|
+
"forms": [{"formNumber": "sample_booking_order", "formName": "定场单"}]})
|
|
1315
|
+
args += ["--run-result", str(receipt)]
|
|
1316
|
+
else:
|
|
1317
|
+
args += ["--form-number", "sample_booking_order"]
|
|
1318
|
+
if full_output:
|
|
1319
|
+
args += ["--full-output"]
|
|
1320
|
+
monkeypatch.setattr(sys, "argv", args)
|
|
1321
|
+
module.main()
|
|
1322
|
+
result = json.loads(capsys.readouterr().out)
|
|
1323
|
+
assert result["status"] is True
|
|
1324
|
+
assert Path(result["handoffPath"]).is_file()
|
|
1325
|
+
saved = json.loads(Path(result["handoffPath"]).read_text())
|
|
1326
|
+
assert len(saved["handoff"]["formGraph"]["entities"]) == 3
|
|
1327
|
+
assert saved["handoff"]["fields"]["date"] == "sample_booking_date"
|
|
1328
|
+
if full_output:
|
|
1329
|
+
assert result["handoff"] == saved["handoff"]
|
|
1330
|
+
else:
|
|
1331
|
+
assert result["contextMode"] == "reference-only"
|
|
1332
|
+
assert "formGraph" not in result["handoff"]
|
|
1333
|
+
assert result["metadata"]["forms"][0]["metadataSource"] == "getEntityType"
|
|
1334
|
+
recorded = [json.loads(line) for line in calls.read_text().splitlines()]
|
|
1335
|
+
assert sum("listOperations" in call for call in recorded) == 1
|
|
1336
|
+
native_reads = [call[call.index("--entityId") + 1] for call in recorded if "getEntityType" in call]
|
|
1337
|
+
assert sorted(native_reads) == ["sample_booking_order", "sample_court", "sample_member"]
|
|
1338
|
+
assert all(str(app.resolve()) == call[call.index("--app-root") + 1] for call in recorded)
|
|
1339
|
+
assert Path(result["handoff"]["sourcePath"]).parent.is_dir()
|
|
1340
|
+
assert not Path(result["handoff"]["sourcePath"]).exists()
|
|
1341
|
+
|
|
1342
|
+
|
|
1343
|
+
def test_large_native_coding_context_is_saved_completely_without_truncating_stdout(tmp_path):
|
|
1344
|
+
from prepare_plugin_handoff import handoff_response
|
|
1345
|
+
native = json.loads((Path(__file__).parent / "fixtures/java_context_booking_native.json").read_text())
|
|
1346
|
+
native["data"]["Properties"] += [
|
|
1347
|
+
{"Name": f"sample_extra_{index}", "DisplayName": {"zh_CN": f"补充字段{index}"}, "_Type_": "TextProp"}
|
|
1348
|
+
for index in range(600)
|
|
1349
|
+
]
|
|
1350
|
+
_java_project(tmp_path)
|
|
1351
|
+
app = tmp_path / "app" / "sample_app"
|
|
1352
|
+
_write(app / "archive.json", {"appCode": "sample_app"})
|
|
1353
|
+
handoff = prepare_handoff(
|
|
1354
|
+
app_root=str(app), project_root=str(tmp_path), plugin_type="operation", artifact_paths=[],
|
|
1355
|
+
runtime_metadata=[native], operation_result={"status": True, "data": [{"operationKey": "save"}]},
|
|
1356
|
+
intent={"form": "sample_booking_order", "trigger": "保存时", "operationKeys": ["save"],
|
|
1357
|
+
"scenes": ["operation/custom-validator"], "coreLogic": ["校验补充字段"],
|
|
1358
|
+
"specialized": {"unresolvedDependencies": [], "externalContracts": []}},
|
|
1359
|
+
)["handoff"]
|
|
1360
|
+
target = tmp_path / "handoff.json"
|
|
1361
|
+
result = {"status": True, "handoffPath": str(target), "handoff": handoff}
|
|
1362
|
+
_write(target, result)
|
|
1363
|
+
stdout = json.dumps(handoff_response(result), ensure_ascii=False).encode("utf-8")
|
|
1364
|
+
assert len(target.read_bytes()) > 50 * 1024
|
|
1365
|
+
assert len(stdout) < 12 * 1024
|
|
1366
|
+
assert json.loads(target.read_text())["handoff"] == handoff
|
|
1367
|
+
assert handoff_response(result, full_output=True)["handoff"] == handoff
|
|
1368
|
+
assert json.loads(stdout)["metadata"]["forms"][0]["fieldCount"] >= 600
|
|
1369
|
+
|
|
1370
|
+
|
|
1371
|
+
def test_prepared_class_assignment_cannot_be_silently_overwritten(tmp_path):
|
|
1372
|
+
from prepare_plugin_handoff import assert_handoff_replace, HandoffError
|
|
1373
|
+
target = tmp_path / "handoff.json"
|
|
1374
|
+
result = {"status": True, "handoff": {"fqcn": "sample.Plugin", "operationKeys": ["save"], "coreLogic": ["validate"]}}
|
|
1375
|
+
_write(target, result)
|
|
1376
|
+
original = target.read_bytes()
|
|
1377
|
+
assert_handoff_replace(target, result)
|
|
1378
|
+
revised = {"status": True, "handoff": {**result["handoff"], "operationKeys": ["confirm"]}}
|
|
1379
|
+
with pytest.raises(HandoffError) as error:
|
|
1380
|
+
assert_handoff_replace(target, revised)
|
|
1381
|
+
assert error.value.error_code == "PLUGIN_HANDOFF_CONFLICT"
|
|
1382
|
+
assert target.read_bytes() == original
|
|
1383
|
+
assert_handoff_replace(target, revised, replace=True)
|