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