@qingflow-tech/qingflow-cli 1.1.43-beta.2 → 1.1.43-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +11 -5
  2. package/entry_point.py +1 -1
  3. package/npm/bin/qingflow.mjs +1 -2
  4. package/npm/lib/runtime.mjs +3 -7
  5. package/package.json +1 -2
  6. package/pyproject.toml +1 -3
  7. package/skills/qingflow-app-form/SKILL.md +23 -11
  8. package/skills/qingflow-cli/SKILL.md +4 -2
  9. package/skills/qingflow-cli/reference/QINGFLOW_CLI_FIELD_DATA_TYPES.md +1 -1
  10. package/skills/qingflow-cli/reference/QINGFLOW_CLI_SCHEMA_APPLY_FIELD_TYPES_AND_SCENARIOS.md +1 -1
  11. package/skills/qingflow-cli/reference/builder/10-build-single-app.md +3 -3
  12. package/skills/qingflow-cli/reference/builder/20-build-complete-system.md +1 -1
  13. package/skills/qingflow-cli/reference/builder/30-schema-fields.md +25 -24
  14. package/skills/qingflow-cli/reference/builder/40-layout.md +5 -5
  15. package/skills/qingflow-cli/reference/builder/README.md +3 -3
  16. package/skills/qingflow-cli/reference/builder/code-integrations/README.md +1 -1
  17. package/skills/qingflow-cli/reference/builder/reference/app-delivery-sop.md +7 -7
  18. package/skills/qingflow-cli/reference/core/QINGFLOW_CLI_FIELD_DATA_TYPES.md +1 -1
  19. package/skills/qingflow-cli/reference/examples/README.md +1 -1
  20. package/skills/qingflow-common/SKILL.md +47 -0
  21. package/skills/qingflow-common/agents/openai.yaml +4 -0
  22. package/src/qingflow_mcp/__main__.py +2 -3
  23. package/src/qingflow_mcp/app_form_compat.py +11 -27
  24. package/src/qingflow_mcp/app_form_validator.py +201 -35
  25. package/src/qingflow_mcp/builder_facade/app_form.py +762 -148
  26. package/src/qingflow_mcp/builder_facade/app_form_schema.py +114 -3
  27. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/complete.json +310 -245
  28. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/address.json +82 -27
  29. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/amount.json +85 -32
  30. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/attachment.json +82 -30
  31. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/code_block.json +113 -37
  32. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/data_relation.json +87 -64
  33. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/date.json +82 -27
  34. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/datetime.json +82 -27
  35. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/department.json +83 -28
  36. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/email.json +82 -27
  37. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/link.json +82 -27
  38. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/long_text.json +82 -27
  39. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/member.json +83 -28
  40. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/multi_select.json +82 -27
  41. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/number.json +85 -32
  42. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/phone.json +82 -27
  43. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/q_linker.json +95 -40
  44. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/relation.json +100 -28
  45. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/single_choice.json +82 -27
  46. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/single_select.json +82 -27
  47. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/subtable.json +212 -117
  48. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/text.json +82 -27
  49. package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/summary.json +143 -161
  50. package/src/qingflow_mcp/builder_facade/service.py +3 -2
  51. package/src/qingflow_mcp/response_trim.py +2 -0
  52. package/src/qingflow_mcp/server.py +24 -3
  53. package/src/qingflow_mcp/server_app_builder.py +2 -2
  54. package/src/qingflow_mcp/tools/record_tools.py +3 -3
  55. package/src/qingflow_mcp/version.py +2 -4
  56. package/docs/local-agent-install.md +0 -370
  57. package/skills/qingflow-mcp-setup/SKILL.md +0 -115
  58. package/skills/qingflow-mcp-setup/agents/openai.yaml +0 -4
  59. package/skills/qingflow-mcp-setup/references/claude-desktop.md +0 -34
  60. package/skills/qingflow-mcp-setup/references/environments.md +0 -62
  61. package/skills/qingflow-mcp-setup/references/generic-stdio.md +0 -32
  62. package/skills/qingflow-mcp-setup/scripts/check_local_server.sh +0 -38
@@ -5,7 +5,7 @@ from dataclasses import dataclass
5
5
  import json
6
6
  import os
7
7
  from pathlib import Path
8
- from typing import Any
8
+ from typing import Any, Iterable
9
9
 
10
10
  from jsonschema import Draft202012Validator, FormatChecker
11
11
 
@@ -14,6 +14,116 @@ JSONObject = dict[str, Any]
14
14
  SCHEMA_ROOT = Path(__file__).with_name("schemas") / "app_form"
15
15
 
16
16
 
