@qingflow-tech/qingflow-app-builder-mcp 1.0.35 → 1.0.36

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/README.md CHANGED
@@ -3,13 +3,13 @@
3
3
  Install:
4
4
 
5
5
  ```bash
6
- npm install @qingflow-tech/qingflow-app-builder-mcp@1.0.35
6
+ npm install @qingflow-tech/qingflow-app-builder-mcp@1.0.36
7
7
  ```
8
8
 
9
9
  Run:
10
10
 
11
11
  ```bash
12
- npx -y -p @qingflow-tech/qingflow-app-builder-mcp@1.0.35 qingflow-app-builder-mcp
12
+ npx -y -p @qingflow-tech/qingflow-app-builder-mcp@1.0.36 qingflow-app-builder-mcp
13
13
  ```
14
14
 
15
15
  Environment:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qingflow-tech/qingflow-app-builder-mcp",
3
- "version": "1.0.35",
3
+ "version": "1.0.36",
4
4
  "description": "Builder MCP for Qingflow app/package/system design and staged solution workflows.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "qingflow-mcp"
7
- version = "1.0.35"
7
+ version = "1.0.36"
8
8
  description = "User-authenticated MCP server for Qingflow"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -160,6 +160,7 @@ MATCH_TYPE_ACCURACY = 1
160
160
  MATCH_TYPE_QUESTION = 2
161
161
  JUDGE_EQUAL = 0
162
162
  JUDGE_UNEQUAL = 1
163
+ JUDGE_INCLUDE = 2
163
164
  JUDGE_GREATER_OR_EQUAL = 5
164
165
  JUDGE_LESS_OR_EQUAL = 7
165
166
  JUDGE_EQUAL_ANY = 9
@@ -10125,14 +10126,31 @@ class AiBuilderFacade:
10125
10126
  expected_filter_summary = _normalize_view_filter_groups_for_compare(expected_filters)
10126
10127
  expected_data_scope = "CUSTOM" if expected_filter_summary else "ALL"
10127
10128
  actual_data_scope = str(config_result.get("dataScope") or "").strip().upper() or None
