@thinkingai/ae-cli 6.1.27 → 6.1.29

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 (177) hide show
  1. package/README.md +52 -6
  2. package/README.zh.md +52 -6
  3. package/bin/ae-cli.js +5 -0
  4. package/bin/runtime-capabilities.json +5 -0
  5. package/cli-installation-guide.md +24 -3
  6. package/dist/{auth-WA7YGGRT.js → auth-OBQ5KVQM.js} +175 -45
  7. package/dist/{automation-VAAQBM7Y.js → automation-NAR5DDGL.js} +94 -38
  8. package/dist/{capability-GAAYJ3IQ.js → capability-LFSVNI5B.js} +57 -28
  9. package/dist/chunk-2UW3Y463.js +2289 -0
  10. package/dist/{chunk-VZZNLV3I.js → chunk-3SDHJOCR.js} +68 -18
  11. package/dist/chunk-6HAUVWOQ.js +132 -0
  12. package/dist/{chunk-5XJYLEVC.js → chunk-72NJKS7O.js} +4 -4
  13. package/dist/{chunk-Q5US7JC5.js → chunk-7JAVT5TK.js} +3 -3
  14. package/dist/{chunk-7KU2WNQE.js → chunk-CVCTFLSO.js} +37 -15
  15. package/dist/{chunk-GHXZK54S.js → chunk-H3K5UGYC.js} +41 -13
  16. package/dist/{chunk-OP44TT4H.js → chunk-IN7KKL6K.js} +4 -2
  17. package/dist/{chunk-S6M42KND.js → chunk-JEUXDY3B.js} +1 -1
  18. package/dist/{chunk-ZL7SGNIA.js → chunk-JNVVJ273.js} +58 -46
  19. package/dist/chunk-N6XBLRB7.js +105 -0
  20. package/dist/{chunk-AP7YP2NX.js → chunk-OAWPT7VP.js} +2 -2
  21. package/dist/{chunk-AQJPNWZF.js → chunk-PWXDWMCW.js} +7 -7
  22. package/dist/{chunk-CT7QDJS6.js → chunk-QVRIDN42.js} +2 -2
  23. package/dist/chunk-QZLRHGVH.js +222 -0
  24. package/dist/{chunk-RPBOAQQQ.js → chunk-QZUVLFNR.js} +1 -1
  25. package/dist/{chunk-3YVEAZUN.js → chunk-RJHGDWSC.js} +32 -21
  26. package/dist/{chunk-MWIA43UJ.js → chunk-VCZE3IWO.js} +10 -8
  27. package/dist/chunk-YVG6QD3V.js +233 -0
  28. package/dist/{chunk-WNGFSIOB.js → chunk-Z3Q2AEIN.js} +237 -86
  29. package/dist/{community-report-client-PEERQCX2.js → community-report-client-TJVCLZ3K.js} +1 -1
  30. package/dist/compat-check-CII6B74E.js +32 -0
  31. package/dist/{config-JOD5DOPE.js → config-YZ6O2UQ2.js} +40 -7
  32. package/dist/{context-NGVFPCBK.js → context-F6FCGTF3.js} +4 -4
  33. package/dist/{data-integration-BCLQFTGU.js → data-integration-6UAZO4GG.js} +207 -24
  34. package/dist/evaluation-4LO2O4AF.js +3714 -0
  35. package/dist/index.js +352 -81
  36. package/dist/{local-data-upload-client-TWMHFLUA.js → local-data-upload-client-KCMG6J62.js} +1 -1
  37. package/dist/{memory-CQKH2JAV.js → memory-HHPX3NF2.js} +6 -6
  38. package/dist/{metadata-NR3PHR23.js → metadata-TIFY24LW.js} +17 -9
  39. package/dist/{model-VX5M2CJ6.js → model-7UHGWYVR.js} +4 -4
  40. package/dist/{personal-semantic-preference-2RAI36P3.js → personal-semantic-preference-WKJEGPTM.js} +7 -6
  41. package/dist/{project-semantic-3BUGP3ZM.js → project-semantic-EIQVZNHW.js} +11 -10
  42. package/dist/{sync-RZIAV37C.js → sync-RV3OPCTI.js} +8 -7
  43. package/dist/{te-agent-AORRE2FM.js → te-agent-5ENVZJUH.js} +757 -94
  44. package/dist/{te-analysis-GP7DSQ7Q.js → te-analysis-USIK6ZZG.js} +584 -100
  45. package/dist/{te-community-42M7ICDO.js → te-community-WE2UU4XV.js} +722 -228
  46. package/dist/{te-dataops-BNKNZ4F2.js → te-dataops-XNMO7MBF.js} +9 -6
  47. package/dist/{te-engage-2CO6LRCX.js → te-engage-6C3HCFQR.js} +13 -12
  48. package/dist/{te-experiment-652CLXMU.js → te-experiment-LVTRI3QK.js} +55 -21
  49. package/dist/{te-kb-ULATOTQ3.js → te-kb-TUPGMVBY.js} +409 -85
  50. package/dist/{te-system-UGHVKBVV.js → te-system-YM657FKO.js} +6 -6
  51. package/dist/{te-team-YA6SNOJ3.js → te-team-G4VEJJNL.js} +64 -41
  52. package/dist/{things-DTCOQIQB.js → things-22KQWC23.js} +7 -7
  53. package/dist/{update-ESNYKUVR.js → update-XX4ILU7X.js} +11 -7
  54. package/dist/{workspace-JEOHKFPX.js → workspace-LYDAP647.js} +6 -6
  55. package/package.json +26 -11
  56. package/skills/ae-agent/SKILL.md +22 -3
  57. package/skills/ae-agent/references/command_index.md +8 -0
  58. package/skills/ae-agent/references/resource-package.md +57 -0
  59. package/skills/ae-analysis/SKILL.md +23 -10
  60. package/skills/ae-analysis/references/adhoc_export.md +1 -1
  61. package/skills/ae-analysis/references/agent_review_preflight.md +55 -10
  62. package/skills/ae-analysis/references/agent_review_priorities_comparisons.md +5 -3
  63. package/skills/ae-analysis/references/agent_review_submit_to_page.md +60 -24
  64. package/skills/ae-analysis/references/ai_models/event.md +58 -2
  65. package/skills/ae-analysis/references/ai_models/rank_list.md +1 -0
  66. package/skills/ae-analysis/references/ai_models/retention.md +2 -0
  67. package/skills/ae-analysis/references/ai_models/revenue.md +2 -0
  68. package/skills/ae-analysis/references/analysis_data_export.md +11 -1
  69. package/skills/ae-analysis/references/asset_batch_info_export.md +2 -2
  70. package/skills/ae-analysis/references/asset_batch_sql_export.md +2 -2
  71. package/skills/ae-analysis/references/asset_export.md +1 -1
  72. package/skills/ae-analysis/references/bi_panel_page_data_export.md +1 -1
  73. package/skills/ae-analysis/references/check_export.md +1 -1
  74. package/skills/ae-analysis/references/command_index.md +39 -26
  75. package/skills/ae-analysis/references/dashboard_report_data_export.md +2 -0
  76. package/skills/ae-analysis/references/dashboard_report_get.md +6 -2
  77. package/skills/ae-analysis/references/dashboard_report_update.md +24 -5
  78. package/skills/ae-analysis/references/feedback_submit.md +1 -1
  79. package/skills/ae-analysis/references/governance_recommendation_export.md +40 -18
  80. package/skills/ae-analysis/references/governance_recommendation_run_update.md +13 -0
  81. package/skills/ae-analysis/references/live_data_export.md +1 -1
  82. package/skills/ae-analysis/references/metric_create.md +29 -0
  83. package/skills/ae-analysis/references/metric_get.md +2 -0
  84. package/skills/ae-analysis/references/metric_update.md +2 -0
  85. package/skills/ae-analysis/references/operation_record_export.md +1 -1
  86. package/skills/ae-analysis/references/plan_change_log_export.md +1 -1
  87. package/skills/ae-analysis/references/plan_export.md +1 -1
  88. package/skills/ae-analysis/references/project_info_get.md +4 -0
  89. package/skills/ae-analysis/references/project_member_group_add_members.md +35 -0
  90. package/skills/ae-analysis/references/project_member_group_create.md +25 -0
  91. package/skills/ae-analysis/references/project_member_group_delete.md +34 -0
  92. package/skills/ae-analysis/references/project_member_group_list.md +29 -0
  93. package/skills/ae-analysis/references/project_member_group_members.md +28 -0
  94. package/skills/ae-analysis/references/project_member_group_remove_members.md +35 -0
  95. package/skills/ae-analysis/references/project_member_group_rename.md +26 -0
  96. package/skills/ae-analysis/references/project_semantic_knowledge_wiki.md +9 -1
  97. package/skills/ae-analysis/references/project_semantic_knowledge_wiki_plan_schema.md +6 -1
  98. package/skills/ae-analysis/references/report_data_export.md +1 -1
  99. package/skills/ae-analysis/references/scenario_append.md +31 -0
  100. package/skills/ae-analysis/references/scenario_begin.md +11 -0
  101. package/skills/ae-analysis/references/scenario_collect.md +43 -0
  102. package/skills/ae-analysis/references/scenario_evaluation.md +18 -0
  103. package/skills/ae-analysis/references/scenario_get.md +15 -0
  104. package/skills/ae-analysis/references/scenario_list.md +64 -0
  105. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/build-project-semantic-wiki.mjs +73 -4
  106. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/company-kb.mjs +14 -1
  107. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/default-compile-schema.md +5 -1
  108. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/execution-contract.mjs +173 -0
  109. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/generate-build-ir.mjs +109 -64
  110. package/skills/ae-automation/SKILL.md +2 -2
  111. package/skills/ae-automation/references/cli.md +18 -1
  112. package/skills/ae-capability/SKILL.md +7 -3
  113. package/skills/ae-community/SKILL.md +77 -27
  114. package/skills/ae-community/references/community-analyzing-theme-comment.md +10 -8
  115. package/skills/ae-community/references/community-data-quality.md +68 -0
  116. package/skills/ae-community/references/community-query-evidence.md +85 -0
  117. package/skills/ae-community/references/community-scenario-capabilities.md +137 -0
  118. package/skills/ae-community/references/community-weekly-report.md +6 -3
  119. package/skills/ae-community/references/get_channel_info.md +7 -4
  120. package/skills/ae-community/references/get_comment_tag_analysis.md +3 -3
  121. package/skills/ae-community/references/get_comments_summary.md +4 -4
  122. package/skills/ae-community/references/get_corpus_tags.md +3 -3
  123. package/skills/ae-community/references/get_daily_summary.md +3 -3
  124. package/skills/ae-community/references/get_hot_topics.md +3 -3
  125. package/skills/ae-community/references/get_livestream_analysis.md +3 -3
  126. package/skills/ae-community/references/get_livestream_detail.md +4 -4
  127. package/skills/ae-community/references/get_livestream_list.md +3 -3
  128. package/skills/ae-community/references/get_livestream_overview.md +3 -3
  129. package/skills/ae-community/references/get_livestream_room_metrics.md +3 -3
  130. package/skills/ae-community/references/get_livestream_rooms.md +3 -3
  131. package/skills/ae-community/references/get_overview_metrics.md +3 -3
  132. package/skills/ae-community/references/get_post_detail.md +4 -4
  133. package/skills/ae-community/references/get_risk_content.md +5 -5
  134. package/skills/ae-community/references/get_sentiment_overview.md +4 -4
  135. package/skills/ae-community/references/get_tag_trends.md +3 -3
  136. package/skills/ae-community/references/get_topic_detail.md +3 -3
  137. package/skills/ae-community/references/list_sources.md +28 -0
  138. package/skills/ae-community/references/list_spaces.md +39 -0
  139. package/skills/ae-community/references/search_posts.md +21 -7
  140. package/skills/ae-data-integration/SKILL.md +6 -4
  141. package/skills/ae-data-integration/references/error-handling.md +1 -0
  142. package/skills/ae-data-integration/references/handoff.md +1 -1
  143. package/skills/ae-data-integration/references/sink-upload.md +11 -0
  144. package/skills/ae-data-integration/references/source-inspect.md +2 -1
  145. package/skills/ae-data-integration/references/transform.md +1 -1
  146. package/skills/ae-data-integration/references/ue-mapping.md +1 -1
  147. package/skills/ae-evaluation/SKILL.md +83 -0
  148. package/skills/ae-evaluation/references/access-and-members.md +55 -0
  149. package/skills/ae-evaluation/references/connections.md +78 -0
  150. package/skills/ae-evaluation/references/datasets.md +57 -0
  151. package/skills/ae-evaluation/references/evaluators.md +81 -0
  152. package/skills/ae-evaluation/references/files.md +33 -0
  153. package/skills/ae-evaluation/references/integrations.md +39 -0
  154. package/skills/ae-evaluation/references/runs.md +160 -0
  155. package/skills/ae-evaluation/references/schedules.md +31 -0
  156. package/skills/ae-experiment/SKILL.md +7 -1
  157. package/skills/ae-experiment/references/manage_experiment.md +18 -1
  158. package/skills/ae-experiment/references/query_experiment_detail.md +7 -0
  159. package/skills/ae-experiment/references/save_experiment.md +76 -2
  160. package/skills/ae-experiment/references/save_submit_experiment.md +7 -0
  161. package/skills/ae-kb/SKILL.md +33 -7
  162. package/skills/ae-kb/references/publication.md +81 -0
  163. package/skills/ae-kb/references/query-workflow.md +14 -1
  164. package/skills/ae-metadata/SKILL.md +3 -2
  165. package/skills/ae-metadata/references/metadata_data_table_download.md +7 -1
  166. package/skills/ae-metadata/references/metadata_data_table_get.md +37 -0
  167. package/skills/ae-system/SKILL.md +1 -1
  168. package/skills/ae-team/SKILL.md +2 -2
  169. package/skills/ae-team/references/run-start.md +1 -0
  170. package/skills/ae-team/references/run-watch.md +7 -5
  171. package/skills/ae-things/SKILL.md +53 -51
  172. package/skills/ae-things/references/cli.md +56 -54
  173. package/skills/ae-things-progress/SKILL.md +20 -20
  174. package/skills/ae-things-review/SKILL.md +19 -19
  175. package/skills/ae-workspace/SKILL.md +1 -1
  176. package/dist/chunk-6VNIR4AP.js +0 -1422
  177. package/dist/chunk-CI24BDR2.js +0 -43
@@ -0,0 +1,173 @@
1
+ // Structural facts only. Business meanings and asset routes remain Agent-authored.
2
+ const obj = value => value && typeof value === 'object' && !Array.isArray(value) ? value : {};
3
+ const list = value => Array.isArray(value) ? value : [];
4
+ const valueOf = value => {
5
+ if (typeof value !== 'string') return value;
6
+ try { return JSON.parse(value); } catch { return value; }
7
+ };
8
+ const present = value => value !== undefined && value !== null && value !== '';
9
+ const pick = (source, keys) => Object.fromEntries(keys.filter(key => present(source[key])).map(key => [key, source[key]]));
10
+ const displayKeys = new Set(['display_name', 'eventNameDisplay', 'eventDesc', 'analysisDesc', 'quotaDesc', 'columnDesc', 'eventUuid', 'uuid']);
11
+
12
+ export function semanticValue(value) {
13
+ if (Array.isArray(value)) return value.map(semanticValue);
14
+ if (!value || typeof value !== 'object') return value;
15
+ return Object.fromEntries(Object.keys(value).filter(key => !displayKeys.has(key)).sort()
16
+ .map(key => [key, semanticValue(value[key])]));
17
+ }
18
+
19
+ export function semanticTime(value) {
20
+ const time = { ...obj(value) };
21
+ if (present(time.recentDay ?? time.recent_day)) {
22
+ for (const key of ['startTime', 'endTime', 'start_time', 'end_time']) delete time[key];
23
+ }
24
+ return time;
25
+ }
26
+
27
+ export function logicalRelation(value) {
28
+ const relation = String(value ?? '').toLowerCase();
29
+ return ({ '1': 'AND', '0': 'OR', and: 'AND', or: 'OR' })[relation] || `UNKNOWN(${relation || 'unspecified'})`;
30
+ }
31
+
32
+ export function conditionTree(value, relation) {
33
+ if (Array.isArray(value)) return { relation: logicalRelation(relation), items: value.map(item => conditionTree(item)) };
34
+ const source = obj(value);
35
+ const nested = source.filts ?? source.items ?? source.filters;
36
+ if (nested != null) {
37
+ if (!Array.isArray(nested)) return { unknown: true, raw: source };
38
+ return { relation: logicalRelation(source.relation ?? source.junctionKind ?? relation), items: nested.map(item => conditionTree(item)) };
39
+ }
40
+ if (!Object.keys(source).length) return { unknown: true };
41
+ return { predicate: source };
42
+ }
43
+
44
+ export function renderConditionTree(value, leaf) {
45
+ if (value.predicate) return leaf(value.predicate);
46
+ if (value.unknown) return 'Unknown predicate; verify the source definition';
47
+ if (!value.items.length) return 'No conditions in this scope';
48
+ if (value.items.length === 1) return renderConditionTree(value.items[0], leaf);
49
+ return `(${value.items.map(item => renderConditionTree(item, leaf)).join(` ${value.relation} `)})`;
50
+ }
51
+
52
+ export function metricContract(item) {
53
+ const formula = obj(item.formula_definition ?? item.formula);
54
+ return {
55
+ ...pick(item, ['event_name', 'aggregation_code', 'measure_key', 'formula_expression', 'entity_grain', 'aggregation_parameters', 'time_scope']),
56
+ filters: item.filters == null ? null : conditionTree(item.filters, item.relation),
57
+ operand_filters: list(item.custom_filters).map(filter => ({ index: filter.index, tree: conditionTree(filter, filter.relation) })),
58
+ operands: list(formula.formulationDeps).map((dependency, index) => ({ index,
59
+ ...pick(obj(dependency), ['event', 'quota', 'property', 'date', 'timeRange', 'time_scope', 'quotaTimeRange']),
60
+ })),
61
+ operand_time_ranges: item.quota_time_ranges ?? item.quotaTimeRanges ?? null,
62
+ };
63
+ }
64
+
65
+ export const modelConfigKeys = [
66
+ 'windowsGapValue', 'windowsGapUnit', 'percentConfig', 'userCrowds', 'retentionType',
67
+ 'rtnRateOrNum', 'statType', 'retentionCollectSet', 'relationProps', 'relationPropSplit',
68
+ 'unitNum', 'eventModel', 'taIdMeasureVo',
69
+ ];
70
+ const timeKeys = ['recentDay', 'startTime', 'endTime', 'startToNow', 'startToYesterday',
71
+ 'timeParticleSize', 'firstDayOfWeek', 'zoneOffset', 'timezone', 'completeIncompletePeriod', 'hideIncompleteData'];
72
+ const interpretedViewKeys = new Set([...timeKeys, ...modelConfigKeys, 'filts', 'groupBy', 'group_by',
73
+ 'relation', 'sqlViewParams', 'graphShape', 'projectId', 'displayQuotas', 'displayGroups']);
74
+
75
+ export function reportContract(report, context = report.calculation_context || {}) {
76
+ const view = obj(valueOf(context.event_view));
77
+ const savedParameters = context.saved_parameters ?? view.sqlViewParams ?? null;
78
+ const parameters = list(context.sql_params ?? context.parameters).map(parameter => ({ ...parameter,
79
+ name: parameter.name ?? parameter.paramName,
80
+ }));
81
+ return {
82
+ model: report.report_model,
83
+ groups: context.group_by ?? view.groupBy ?? view.group_by ?? [],
84
+ time: semanticTime({ ...pick(view, timeKeys), ...obj(context.time_scope) }),
85
+ filters: conditionTree(context.filters ?? view.filts ?? [], context.relation ?? view.relation),
86
+ metrics: list(report.metric_occurrences).map(metricContract),
87
+ model_config: { ...pick(view, modelConfigKeys), ...obj(context.model_config) },
88
+ unparsed_model_config: Object.fromEntries(Object.entries(view).filter(([key]) => !interpretedViewKeys.has(key))),
89
+ model_events: list(valueOf(context.source_events)).map(event => pick(obj(event), ['eventName', 'type', 'filts', 'relation', 'analysis', 'analysisParams', 'quota', 'quotaEntities'])),
90
+ parameters,
91
+ saved_parameters: Array.isArray(savedParameters) ? savedParameters.map(parameter => Object.keys(obj(parameter)).length ? semanticTime(parameter) : parameter) : savedParameters,
92
+ sql_projections: report.definition_kind === 'SQL' ? list(obj(context.sql_semantic_facts).final_select?.fields) : [],
93
+ };
94
+ }
95
+
96
+ export function reportSignature(report, context = report.calculation_context || {}, includeSavedSettings = true) {
97
+ const contract = reportContract(report, context);
98
+ contract.time = semanticTime(contract.time);
99
+ if (!includeSavedSettings) contract.unparsed_model_config = {};
100
+ if (report.definition_kind === 'SQL') {
101
+ const definitions = contract.parameters.map(parameter => pick(parameter, ['name', 'paramType', 'type', 'options', 'selectorItems', 'required', 'useTimezone', 'use_timezone']));
102
+ return JSON.stringify(semanticValue({ sql: String(context.sql || '').replace(/\r\n?/g, '\n').trim(),
103
+ parameters: definitions, ...(includeSavedSettings ? { saved_parameters: contract.saved_parameters, time: contract.time } : {}) }));
104
+ }
105
+ return JSON.stringify(semanticValue(contract));
106
+ }
107
+
108
+ export function enrichMetricEvidence(metrics, context) {
109
+ const source = list(valueOf(context.source_events));
110
+ return metrics.map((metric, index) => {
111
+ const raw = obj(source[index]);
112
+ if (source.length !== metrics.length || raw.eventName !== metric.event_name || String(raw.analysis ?? '') !== String(metric.aggregation_code ?? '')) return metric;
113
+ return { ...metric,
114
+ ...(raw.analysisParams != null ? { aggregation_parameters: raw.analysisParams } : {}),
115
+ ...(raw.quotaEntities != null ? { entity_grain: raw.quotaEntities } : {}),
116
+ ...(raw.quotaTimeRanges != null ? { quota_time_ranges: raw.quotaTimeRanges } : {}),
117
+ };
118
+ });
119
+ }
120
+
121
+ const inline = value => `\`${JSON.stringify(value).replaceAll('`', '\\`').replaceAll('\n', ' ')}\``;
122
+
123
+ export function renderExecutionContract(contract) {
124
+ const rows = [
125
+ '- These are saved definition settings. Execution overrides and inherited dashboard settings require live verification.',
126
+ '- Saved result grouping is listed below. SQL output grain and inherited settings require the asset explanation; referenced metadata is not a saved group.',
127
+ ...contract.groups.map((group, index) => `- Group ${index + 1}: ${inline(group)}`),
128
+ `- Saved population filter structure: ${inline(contract.filters)}`,
129
+ `- Saved time settings: ${Object.keys(contract.time).length ? inline(contract.time) : 'unknown; verify the current definition'}`,
130
+ '- State timing: a historical behavior window does not establish historical values for user or dimension tables. Source refresh times and historical reconstruction require source evidence.',
131
+ '- Empty/missing rows, null, empty arrays, false and zero are distinct. Do not fill missing rows with zero without coverage evidence.',
132
+ ];
133
+ if (contract.groups.some(group => /array/i.test(JSON.stringify(group)))) rows.push('- Array grouping can expand an entity into several rows. Distinct counts, ratios and percentiles are not additive across those rows.');
134
+ if (Object.keys(contract.model_config).length) rows.push(`- Model-specific settings: ${inline(contract.model_config)}`);
135
+ if (Object.keys(contract.unparsed_model_config).length) rows.push(`- Uninterpreted saved configuration; semantic support is unknown: ${inline(contract.unparsed_model_config)}`);
136
+ if ((contract.model_config.windowsGapValue != null || list(contract.model_config.userCrowds).length) && contract.model_events.length) rows.push(`- Model event roles and measures: ${inline(contract.model_events)}`);
137
+ if (contract.parameters.length) rows.push(`- Parameter definitions: ${inline(contract.parameters)}`);
138
+ if (contract.saved_parameters != null) rows.push(`- Saved parameter choices (separate from definitions and call-time overrides): ${inline(contract.saved_parameters)}`);
139
+ if (contract.sql_projections.length) {
140
+ const projections = contract.sql_projections.filter(field => !/\$\{/.test(field.expression || ''));
141
+ rows.push(`- SQL outer projection evidence: ${inline(projections)}`);
142
+ rows.push('- SQL lineage completeness: unknown for nested scopes, CTEs, wildcard fields and dynamic expressions. Output names alone do not prove source meanings; verify unresolved columns.');
143
+ }
144
+ for (const [index, metric] of contract.metrics.entries()) {
145
+ if (metric.operands.length || metric.operand_filters.length || metric.aggregation_parameters != null || metric.entity_grain != null) {
146
+ rows.push(`- Measure ${index + 1} calculation scopes: ${inline(metric)}`);
147
+ }
148
+ }
149
+ return `\n\n## Execution contract\n\n${rows.join('\n')}\n`;
150
+ }
151
+
152
+ export function validateSqlExplanations(summary, sql) {
153
+ const normalize = value => String(value).replace(/\s+/g, ' ').trim();
154
+ for (const key of ['calculation_rules', 'time_semantics', 'coverage_gaps']) {
155
+ if (summary[key] == null) continue;
156
+ if (!Array.isArray(summary[key])) throw new Error(`${key} must be an array.`);
157
+ for (const item of summary[key]) {
158
+ if (!item?.meaning || !item?.evidence || !normalize(sql).includes(normalize(item.evidence))) {
159
+ throw new Error(`${key} requires a meaning and exact SQL evidence from the current definition.`);
160
+ }
161
+ }
162
+ }
163
+ for (const field of list(summary.output_fields)) {
164
+ if (field.evidence != null && !normalize(sql).includes(normalize(field.evidence))) {
165
+ throw new Error('Output field lineage evidence must occur in the current SQL.');
166
+ }
167
+ }
168
+ }
169
+
170
+ export function renderSqlExplanations(summary) {
171
+ return ['calculation_rules', 'time_semantics', 'coverage_gaps'].flatMap(key => list(summary?.[key])
172
+ .map(item => `- ${key}: ${item.meaning} (evidence: ${inline(item.evidence)})`)).join('\n');
173
+ }
@@ -3,6 +3,9 @@
3
3
  import { createHash, randomBytes } from 'node:crypto';
4
4
  import fs from 'node:fs';
5
5
  import path from 'node:path';
6
+ import { conditionTree, renderConditionTree, reportContract, reportSignature,
7
+ semanticValue, enrichMetricEvidence, renderExecutionContract, validateSqlExplanations,
8
+ renderSqlExplanations } from './execution-contract.mjs';
6
9
 
7
10
  const args = parseArgs(process.argv.slice(2));
8
11
  const packageRoot = path.resolve(requiredArg(args, 'asset-package'));
@@ -99,13 +102,19 @@ const sqlReportSemanticsById = new Map(array(domainPlan.sql_report_semantics)
99
102
  .map((value) => object(value))
100
103
  .map((value) => [text(value.report_id), value])
101
104
  .filter(([id]) => id));
105
+ for (const report of reports) {
106
+ report.calculation_context = calculationContextForKb(report);
107
+ report.metric_occurrences = enrichMetricEvidence(array(report.metric_occurrences), report.calculation_context);
108
+ }
102
109
  const semanticConflicts = detectSemanticConflicts();
103
110
  const reportConflicts = semanticConflicts.reportConflicts;
111
+ const reportSettingVariants = semanticConflicts.executionVariants;
104
112
  const metadataSemanticConflicts = semanticConflicts.metadataConflicts;
105
113
  const metadataMissingTitles = metadata.filter((item) => !text(item.title));
106
114
  const reportsInBusinessDomains = reports.filter((report) => domainsForReport(report).length);
107
115
  const reportsInTechnicalAppendices = reports.filter((report) => appendicesForReport(report).length);
108
116
  const warnings = [
117
+ ...[...reportSettingVariants.keys()].map(id => `Report ${id} has matching calculation evidence but different saved or uninterpreted settings; verify explicit overrides before reuse.`),
109
118
  ...(text(descriptor.asset_scope ?? manifest.asset_scope) !== 'governed'
110
119
  ? [`The package scope is ${text(descriptor.asset_scope ?? manifest.asset_scope) || 'unknown'}; unauthenticated assets may be present.`]
111
120
  : []),
@@ -533,6 +542,9 @@ function validateSqlSemanticPlan(plan) {
533
542
  const summary = object(summaryValue);
534
543
  const reportId = text(summary.report_id);
535
544
  if (!sqlReportsById.has(reportId)) continue;
545
+ try {
546
+ validateSqlExplanations(summary, sqlTextFromCalculationContext(calculationContextForKb(sqlReportsById.get(reportId))));
547
+ } catch (error) { fail(`SQL report ${reportId}: ${error.message}`); }
536
548
  if (text(summary.definition_state) !== 'valid') continue;
537
549
  const issues = sqlSemanticQualityIssues(summary);
538
550
  if (issues.length) {
@@ -1036,13 +1048,29 @@ function renderDashboardAgentUsageSummary(dashboard, reportRows, notes, domain,
1036
1048
  }
1037
1049
 
1038
1050
  function renderReportAgentUsageSummary(report, metadataRefs, calculationContext, sqlSummary) {
1051
+ const explanation = renderSqlExplanations(sqlSummary);
1052
+ const coverage = text(report.definition_kind) === 'SQL' && !array(sqlSummary?.calculation_rules).length
1053
+ ? '\n\n- Calculation coverage: unknown for this legacy summary. Verify the current SQL for operand filters, denominator, units, rounding, time baselines and excluded rows before execution.' : '';
1054
+ return renderReportAuthoredSummary(report, metadataRefs, calculationContext, sqlSummary)
1055
+ + (explanation ? `\n\n### Calculation evidence\n\n${explanation}` : '')
1056
+ + coverage
1057
+ + renderExecutionContract(reportContract(report, calculationContext))
1058
+ + (reportSettingVariants.has(text(report.resource_key)) ? '\n- Saved-setting variant: matching calculation evidence does not establish identical defaults. Verify or explicitly override the required settings before reuse.\n' : '');
1059
+ }
1060
+
1061
+ function renderReportAuthoredSummary(report, metadataRefs, calculationContext, sqlSummary) {
1039
1062
  const isSql = text(report.definition_kind) === 'SQL';
1040
1063
  if (isSql) return renderSqlReportAgentUsageSummary(report, sqlSummary);
1064
+ const modelConfig = reportContract(report, calculationContext).model_config;
1065
+ if (array(modelConfig.userCrowds).length || modelConfig.windowsGapValue != null) {
1066
+ const model = array(modelConfig.userCrowds).length ? 'Saved population analysis' : 'Saved interval analysis';
1067
+ return `- Model: ${model}.\n- Purpose: ${text(report.description || report.summary) || 'Verify the business question against the saved model settings below.'}\n- Result semantics: use the complete population conditions or ordered event roles, window and selected statistics in the execution contract.\n- Missing configuration, entity scope and historical state remain unknown; do not infer ordinary event metrics from model endpoints.`;
1068
+ }
1041
1069
  const purpose = usefulSemanticText(sqlSummary?.business_purpose) || reportPurpose(report);
1042
1070
  const questions = usefulSemanticList(sqlSummary?.applicable_questions)
1043
1071
  ? array(sqlSummary.applicable_questions).map(renderPlainSemanticItem)
1044
1072
  : reportApplicableQuestions(report);
1045
- const metrics = topDomainFacts(array(report.metric_occurrences).map(occurrenceSentence), 12);
1073
+ const metrics = array(report.metric_occurrences).map(occurrenceSentence);
1046
1074
  const parameters = reportParameterSummaryItems(calculationContext);
1047
1075
  const dimensions = reportDimensionSummaryItems(report, sqlSummary, calculationContext, metadataRefs);
1048
1076
  const filters = reportFilterSummaryItems(report, calculationContext);
@@ -1083,18 +1111,29 @@ function renderSqlReportAgentUsageSummary(report, summary) {
1083
1111
  }
1084
1112
 
1085
1113
  function renderSqlSemanticItem(value, summary) {
1114
+ const item = object(value);
1115
+ if (Object.keys(item).length && item.required == null && item.default_value == null && !Array.isArray(item.options)) {
1116
+ return [text(item.name) ? `\`${text(item.name)}\`` : '', sanitizeVisibleSqlSemanticText(item.meaning)].filter(Boolean).join(':')
1117
+ + (item.evidence ? ` [source: ${jsonInline(item.evidence)}]` : '')
1118
+ + (item.identity_state === 'unknown' ? ' [output identity unverified; match the current SQL projection before use]' : '');
1119
+ }
1086
1120
  return replaceSqlParameterMentions(renderSemanticItem(value), summary);
1087
1121
  }
1088
1122
 
1089
1123
  function renderSqlInputParameterItem(value) {
1090
- return sanitizeVisibleSqlSemanticText(renderSemanticItem(value));
1124
+ if (!value || typeof value !== 'object') return sanitizeVisibleSqlSemanticText(value);
1125
+ return renderSemanticItem({ ...value, meaning: sanitizeVisibleSqlSemanticText(value.meaning) });
1091
1126
  }
1092
1127
 
1093
1128
  function replaceSqlParameterMentions(value, summary) {
1094
- let result = sanitizeVisibleSqlSemanticText(value);
1129
+ let result = text(value);
1095
1130
  for (const [name, meaning] of sqlParameterMeaningEntries(summary)) {
1096
- const pattern = new RegExp(`(?<![A-Za-z0-9_#$])${escapeRegExp(name)}(?![A-Za-z0-9_#$]|\\s*\\()`, 'giu');
1131
+ const pattern = new RegExp(`\\$\\{(?:[A-Za-z]+:)?${escapeRegExp(name)}\\}`, 'gu');
1097
1132
  result = result.replace(pattern, meaning);
1133
+ if (/^(?:selector|variable|text|number|date)\d+$/i.test(name)) {
1134
+ const generatedReference = new RegExp(`(?<![A-Za-z0-9_#$\x60])${escapeRegExp(name)}(?![A-Za-z0-9_#$\x60])`, 'gu');
1135
+ result = result.replace(generatedReference, meaning);
1136
+ }
1098
1137
  }
1099
1138
  return sanitizeVisibleSqlSemanticText(result)
1100
1139
  .replace(/\bLIMIT\s+([^,;。]+?)限制/g, '按$1限制')
@@ -1135,11 +1174,6 @@ function genericSqlParameterMeaning(name) {
1135
1174
  return normalized;
1136
1175
  }
1137
1176
 
1138
- function isGeneratedSqlParameterName(name) {
1139
- return /^(?:selector|variable|text|number|partdate)\d*$/i.test(text(name))
1140
- || /^date\d+$/i.test(text(name));
1141
- }
1142
-
1143
1177
  function escapeRegExp(value) {
1144
1178
  return text(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1145
1179
  }
@@ -1161,7 +1195,7 @@ function renderPlainSemanticItem(value) {
1161
1195
  if (item.default_value != null && text(item.default_value)) extras.push(`default=${text(item.default_value)}`);
1162
1196
  const expressionKind = text(item.expression_kind);
1163
1197
  if (expressionKind) extras.push(expressionKind);
1164
- if (isInputParameterSemanticItem(item) && meaning && name && !isGeneratedSqlParameterName(name)) {
1198
+ if (isInputParameterSemanticItem(item) && meaning && name) {
1165
1199
  return [`${meaning}(\`${name}\`)`, extras.join(',')].filter(Boolean).join(':');
1166
1200
  }
1167
1201
  if (isInputParameterSemanticItem(item) && meaning) {
@@ -1190,6 +1224,12 @@ function reportDimensionSummaryItems(report, summary = null, calculationContext
1190
1224
  const context = calculationContext || calculationContextForKb(report);
1191
1225
  const facts = object(context.sql_semantic_facts);
1192
1226
  const refs = metadataRefs || metadataRefsForReport(report);
1227
+ if (text(report.definition_kind) !== 'SQL') {
1228
+ return reportContract(report, context).groups.map(group => {
1229
+ const row = object(group);
1230
+ return namedCodeFact(row.columnDesc || row.columnName || text(group), row.columnName || text(group));
1231
+ });
1232
+ }
1193
1233
  const candidates = [
1194
1234
  ...array(summary?.output_fields)
1195
1235
  .filter((item) => text(object(item).semantic_role) === 'dimension')
@@ -1207,14 +1247,18 @@ function reportDimensionSummaryItems(report, summary = null, calculationContext
1207
1247
  function reportParameterSummaryItems(calculationContext) {
1208
1248
  const context = object(calculationContext);
1209
1249
  return [
1250
+ ...(Object.keys(object(context.time_scope)).length ? [`Saved time settings: ${JSON.stringify(context.time_scope)}`] : []),
1210
1251
  ...array(context.sql_params).map((item) => renderPlainSemanticItem({ name: object(item).name || object(item).paramName, meaning: object(item).displayName || object(item).placeholder })),
1211
1252
  ].filter(Boolean).slice(0, 12);
1212
1253
  }
1213
1254
 
1214
1255
  function reportFilterSummaryItems(report, calculationContext) {
1215
1256
  const items = [
1216
- ...array(report.metric_occurrences).flatMap((item) => filterSummaryLines(item.filters ?? item.custom_filters)),
1217
- ...array(object(calculationContext).filters).flatMap((item) => filterSummaryLines(item)),
1257
+ ...array(report.metric_occurrences).flatMap((item) => [
1258
+ ...filterSummaryLines(item.filters, item.relation),
1259
+ ...array(item.custom_filters).flatMap((filter) => filterSummaryLines(filter).map(line => `Operand ${filter.index}: ${line}`)),
1260
+ ]),
1261
+ ...filterSummaryLines(object(calculationContext).filters, object(calculationContext).relation),
1218
1262
  ...array(object(calculationContext).event_view?.filts).flatMap((item) => filterSummaryLines(item)),
1219
1263
  ...array(object(calculationContext).visual_view?.filters).flatMap((item) => filterSummaryLines(item)),
1220
1264
  ...array(object(calculationContext).key_predicate_fragments).map(compactSqlFragment),
@@ -1224,7 +1268,17 @@ function reportFilterSummaryItems(report, calculationContext) {
1224
1268
 
1225
1269
  function completeSqlAgentSummary(report, planSummary, facts) {
1226
1270
  const base = object(planSummary);
1227
- if (Object.keys(base).length) return { summary_source: 'semantic_plan', ...base };
1271
+ if (Object.keys(base).length) {
1272
+ const names = new Set(array(object(facts.final_select).fields).map(field => {
1273
+ if (field.name) return field.name;
1274
+ const expression = text(field.expression);
1275
+ return /^(?:[A-Za-z_][\w$#@.]*|"[^"]+")$/.test(expression) ? unquoteIdentifier(expression) : null;
1276
+ }).filter(Boolean));
1277
+ return { summary_source: 'semantic_plan', ...base,
1278
+ output_fields: array(base.output_fields).map(field => typeof field === 'object' && !names.has(field.name)
1279
+ ? { ...field, identity_state: 'unknown' } : field),
1280
+ };
1281
+ }
1228
1282
  return null;
1229
1283
  }
1230
1284
 
@@ -1380,7 +1434,7 @@ function renderSemanticItem(value) {
1380
1434
  .filter(([key]) => !['name', 'field', 'key', 'column', 'title', 'meaning', 'description', 'remark', 'purpose'].includes(key))
1381
1435
  .map(([key, itemValue]) => `${key}=${inlineSemanticValue(itemValue)}`)
1382
1436
  .join(', ');
1383
- if (isInputParameterSemanticItem(item) && meaning && name && !isGeneratedSqlParameterName(name)) {
1437
+ if (isInputParameterSemanticItem(item) && meaning && name) {
1384
1438
  return [`${meaning}(\`${name}\`)`, details].filter(Boolean).join(':') || jsonInline(item);
1385
1439
  }
1386
1440
  if (isInputParameterSemanticItem(item) && meaning) {
@@ -1391,12 +1445,9 @@ function renderSemanticItem(value) {
1391
1445
 
1392
1446
  function isInputParameterSemanticItem(item) {
1393
1447
  const row = object(item);
1394
- const name = text(row.name ?? row.field ?? row.key ?? row.column ?? row.title);
1395
- const meaning = text(row.meaning ?? row.description ?? row.remark ?? row.purpose);
1396
1448
  return row.required != null
1397
1449
  || row.default_value != null
1398
- || Array.isArray(row.options)
1399
- || (Boolean(meaning) && /^(?:selector|variable|date|partdate|text|number)\d*$/i.test(name));
1450
+ || Array.isArray(row.options);
1400
1451
  }
1401
1452
 
1402
1453
  function inlineSemanticValue(value) {
@@ -1434,6 +1485,11 @@ function reportDetailCalculationContext(report) {
1434
1485
  if (Object.keys(eventView).length) result.event_view = eventView;
1435
1486
  const events = jsonObject(rawDefinition.events);
1436
1487
  if (array(events.sqlVoParams).length) result.sql_params = events.sqlVoParams;
1488
+ if (rawDefinition.events != null) {
1489
+ try { result.source_events = typeof rawDefinition.events === 'string' ? JSON.parse(rawDefinition.events) : rawDefinition.events; }
1490
+ catch { result.source_events = null; }
1491
+ }
1492
+ if (eventView.sqlViewParams != null) result.saved_parameters = eventView.sqlViewParams;
1437
1493
  return result;
1438
1494
  }
1439
1495
 
@@ -1857,6 +1913,9 @@ function metadataDefinitionLines(item) {
1857
1913
  if (text(definition.entity_mapping_scope)) lines.push(`实体映射范围: ${text(definition.entity_mapping_scope)}`);
1858
1914
  const sourceVersion = text(object(item.raw).sourceVersion ?? object(item.raw).source_version);
1859
1915
  if (sourceVersion) lines.push(`来源版本: ${sourceVersion}`);
1916
+ for (const key of ['rule', 'rules', 'expression', 'dependent_events', 'dependent_properties']) {
1917
+ if (definition[key] != null) lines.push(`Rule evidence (${key}): ${jsonInline(definition[key])}`);
1918
+ }
1860
1919
  return lines;
1861
1920
  }
1862
1921
 
@@ -2020,11 +2079,17 @@ function eventBasisLabel(value) {
2020
2079
 
2021
2080
  function detectSemanticConflicts() {
2022
2081
  const reportConflicts = new Map();
2082
+ const executionVariants = new Map();
2023
2083
  const metadataConflicts = new Map();
2024
2084
  for (const [key, values] of groupBySemanticKey(reports, reportSemanticKey)) {
2025
2085
  if (values.length < 2) continue;
2026
- const signatures = new Set(values.map(reportSemanticSignature));
2027
- if (signatures.size <= 1) continue;
2086
+ const signatures = new Set(values.map(report => reportSignature(report, report.calculation_context, false)));
2087
+ if (signatures.size <= 1) {
2088
+ if (new Set(values.map(reportSemanticSignature)).size > 1) {
2089
+ for (const report of values) executionVariants.set(text(report.resource_key), true);
2090
+ }
2091
+ continue;
2092
+ }
2028
2093
  const summary = semanticConflictSummary('报表', key, values, (row) => reportSemanticSignatureLabel(row));
2029
2094
  for (const report of values) appendConflict(reportConflicts, text(report.resource_key), summary);
2030
2095
  }
@@ -2036,7 +2101,7 @@ function detectSemanticConflicts() {
2036
2101
  const summary = semanticConflictSummary('元数据', key, values, (row) => metadataSemanticSignatureLabel(row));
2037
2102
  for (const item of values) appendConflict(metadataConflicts, text(item.evidence_id), summary);
2038
2103
  }
2039
- return { reportConflicts, metadataConflicts };
2104
+ return { reportConflicts, metadataConflicts, executionVariants };
2040
2105
  }
2041
2106
 
2042
2107
  function groupBySemanticKey(values, keyFn) {
@@ -2105,25 +2170,7 @@ function normalizeSemanticIntent(value) {
2105
2170
  }
2106
2171
 
2107
2172
  function reportSemanticSignature(report) {
2108
- if (text(report.definition_kind) === 'SQL') {
2109
- return JSON.stringify({ kind: 'SQL', sql: normalizeSql(text(object(report.calculation_context).sql)) });
2110
- }
2111
- return JSON.stringify({
2112
- kind: text(report.definition_kind) || text(report.report_model),
2113
- metrics: array(report.metric_occurrences).map(metricSemanticSignature).sort(),
2114
- filters: filterSummaryLines(object(report.calculation_context).filters).sort(),
2115
- groups: array(object(report.calculation_context).group_by).map(groupLabel).sort(),
2116
- });
2117
- }
2118
-
2119
- function metricSemanticSignature(item) {
2120
- return JSON.stringify({
2121
- event_name: text(item.event_name),
2122
- aggregation_code: text(item.aggregation_code),
2123
- measure_key: text(item.measure_key),
2124
- formula_expression: text(item.formula_expression),
2125
- filters: filterSummaryLines(item.filters ?? item.custom_filters).sort(),
2126
- });
2173
+ return reportSignature(report);
2127
2174
  }
2128
2175
 
2129
2176
  function reportSemanticSignatureLabel(report) {
@@ -2146,6 +2193,7 @@ function metadataSemanticSignature(item) {
2146
2193
  select_type: text(definition.select_type),
2147
2194
  source_type: text(definition.source_type),
2148
2195
  tag_type: text(definition.tag_type || definition.tag_sub_type),
2196
+ rules: semanticValue(Object.fromEntries(['rule', 'rules', 'expression', 'dependent_events', 'dependent_properties'].filter(key => definition[key] != null).map(key => [key, definition[key]]))),
2149
2197
  });
2150
2198
  }
2151
2199
 
@@ -2173,7 +2221,10 @@ function collectFilterValues(value) {
2173
2221
  function renderOccurrence(item, index) {
2174
2222
  const expression = formulaExpression(item);
2175
2223
  const formulaSummary = formulaDefinitionSummary(item);
2176
- const filterSummary = filterSummaryLines(item.filters || item.custom_filters);
2224
+ const filterSummary = [
2225
+ ...filterSummaryLines(item.filters, item.relation),
2226
+ ...array(item.custom_filters).flatMap(filter => filterSummaryLines(filter).map(line => `Operand ${filter.index}: ${line}`)),
2227
+ ];
2177
2228
  const event = metricEventLabel(item) || 'unknown';
2178
2229
  return `### ${index + 1}. ${heading(occurrenceLabel(item) || `Metric ${index + 1}`)}
2179
2230
 
@@ -2219,15 +2270,17 @@ function formulaDefinition(item) {
2219
2270
  }
2220
2271
 
2221
2272
  function formulaDependencies(item) {
2222
- return array(formulaDefinition(item).formulationDeps).map((dependency) => {
2273
+ return array(formulaDefinition(item).formulationDeps).map((dependency, index) => {
2223
2274
  const event = object(object(dependency).event);
2224
2275
  const quota = object(object(dependency).quota);
2225
2276
  const eventName = text(event.eventDesc || event.eventName) || '未知事件';
2226
2277
  const aggregation = text(quota.quotaDesc || quota.quotaName) || '资产包未暴露聚合方式';
2227
- const filters = array(object(event.filter).filts)
2228
- .map((filter) => text(object(filter).columnDesc || object(filter).quotDesc || object(filter).prop))
2229
- .filter(Boolean);
2230
- return `“${eventName}”的“${aggregation}”${filters.length ? `(过滤:${filters.join('、')})` : ''}`;
2278
+ const filters = [
2279
+ ...filterSummaryLines(event.filter),
2280
+ ...array(item.custom_filters).filter(filter => Number(filter.index) === index).flatMap(filter => filterSummaryLines(filter)),
2281
+ ];
2282
+ const time = object(dependency).date ?? array(item.quota_time_ranges)[index];
2283
+ return `“${eventName}”的“${aggregation}”${filters.length ? `(过滤:${filters.join('、')})` : ''}${time != null ? ` [operand ${index}, time=${JSON.stringify(time)}]` : ''}`;
2231
2284
  });
2232
2285
  }
2233
2286
 
@@ -2249,6 +2302,10 @@ function formulaDefinitionSummary(item) {
2249
2302
  }
2250
2303
 
2251
2304
  function aggregationLabel(item) {
2305
+ const entity = array(item.entity_grain).map(row => object(row.taIdMeasure ?? row.ta_id_measure ?? row)).find(row => row.custom === true || text(row.entityType ?? row.entity_type).toUpperCase() === 'CUSTOM');
2306
+ if (text(item.aggregation_code) === 'A101' && entity) {
2307
+ return `Distinct entities (${text(entity.columnName ?? entity.column_name) || 'verify entity key'})`;
2308
+ }
2252
2309
  const name = text(item.aggregation_name);
2253
2310
  const code = text(item.aggregation_code);
2254
2311
  const codeLabel = aggregationCodeLabel(code);
@@ -2312,31 +2369,19 @@ function isAggregationCode(value) {
2312
2369
  return /^A\d{3}(?:_\d+)?$/.test(text(value));
2313
2370
  }
2314
2371
 
2315
- function filterSummaryLines(value) {
2316
- const conditions = collectFilterConditions(value);
2317
- return [...new Set(conditions.map(filterConditionSentence).filter(Boolean))];
2318
- }
2319
-
2320
- function collectFilterConditions(value) {
2321
- if (Array.isArray(value)) return value.flatMap(collectFilterConditions);
2322
- if (!value || typeof value !== 'object') return [];
2323
- const source = object(value);
2324
- const nested = Object.entries(source)
2325
- .filter(([key]) => !['ftv', 'columnDesc', 'columnName', 'quotDesc', 'prop', 'calcuSymbol', 'calcu_symbol', 'relation', 'junctionKind', 'junction_kind'].includes(key))
2326
- .flatMap(([, item]) => collectFilterConditions(item));
2327
- const hasConditionIdentity = ['columnDesc', 'columnName', 'quotDesc', 'prop', 'propertyName', 'tagName'].some((key) => text(source[key]))
2328
- || array(source.ftv).some((item) => text(item));
2329
- return hasConditionIdentity ? [source, ...nested] : nested;
2372
+ function filterSummaryLines(value, relation) {
2373
+ if (value == null || (Array.isArray(value) && !value.length) || (!Array.isArray(value) && !Object.keys(object(value)).length)) return [];
2374
+ return [renderConditionTree(conditionTree(value, relation), filterConditionSentence)];
2330
2375
  }
2331
2376
 
2332
2377
  function filterConditionSentence(condition) {
2333
2378
  const field = text(condition.columnDesc || condition.quotDesc || condition.columnName || condition.prop || condition.propertyName || condition.tagName) || '未命名字段';
2334
- const values = array(condition.ftv).map(text).filter(Boolean);
2379
+ const values = array(condition.ftv).map(value => typeof value === 'string' ? `“${value}”` : JSON.stringify(value));
2335
2380
  const code = text(condition.calcuSymbol ?? condition.calcu_symbol);
2336
2381
  const operator = filterOperatorLabel(code);
2337
2382
  const relation = text(condition.relation || condition.junctionKind || condition.junction_kind);
2338
2383
  const needsValue = filterOperatorNeedsValue(code);
2339
- const valueText = values.length ? values.map((value) => `“${value}”`).join('、') : needsValue ? '未给出固定值' : '';
2384
+ const valueText = values.length ? values.join('、') : needsValue ? '未给出固定值' : '';
2340
2385
  const valuePart = valueText ? ` ${valueText}` : '';
2341
2386
  const relationText = relation ? `(关系:${relation})` : '';
2342
2387
  return `${field} ${operator}${valuePart}${relationText}`;
@@ -2381,6 +2426,7 @@ function filterOperatorNeedsValue(value) {
2381
2426
  }
2382
2427
 
2383
2428
  function occurrenceLabel(item) { return readableMetricText(item.display_name || item.event_title || item.event_name); }
2429
+ function groupLabel(group) { return text(object(group).columnDesc || object(group).columnName || group); }
2384
2430
 
2385
2431
  function metricEventLabel(item) {
2386
2432
  const title = readableMetricText(item.event_title);
@@ -2411,7 +2457,6 @@ function readableMetricText(value) {
2411
2457
  .replace(/^、|、$/g, '')
2412
2458
  .trim();
2413
2459
  }
2414
- function groupLabel(group) { return text(object(group).columnDesc || object(group).columnName || group); }
2415
2460
 
2416
2461
  function renderSemanticConflictList() {
2417
2462
  const reportLines = [...reportConflicts.entries()]
@@ -7,9 +7,9 @@ description: "Manage AE Agent and Agent team automations with ae-cli: create and
7
7
 
8
8
  See [command risks](references/command_index.md). Read [the command contract](references/cli.md) before writing or executing an automation. Use `ae-cli automation --help` and leaf help to discover flags.
9
9
 
10
- 1. Resolve the host and space. A local user signs in with `ae-cli auth login`; a sandbox uses its injected credentials. Pass `--agent-space-id` when the user identifies a space. Without a space, the server uses a bound sandbox space or the personal default. Show the resolved space when it matters.
10
+ 1. Resolve the host and space. A local user signs in with `ae-cli auth login`; a sandbox uses the current conversation user's injected CLI token, never a shared sandbox's user binding. Pass `--agent-space-id` when the user identifies a space. Without an explicit space, the CLI inherits the current AgentSpace from its runtime context, working directory or verified current conversation; only a local invocation without space context uses the personal default. Show the resolved space when it matters.
11
11
  2. Discover the automation with `automation list` and inspect `automation get`. Use the returned IDs; names are not unique.
12
- 3. For creation, select an Agent or team through the corresponding discovery commands. Match configuration to the target type. Preview writes with `--dry-run` when useful; the CLI shows a redacted request and asks the server to validate authentication, space, permissions, target state, fields, and idempotency without applying the operation.
12
+ 3. For creation, select an Agent or team through the corresponding discovery commands. Match configuration to the target type. Use `--weekdays` or `--days-of-month` JSON arrays for multiple days with one time; skip dates absent in a month. For the actual last day of every month use `--days-of-month '["L"]'`, or combine it with dates, e.g. `'[1,15,"L"]'`; overlapping selections execute only once. Both Agent and Agent Team accept result notifications. Confirm the complete configuration before creation; save only the recurring task instructions as `--message`, never the automation-creation guide. Preview writes with `--dry-run` when useful; the CLI shows a redacted request and asks the server to validate authentication, space, permissions, target state, fields, and idempotency without applying the operation.
13
13
  4. Execute the requested operation. Deleting an automation or stopping an execution requires confirmation; use `--yes` only when the user already authorized that action. In a non-interactive shell, these operations fail immediately unless global `--yes` is present.
14
14
  5. Read the returned run ID, then `automation runs result`. Report acceptance separately from completion. Use `--wait` only when the user needs the final outcome now.
15
15
 
@@ -17,7 +17,7 @@ Exit condition: equivalent gateway capabilities preserve permissions, idempotenc
17
17
  - `ae-cli automation enable|disable --id ID`
18
18
  - `ae-cli automation delete --id ID --yes`
19
19
 
20
- Scheduled creation needs `--schedule-kind` or `--cron-expression`. Frequencies: hourly with `--minute 0..59`; daily with `--time HH:mm`; weekly also `--weekday 0..6`; monthly also `--day-of-month 1..28`. Times use the server timezone. Manual creation does not require a schedule. `--trigger-immediately` executes after creation; `--wait` then waits for that execution. Creation and immediate execution have separate results: do not create another automation if only its execution failed.
20
+ Scheduled creation needs `--schedule-kind` or `--cron-expression`. Frequencies: hourly with `--minute 0..59`; daily with `--time HH:mm`; weekly also `--weekdays '[1,3,5]'` (0=Sunday through 6=Saturday); monthly also `--days-of-month '[1,15,31]'` (integers 1–31 and `"L"` for the actual last day of the month). Existing single-day flags `--weekday 0..6` and `--day-of-month 1..31` remain supported; do not combine a single-day flag with its plural array. Arrays must be non-empty; duplicates are removed and dates sorted. Dates absent in the current month are skipped, never shifted into the next month. `"L"` follows the actual month end in the server timezone (including leap years). It can be selected alone or with numeric dates; overlapping selections at the same time run only once. Any minute 00–59 is valid in `--time`. Times use the server timezone. Manual creation does not require a schedule. `--trigger-immediately` executes after creation; `--wait` then waits for that execution. Creation and immediate execution have separate results: do not create another automation if only its execution failed.
21
21
 
22
22
  On update, omitted fields retain their values. `--model null`, `--scope null`, destination IDs set to `null`, and `--save-to-knowledge-base-id null` clear nullable fields. `--channels '[]'` clears notifications; `--clear-message` clears instructions; `--reuse-conversation false` disables reuse. An automation's target is immutable on update. Team fields are rejected for Agent targets and vice versa.
23
23
 
@@ -51,3 +51,20 @@ Without `--wait`, success means accepted. With `--wait`, output includes complet
51
51
  ## Compatibility
52
52
 
53
53
  The retired Agent-domain automation commands are unavailable. Use the `automation` domain for the complete lifecycle, including teams.
54
+
55
+ ## Shared result notifications and identity
56
+
57
+ Agent and Agent Team both accept `--channels '["feishu","slack"]'`, `--feishu-chat-id`, `--lark-chat-id` and `--slack-channel-id`. Supported channels: `feishu`, `lark`, `slack`, `discord`, `dingtalk`, `wecom`, `mattermost`, `google_chat`, `zoom_chat`, `whatsapp`. Availability and destination permissions are validated by the server. Omitted update fields keep the existing values; `--channels '[]'` disables external delivery, and a receiver value of `null` clears that override. Results remain available in the application.
58
+
59
+ Automation requests use the current user's `cli-token`, including in a shared sandbox. They do not infer an owner from `SandboxUser` or fall back to a different member. `--agent-space-id` takes precedence over `TE_AGENT_CURRENT_AGENT_SPACE_ID` or the current AgentSpace working directory. When the shell has changed directories, `TE_AGENT_CONVERSATION_ID` resolves the space through the existing authenticated workspace target API; a resolution failure never falls back to the personal space. Membership and write permissions are enforced by the server.
60
+
61
+ For conversational creation, keep the supplied prompt editable until the user sends it. Resolve missing or ambiguous targets, summarize the configuration and obtain confirmation before creating. Agent runs inherit its model unless explicitly overridden; Agent Team retains its own configuration. Omit `--trigger-immediately` unless the user requests an immediate run.
62
+
63
+ ## Multi-date examples
64
+
65
+ ```bash
66
+ ae-cli automation create --target-type agent --agent-id ID --name "Weekly report" --message "Summarize the latest metrics" --schedule-kind weekly --weekdays '[1,3,5]' --time 09:17
67
+ ae-cli automation update --id ID --target-type team --schedule-kind monthly --days-of-month '[1,15,"L"]' --time 18:59
68
+ ```
69
+
70
+ Each schedule has one execution time. Select `[1,2,3,4,5]` for Monday–Friday or `[0,6]` for weekends; these are weekdays, not a regional holiday calendar. Five-field cron accepts equivalent comma-separated weekday or month-day lists, such as `17 9 * * 1,3,5` and `59 18 1,15,L * *`. `L` is supported only in the month-day field. Ranges, steps and multiple execution times are not supported. `get` returns the saved `cronExpression` and notification configuration. On a scheduled active task, `nextRunAt`, `scheduleTimeZone` and `serverUtcOffset` describe the server's next execution.