17
+ def _discriminator_score(schema: Any, instance: Any, root_schema: Any = None, seen_refs: set[str] | None = None) -> int:
18
+ """Score a schema branch against its input discriminator values."""
19
+ if not isinstance(schema, dict) or not isinstance(instance, dict):
20
+ return 0
21
+ reference = schema.get("$ref")
22
+ if isinstance(reference, str) and reference.startswith("#/") and isinstance(root_schema, dict):
23
+ seen_refs = seen_refs or set()
24
+ if reference in seen_refs:
25
+ return 0
26
+ target: Any = root_schema
27
+ for part in reference[2:].split("/"):
28
+ if not isinstance(target, dict):
29
+ return 0
30
+ target = target.get(part.replace("~1", "/").replace("~0", "~"))
31
+ return _discriminator_score(target, instance, root_schema, seen_refs | {reference})
32
+ score = 0
33
+ properties = schema.get("properties")
34
+ if isinstance(properties, dict):
35
+ for key, constraint in properties.items():
36
+ if not isinstance(constraint, dict) or key not in instance:
37
+ continue
38
+ value = instance.get(key)
39
+ if "const" in constraint:
40
+ score += 3 if value == constraint["const"] else -3
41
+ elif isinstance(constraint.get("enum"), list):
42
+ score += 3 if value in constraint["enum"] else -3
43
+ for combinator in ("allOf", "anyOf", "oneOf"):
44
+ children = schema.get(combinator)
45
+ if not isinstance(children, list):
46
+ continue
47
+ child_scores = [_discriminator_score(child, instance, root_schema, seen_refs) for child in children]
48
+ if combinator == "allOf":
49
+ score += sum(child_score for child_score in child_scores if child_score > 0)
50
+ elif child_scores:
51
+ score += max(child_scores)
52
+ return score
53
+
54
+
55
+ def _selected_combinator_children(error: Any, root_schema: Any = None) -> list[Any] | None:
56
+ """Return errors from the branch matching a known discriminator.
57
+
58
+ jsonschema flattens errors from nested ``allOf`` branches into the parent
59
+ combinator context, so branch identity is recovered from the schema path.
60
+ """
61
+ if not isinstance(error.schema, dict):
62
+ return None
63
+ branches = error.schema.get(error.validator)
64
+ if not isinstance(branches, list) or not branches:
65
+ return None
66
+ scores = [_discriminator_score(branch, error.instance, root_schema) for branch in branches]
67
+ best = max(scores)
68
+ if best <= 0:
69
+ return None
70
+ selected = {index for index, score in enumerate(scores) if score == best}
71
+ prefix = tuple(error.absolute_schema_path)
72
+ children = [
73
+ child
74
+ for child in error.context
75
+ if len(tuple(child.absolute_schema_path)) > len(prefix)
76
+ and tuple(child.absolute_schema_path)[: len(prefix)] == prefix
77
+ and tuple(child.absolute_schema_path)[len(prefix)] in selected
78
+ ]
79
+ return children or None
80
+
81
+
82
+ def _iter_compact_schema_errors(error: Any, root_schema: Any = None) -> Iterable[Any]:
83
+ if error.validator in {"oneOf", "anyOf"}:
84
+ selected = _selected_combinator_children(error, root_schema)
85
+ if selected:
86
+ for child in selected:
87
+ yield from _iter_compact_schema_errors(child, root_schema)
88
+ return
89
+ yield error
90
+ return
91
+ if error.context:
92
+ for child in error.context:
93
+ yield from _iter_compact_schema_errors(child, root_schema)
94
+ return
95
+ yield error
96
+
97
+
98
+ def compact_schema_errors(errors: Iterable[Any], root_schema: Any = None) -> list[Any]:
99
+ """Collapse polymorphic-schema noise while retaining actionable errors."""
100
+ flattened = [leaf for error in errors for leaf in _iter_compact_schema_errors(error, root_schema)]
101
+ descendant_paths = [tuple(error.absolute_path) for error in flattened]
102
+ result: list[Any] = []
103
+ seen: set[tuple[tuple[Any, ...], str, str]] = set()
104
+ for error in flattened:
105
+ path = tuple(error.absolute_path)
106
+ if error.validator == "unevaluatedProperties" and any(
107
+ len(other) > len(path) and other[: len(path)] == path for other in descendant_paths
108
+ ):
109
+ continue
110
+ key = (path, str(error.validator), schema_error_message(error))
111
+ if key in seen:
112
+ continue
113
+ seen.add(key)
114
+ result.append(error)
115
+ return result
116
+
117
+
118
+ def schema_error_message(error: Any) -> str:
119
+ """Render a bounded, user-facing message for a JSON Schema error."""
120
+ if error.validator == "oneOf":
121
+ return "value is not valid under any of the given schemas"
122
+ if error.validator == "anyOf":
123
+ return "value is not valid under any of the given schemas"
124
+ return str(error.message)
125
+
126
+
17
127
  class AppFormContractError(ValueError):
18
128
  def __init__(self, error_code: str, message: str, *, path: str | None = None, details: JSONObject | None = None) -> None:
19
129
  super().__init__(message)
@@ -39,11 +149,12 @@ class AppFormSchemaContext:
39
149
  errors = sorted(validator.iter_errors(payload), key=lambda item: (list(item.absolute_path), item.message))
40
150
  if not errors:
41
151
  return
42
- error = errors[0]
152
+ compact_errors = compact_schema_errors(errors, self.complete_schema)
153
+ error = compact_errors[0] if compact_errors else errors[0]
43
154
  pointer = "".join(f"/{str(part).replace('~', '~0').replace('/', '~1')}" for part in error.absolute_path) or "/"
44
155
  raise AppFormContractError(
45
156
  "INVALID_SPEC",
46
- error.message,
157
+ schema_error_message(error),
47
158
  path=pointer,
48
159
  details={"validator": error.validator, "schemaPath": "/".join(str(part) for part in error.absolute_schema_path)},
49
160
  )