10128
- filters_verified = (
10129
- _view_filter_groups_equivalent(expected_filter_summary, actual_filters)
10130
- and actual_data_scope == expected_data_scope
10129
+ (
10130
+ filters_semantically_equivalent,
10131
+ semantic_actual_filters,
10132
+ lossy_filter_readbacks,
10133
+ ) = _view_filter_groups_semantic_readback(expected_filter_summary, actual_filters)
10134
+ filters_verified = filters_semantically_equivalent and actual_data_scope == expected_data_scope
10135
+ public_expected_filters = _public_view_filter_groups_from_match_rules(
10136
+ expected_filter_summary,
10137
+ current_fields_by_name=current_fields_by_name,
10138
+ )
10139
+ public_actual_filters = _public_view_filter_groups_from_match_rules(
10140
+ semantic_actual_filters,
10141
+ current_fields_by_name=current_fields_by_name,
10131
10142
  )
10132
10143
  verification_entry["filters_verified"] = filters_verified
10133
10144
  verification_entry["view_key"] = verification_key
10134
- verification_entry["expected_filters"] = expected_filter_summary
10135
- verification_entry["actual_filters"] = actual_filters
10145
+ verification_entry["expected_filters"] = public_expected_filters
10146
+ verification_entry["actual_filters"] = public_actual_filters
10147
+ verification_entry["expected_filters_raw"] = expected_filter_summary
10148
+ if lossy_filter_readbacks:
10149
+ verification_entry["actual_filters_raw"] = actual_filters
10150
+ verification_entry["filter_value_readback_degraded"] = True
10151
+ verification_entry["filter_value_readback_warnings"] = lossy_filter_readbacks
10152
+ elif public_actual_filters != semantic_actual_filters:
10153
+ verification_entry["actual_filters_raw"] = semantic_actual_filters
10136
10154
  verification_entry["expected_data_scope"] = expected_data_scope
10137
10155
  verification_entry["actual_data_scope"] = actual_data_scope
10138
10156
  if not filters_verified:
@@ -10140,8 +10158,10 @@ class AiBuilderFacade:
10140
10158
  {
10141
10159
  "name": name,
10142
10160
  "type": item.get("type"),
10143
- "expected_filters": expected_filter_summary,
10144
- "actual_filters": actual_filters,
10161
+ "expected_filters": public_expected_filters,
10162
+ "actual_filters": public_actual_filters,
10163
+ "expected_filters_raw": expected_filter_summary,
10164
+ "actual_filters_raw": actual_filters if lossy_filter_readbacks else semantic_actual_filters,
10145
10165
  "expected_data_scope": expected_data_scope,
10146
10166
  "actual_data_scope": actual_data_scope,
10147
10167
  }
@@ -22586,7 +22606,7 @@ def _translate_flow_condition_rule(*, field: dict[str, Any], rule: dict[str, Any
22586
22606
  base["judgeType"] = JUDGE_INCLUDE_ANY if field_type in INCLUDE_ANY_FLOW_FIELD_TYPES else JUDGE_EQUAL_ANY
22587
22607
  base["judgeValues"] = [_stringify_condition_value(value) for value in values]
22588
22608
  elif operator == "contains":
22589
- base["judgeType"] = JUDGE_FUZZY_MATCH
22609
+ base["judgeType"] = JUDGE_INCLUDE
22590
22610
  base["judgeValues"] = [_stringify_condition_value(values[0])]
22591
22611
  elif operator == "gte":
22592
22612
  base["judgeType"] = JUDGE_GREATER_OR_EQUAL
@@ -24330,7 +24350,7 @@ def _translate_view_filter_rule(*, field: dict[str, Any], rule: dict[str, Any])
24330
24350
  payload["judgeType"] = JUDGE_INCLUDE_ANY if field_type in INCLUDE_ANY_FLOW_FIELD_TYPES else JUDGE_EQUAL_ANY
24331
24351
  payload["judgeValues"] = judge_values
24332
24352
  elif operator == "contains":
24333
- payload["judgeType"] = JUDGE_FUZZY_MATCH
24353
+ payload["judgeType"] = JUDGE_INCLUDE
24334
24354
  payload["judgeValues"] = judge_values[:1] if judge_values else []
24335
24355
  elif operator == "gte":
24336
24356
  payload["judgeType"] = JUDGE_GREATER_OR_EQUAL
@@ -24515,6 +24535,96 @@ def _view_filter_groups_equivalent(expected: Any, actual: Any) -> bool:
24515
24535
  return _view_filter_groups_signature(expected) == _view_filter_groups_signature(actual)
24516
24536
 
24517
24537
 
24538
+ def _view_filter_groups_semantic_readback(expected: Any, actual: Any) -> tuple[bool, list[list[dict[str, Any]]], list[dict[str, Any]]]:
24539
+ expected_groups = _normalize_view_filter_groups_for_compare(expected)
24540
+ actual_groups = _normalize_view_filter_groups_for_compare(actual)
24541
+ if _view_filter_groups_signature(expected_groups) == _view_filter_groups_signature(actual_groups):
24542
+ return True, actual_groups, []
24543
+ if len(expected_groups) != len(actual_groups):
24544
+ return False, actual_groups, []
24545
+ semantic_groups: list[list[dict[str, Any]]] = []
24546
+ lossy_readbacks: list[dict[str, Any]] = []
24547
+ for group_index, (expected_group, actual_group) in enumerate(zip(expected_groups, actual_groups)):
24548
+ if len(expected_group) != len(actual_group):
24549
+ return False, actual_groups, []
24550
+ semantic_group: list[dict[str, Any]] = []
24551
+ for rule_index, (expected_rule, actual_rule) in enumerate(zip(expected_group, actual_group)):
24552
+ if expected_rule.get("queId") != actual_rule.get("queId") or expected_rule.get("judgeType") != actual_rule.get("judgeType"):
24553
+ return False, actual_groups, []
24554
+ expected_values = _view_filter_rule_values_for_signature(expected_rule)
24555
+ actual_values = _view_filter_rule_values_for_signature(actual_rule)
24556
+ if expected_values == actual_values:
24557
+ semantic_group.append(actual_rule)
24558
+ continue
24559
+ if (
24560
+ expected_values
24561
+ and not actual_values
24562
+ and actual_rule.get("judgeType") in {JUDGE_INCLUDE, JUDGE_FUZZY_MATCH}
24563
+ ):
24564
+ semantic_rule = deepcopy(actual_rule)
24565
+ semantic_rule["judgeValues"] = expected_values
24566
+ semantic_group.append(semantic_rule)
24567
+ lossy_readbacks.append(
24568
+ {
24569
+ "group_index": group_index,
24570
+ "rule_index": rule_index,
24571
+ "queId": actual_rule.get("queId"),
24572
+ "judgeType": actual_rule.get("judgeType"),
24573
+ "message": "view filter literal value was accepted by write path but omitted by raw viewConfig readback; semantic readback was reconstructed from the write payload",
24574
+ }
24575
+ )
24576
+ continue
24577
+ return False, actual_groups, []
24578
+ semantic_groups.append(semantic_group)
24579
+ return True, semantic_groups, lossy_readbacks
24580
+
24581
+
24582
+ def _public_view_filter_groups_from_match_rules(
24583
+ groups: Any,
24584
+ *,
24585
+ current_fields_by_name: dict[str, dict[str, Any]],
24586
+ ) -> list[list[dict[str, Any]]]:
24587
+ fields_by_que_id = {
24588
+ _coerce_positive_int(field.get("que_id")): field
24589
+ for field in current_fields_by_name.values()
24590
+ if isinstance(field, dict) and _coerce_positive_int(field.get("que_id")) is not None
24591
+ }
24592
+ public_groups: list[list[dict[str, Any]]] = []
24593
+ for group in _normalize_view_filter_groups_for_compare(groups):
24594
+ public_group: list[dict[str, Any]] = []
24595
+ for rule in group:
24596
+ que_id = _coerce_positive_int(rule.get("queId")) or 0
24597
+ field = fields_by_que_id.get(que_id) or {}
24598
+ values = _view_filter_rule_values_for_signature(rule)
24599
+ public_rule: dict[str, Any] = {
24600
+ "field_name": str(field.get("name") or rule.get("queTitle") or que_id),
24601
+ "operator": _public_view_filter_operator_from_judge_type(rule.get("judgeType")),
24602
+ }
24603
+ if values:
24604
+ public_rule["values"] = values
24605
+ public_group.append(public_rule)
24606
+ if public_group:
24607
+ public_groups.append(public_group)
24608
+ return public_groups
24609
+
24610
+
24611
+ def _public_view_filter_operator_from_judge_type(judge_type: Any) -> str:
24612
+ normalized = _coerce_positive_int(judge_type)
24613
+ if normalized == JUDGE_EQUAL:
24614
+ return "eq"
24615
+ if normalized == JUDGE_UNEQUAL:
24616
+ return "neq"
24617
+ if normalized in {JUDGE_INCLUDE, JUDGE_FUZZY_MATCH}:
24618
+ return "contains"
24619
+ if normalized in {JUDGE_EQUAL_ANY, JUDGE_INCLUDE_ANY}:
24620
+ return "in"
24621
+ if normalized == JUDGE_GREATER_OR_EQUAL:
24622
+ return "gte"
24623
+ if normalized == JUDGE_LESS_OR_EQUAL:
24624
+ return "lte"
24625
+ return f"judge_type:{judge_type}"
24626
+
24627
+
24518
24628
  def _infer_status_field_id(fields: list[dict[str, Any]]) -> str | None:
24519
24629
  preferred_names = {"status", "状态", "订单状态", "审批状态", "流程状态"}
24520
24630
  for field in fields: