@thinkingai/ae-cli 6.0.48 → 6.1.0-project-role-upsert.1

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 (82) hide show
  1. package/README.md +4 -1
  2. package/README.zh.md +4 -1
  3. package/dist/{capability-J4FU4JZW.js → capability-AXFQW5WM.js} +13 -10
  4. package/dist/{chunk-6KBBAPPP.js → chunk-JDPEVVHR.js} +4 -3
  5. package/dist/index.js +23 -13
  6. package/dist/{metadata-3J5ZAVMF.js → metadata-SOALPEA7.js} +3 -3
  7. package/dist/{personal-semantic-preference-32AAJ37M.js → personal-semantic-preference-LOF33N5X.js} +2 -2
  8. package/dist/project-semantic-RV6NA5LN.js +1113 -0
  9. package/dist/{te-analysis-WHYXHRJD.js → te-analysis-TV2YZ4IC.js} +23 -7
  10. package/dist/{te-engage-DHKNJ5BO.js → te-engage-EUVMTUDJ.js} +63 -5
  11. package/dist/te-experiment-ESEYE2SK.js +1059 -0
  12. package/package.json +2 -1
  13. package/skills/ae-analysis/SKILL.md +17 -1
  14. package/skills/ae-analysis/references/command_index.md +1 -1
  15. package/skills/ae-analysis/references/personal_semantic_preference_add.md +1 -1
  16. package/skills/ae-analysis/references/personal_semantic_preference_get.md +3 -1
  17. package/skills/ae-analysis/references/personal_semantic_preference_list.md +3 -1
  18. package/skills/ae-analysis/references/personal_semantic_preference_update.md +1 -1
  19. package/skills/ae-analysis/references/project_role_upsert.md +32 -2
  20. package/skills/ae-engage/SKILL.md +2 -0
  21. package/skills/ae-engage/references/save-flow.md +37 -1
  22. package/skills/ae-experiment/SKILL.md +117 -0
  23. package/skills/ae-experiment/references/batch_delete_experiment.md +11 -0
  24. package/skills/ae-experiment/references/batch_delete_feature.md +11 -0
  25. package/skills/ae-experiment/references/batch_delete_traffic_layer.md +11 -0
  26. package/skills/ae-experiment/references/cancel_experiment_query_by_request_id.md +11 -0
  27. package/skills/ae-experiment/references/check_experiment_ready.md +17 -0
  28. package/skills/ae-experiment/references/check_experiment_traffic_layer_conflict.md +37 -0
  29. package/skills/ae-experiment/references/delete_metric.md +13 -0
  30. package/skills/ae-experiment/references/manage_experiment.md +17 -0
  31. package/skills/ae-experiment/references/manage_feature_status.md +11 -0
  32. package/skills/ae-experiment/references/manage_feature_whitelist.md +66 -0
  33. package/skills/ae-experiment/references/manage_guardrail_metrics.md +26 -0
  34. package/skills/ae-experiment/references/query_bucket_list.md +12 -0
  35. package/skills/ae-experiment/references/query_experiment_detail.md +19 -0
  36. package/skills/ae-experiment/references/query_experiment_list.md +22 -0
  37. package/skills/ae-experiment/references/query_experiment_list_archived.md +18 -0
  38. package/skills/ae-experiment/references/query_experiment_metric_trend.md +12 -0
  39. package/skills/ae-experiment/references/query_experiment_operation_log.md +17 -0
  40. package/skills/ae-experiment/references/query_experiment_report_summary.md +12 -0
  41. package/skills/ae-experiment/references/query_experiment_sample_size_report.md +13 -0
  42. package/skills/ae-experiment/references/query_feature_detail.md +14 -0
  43. package/skills/ae-experiment/references/query_feature_list.md +12 -0
  44. package/skills/ae-experiment/references/query_feature_operation_log.md +19 -0
  45. package/skills/ae-experiment/references/query_feature_version_list.md +20 -0
  46. package/skills/ae-experiment/references/query_metric_detail.md +18 -0
  47. package/skills/ae-experiment/references/query_metric_list.md +15 -0
  48. package/skills/ae-experiment/references/query_traffic_layer_detail.md +13 -0
  49. package/skills/ae-experiment/references/query_traffic_layer_list.md +12 -0
  50. package/skills/ae-experiment/references/save_build_guide.md +39 -0
  51. package/skills/ae-experiment/references/save_experiment.md +210 -0
  52. package/skills/ae-experiment/references/save_feature.md +13 -0
  53. package/skills/ae-experiment/references/save_metric.md +91 -0
  54. package/skills/ae-experiment/references/save_submit_experiment.md +16 -0
  55. package/skills/ae-experiment/references/save_traffic_layer.md +13 -0
  56. package/skills/ae-experiment/references/save_validate.md +33 -0
  57. package/skills/ae-experiment/references/update_experiment_group.md +20 -0
  58. package/skills/ae-experiment-design/SKILL.md +149 -0
  59. package/skills/ae-experiment-design/agents/openai.yaml +4 -0
  60. package/skills/ae-experiment-design/references/client-experiment-sdk.md +147 -0
  61. package/skills/ae-experiment-design/references/experiment-creation.md +108 -0
  62. package/skills/ae-experiment-design/references/experiment-sdk-contract.md +100 -0
  63. package/skills/ae-experiment-design/references/exposure-contract.md +91 -0
  64. package/skills/ae-experiment-design/references/hybrid-experiment-sdk.md +74 -0
  65. package/skills/ae-experiment-design/references/metric-readiness.md +143 -0
  66. package/skills/ae-experiment-design/references/platform-operations.md +105 -0
  67. package/skills/ae-experiment-design/references/sdk-index.md +76 -0
  68. package/skills/ae-experiment-design/references/sdk-integration.md +114 -0
  69. package/skills/ae-experiment-design/references/sdk-troubleshooting.md +139 -0
  70. package/skills/ae-experiment-design/references/server-experiment-sdk.md +78 -0
  71. package/skills/ae-experiment-design/scripts/calculate_experiment_plan.py +450 -0
  72. package/skills/ae-experiment-insight/SKILL.md +149 -0
  73. package/skills/ae-experiment-insight/agents/openai.yaml +4 -0
  74. package/skills/ae-experiment-insight/references/decision-framework.md +69 -0
  75. package/skills/ae-experiment-insight/references/diagnostic-playbook.md +225 -0
  76. package/skills/ae-experiment-insight/references/platform-operations.md +82 -0
  77. package/skills/ae-experiment-insight/scripts/analyze_experiment.py +478 -0
  78. package/skills/ae-project-semantic/SKILL.md +193 -0
  79. package/skills/ae-project-semantic/references/query-routing-v5.md +165 -0
  80. package/skills/ae-project-semantic/references/recommendation-quality.md +68 -0
  81. package/dist/{chunk-HPLNYQPM.js → chunk-RSG4ONOI.js} +3 -3
  82. package/dist/{sync-2GTAKZYX.js → sync-YV3E66IF.js} +3 -3
@@ -0,0 +1,78 @@
1
+ # Server Experiment Architecture
2
+
3
+ Read this reference when assignment, Feature evaluation, or behavior execution belongs on a backend service.
4
+
5
+ ## Evidence boundary
6
+
7
+ The verified client documentation does not establish an exact AE server experiment SDK, package, endpoint, or method. Do not invent one.
8
+
9
+ Before exact implementation:
10
+
11
+ 1. Resolve the real project, experiment, Feature, and assignment unit with `ae-cli`.
12
+ 2. Search and inspect the Capability Gateway for server experiment evaluation or SDK generation.
13
+ 3. Read verified server documentation if returned by the platform or provided by the user.
14
+ 4. Use architecture-level pseudocode until the API contract is verified.
15
+
16
+ ## Recommended server contract
17
+
18
+ ```text
19
+ evaluateExperiment(
20
+ project,
21
+ featureKey,
22
+ stableAssignmentId,
23
+ context
24
+ ) -> {
25
+ value,
26
+ valueType,
27
+ assignmentOrGroupId,
28
+ configVersion,
29
+ source,
30
+ evaluatedAt
31
+ }
32
+ ```
33
+
34
+ The field names above describe an application contract, not a verified AE API schema.
35
+
36
+ ## Server responsibilities
37
+
38
+ - derive a stable assignment ID before evaluation;
39
+ - evaluate once per request, session, or product-defined decision boundary;
40
+ - apply a typed control default on timeout or unavailable configuration;
41
+ - preserve assignment consistency across replicas and regions;
42
+ - prevent cache keys from mixing projects, environments, Features, or identities;
43
+ - expose enough internal metadata to diagnose assignment without leaking it to end users;
44
+ - emit or authorize exposure only when treatment becomes effective;
45
+ - report outcomes with a joinable identity.
46
+
47
+ ## Cache policy
48
+
49
+ Define separately:
50
+
51
+ - configuration cache;
52
+ - per-identity assignment cache;
53
+ - response or page cache.
54
+
55
+ Do not cache a personalized treatment under a shared response key. Include environment, project, Feature key, and assignment identity in any evaluation cache key unless the verified SDK guarantees safe internal caching.
56
+
57
+ ## Failure policy
58
+
59
+ - timeout or transport failure: use typed control default;
60
+ - invalid value type: reject the value and use control;
61
+ - unknown Feature: fail closed to control and alert;
62
+ - stale configuration: use only within the approved stale window;
63
+ - identity missing: either use a documented anonymous assignment or control; never generate a new unstable ID per request.
64
+
65
+ ## Exposure ownership
66
+
67
+ If the server fully executes the behavior, server exposure may be appropriate. If the server only sends a variant and the client decides whether it is rendered, client exposure is usually the truthful point.
68
+
69
+ Choose one owner and read `exposure-contract.md`.
70
+
71
+ ## Verification checklist
72
+
73
+ - Same identity returns a stable value across replicas.
74
+ - Control fallback is safe and typed.
75
+ - Cache isolation prevents cross-user treatment leakage.
76
+ - Exposure reflects effective behavior, not evaluation alone.
77
+ - Client and server do not both report the same exposure.
78
+ - Outcome identity joins to assignment and exposure.
@@ -0,0 +1,450 @@
1
+ #!/usr/bin/env python3
2
+ """Calculate deterministic A/B experiment sample targets and duration."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import json
7
+ import math
8
+ import sys
9
+ from typing import Any
10
+
11
+
12
+ def normal_ppf(probability: float) -> float:
13
+ """Return the inverse standard-normal CDF using Acklam's approximation."""
14
+ if not 0.0 < probability < 1.0:
15
+ raise ValueError("probability must be between 0 and 1")
16
+
17
+ a = (
18
+ -3.969683028665376e01,
19
+ 2.209460984245205e02,
20
+ -2.759285104469687e02,
21
+ 1.383577518672690e02,
22
+ -3.066479806614716e01,
23
+ 2.506628277459239e00,
24
+ )
25
+ b = (
26
+ -5.447609879822406e01,
27
+ 1.615858368580409e02,
28
+ -1.556989798598866e02,
29
+ 6.680131188771972e01,
30
+ -1.328068155288572e01,
31
+ )
32
+ c = (
33
+ -7.784894002430293e-03,
34
+ -3.223964580411365e-01,
35
+ -2.400758277161838e00,
36
+ -2.549732539343734e00,
37
+ 4.374664141464968e00,
38
+ 2.938163982698783e00,
39
+ )
40
+ d = (
41
+ 7.784695709041462e-03,
42
+ 3.224671290700398e-01,
43
+ 2.445134137142996e00,
44
+ 3.754408661907416e00,
45
+ )
46
+
47
+ low = 0.02425
48
+ high = 1.0 - low
49
+ if probability < low:
50
+ q = math.sqrt(-2.0 * math.log(probability))
51
+ return (
52
+ (((((c[0] * q + c[1]) * q + c[2]) * q + c[3]) * q + c[4]) * q + c[5])
53
+ / ((((d[0] * q + d[1]) * q + d[2]) * q + d[3]) * q + 1.0)
54
+ )
55
+ if probability <= high:
56
+ q = probability - 0.5
57
+ r = q * q
58
+ return (
59
+ (((((a[0] * r + a[1]) * r + a[2]) * r + a[3]) * r + a[4]) * r + a[5])
60
+ * q
61
+ / (((((b[0] * r + b[1]) * r + b[2]) * r + b[3]) * r + b[4]) * r + 1.0)
62
+ )
63
+ q = math.sqrt(-2.0 * math.log(1.0 - probability))
64
+ return -(
65
+ (((((c[0] * q + c[1]) * q + c[2]) * q + c[3]) * q + c[4]) * q + c[5])
66
+ / ((((d[0] * q + d[1]) * q + d[2]) * q + d[3]) * q + 1.0)
67
+ )
68
+
69
+
70
+ def require_number(params: dict[str, Any], key: str) -> float:
71
+ value = params.get(key)
72
+ if isinstance(value, bool) or not isinstance(value, (int, float)):
73
+ raise ValueError(f"{key} must be a number")
74
+ if not math.isfinite(float(value)):
75
+ raise ValueError(f"{key} must be finite")
76
+ return float(value)
77
+
78
+
79
+ def optional_integer(params: dict[str, Any], key: str, default: int) -> int:
80
+ value = params.get(key, default)
81
+ if isinstance(value, bool) or not isinstance(value, int):
82
+ raise ValueError(f"{key} must be an integer")
83
+ return value
84
+
85
+
86
+ def optional_boolean(params: dict[str, Any], key: str, default: bool) -> bool:
87
+ value = params.get(key, default)
88
+ if not isinstance(value, bool):
89
+ raise ValueError(f"{key} must be a boolean")
90
+ return value
91
+
92
+
93
+ def pair_sample_proportion(
94
+ baseline: float,
95
+ treatment: float,
96
+ ratio: float,
97
+ z_alpha: float,
98
+ z_power: float,
99
+ ) -> tuple[int, int]:
100
+ delta = abs(treatment - baseline)
101
+ if delta == 0.0:
102
+ raise ValueError("MDE must produce a non-zero absolute effect")
103
+ pooled_alternative = (baseline + ratio * treatment) / (1.0 + ratio)
104
+ first = z_alpha * math.sqrt(
105
+ pooled_alternative * (1.0 - pooled_alternative) * (1.0 + 1.0 / ratio)
106
+ )
107
+ second = z_power * math.sqrt(
108
+ baseline * (1.0 - baseline) + treatment * (1.0 - treatment) / ratio
109
+ )
110
+ control = math.ceil(((first + second) / delta) ** 2)
111
+ return control, math.ceil(control * ratio)
112
+
113
+
114
+ def pair_sample_continuous(
115
+ absolute_effect: float,
116
+ ratio: float,
117
+ z_alpha: float,
118
+ z_power: float,
119
+ control_stddev: float,
120
+ treatment_stddev: float,
121
+ ) -> tuple[int, int]:
122
+ if absolute_effect == 0.0:
123
+ raise ValueError("MDE must produce a non-zero absolute effect")
124
+ control = math.ceil(
125
+ (z_alpha + z_power) ** 2
126
+ * (control_stddev**2 + treatment_stddev**2 / ratio)
127
+ / absolute_effect**2
128
+ )
129
+ return control, math.ceil(control * ratio)
130
+
131
+
132
+ def calculate_duration(
133
+ total_required: int,
134
+ daily_eligible_units: float,
135
+ experiment_traffic: float,
136
+ min_runtime_days: int,
137
+ align_to_full_weeks: bool,
138
+ ) -> tuple[float, int, int]:
139
+ """Return effective daily units, raw duration, and policy-adjusted duration."""
140
+ effective_daily_units = daily_eligible_units * experiment_traffic
141
+ nearest_integer = round(effective_daily_units)
142
+ if math.isclose(
143
+ effective_daily_units,
144
+ nearest_integer,
145
+ rel_tol=0.0,
146
+ abs_tol=1e-9,
147
+ ):
148
+ effective_daily_units = float(nearest_integer)
149
+ else:
150
+ effective_daily_units = round(effective_daily_units, 12)
151
+ raw_duration_days = math.ceil(total_required / effective_daily_units)
152
+ duration_days = max(min_runtime_days, raw_duration_days)
153
+ if align_to_full_weeks:
154
+ duration_days = math.ceil(duration_days / 7.0) * 7
155
+ return effective_daily_units, raw_duration_days, duration_days
156
+
157
+
158
+ def traffic_candidates(params: dict[str, Any], max_available_traffic: float) -> list[float]:
159
+ """Return validated absolute experiment-traffic candidates."""
160
+ raw_candidates = params.get(
161
+ "traffic_candidates",
162
+ [0.05, 0.10, 0.20, 0.30, 0.50, 0.70, 1.0],
163
+ )
164
+ if not isinstance(raw_candidates, list) or not raw_candidates:
165
+ raise ValueError("traffic_candidates must be a non-empty list")
166
+
167
+ candidates: list[float] = []
168
+ for value in raw_candidates:
169
+ if isinstance(value, bool) or not isinstance(value, (int, float)):
170
+ raise ValueError("every traffic candidate must be a number")
171
+ candidate = float(value)
172
+ if not math.isfinite(candidate) or not 0.0 < candidate <= 1.0:
173
+ raise ValueError("every traffic candidate must be greater than 0 and at most 1")
174
+ if candidate <= max_available_traffic + 1e-12:
175
+ candidates.append(min(candidate, max_available_traffic))
176
+
177
+ candidates.append(max_available_traffic)
178
+ return sorted(set(candidates))
179
+
180
+
181
+ def calculate(params: dict[str, Any]) -> dict[str, Any]:
182
+ metric_type = params.get("metric_type")
183
+ if metric_type not in {"proportion", "continuous"}:
184
+ raise ValueError("metric_type must be proportion or continuous")
185
+
186
+ baseline = require_number(params, "baseline")
187
+ mde = require_number(params, "mde")
188
+ if mde <= 0.0:
189
+ raise ValueError("mde must be greater than 0")
190
+ mde_type = params.get("mde_type")
191
+ if mde_type not in {"relative", "absolute"}:
192
+ raise ValueError("mde_type must be relative or absolute")
193
+ effect_direction = params.get("effect_direction")
194
+ if effect_direction not in {"increase", "decrease"}:
195
+ raise ValueError("effect_direction must be increase or decrease")
196
+
197
+ confidence = (
198
+ require_number(params, "confidence_level")
199
+ if "confidence_level" in params
200
+ else 0.95
201
+ )
202
+ power_source = "provided" if "power" in params else "policy_default"
203
+ power = require_number(params, "power") if "power" in params else 0.80
204
+ if not math.isclose(confidence, 0.95, rel_tol=0.0, abs_tol=1e-12):
205
+ raise ValueError("current experiment reporting requires confidence_level=0.95")
206
+ if not 0.5 < power < 1.0:
207
+ raise ValueError("power must be between 0.5 and 1")
208
+
209
+ allocations_value = params.get("allocations")
210
+ if not isinstance(allocations_value, list) or len(allocations_value) < 2:
211
+ raise ValueError("allocations must contain control and at least one treatment")
212
+ if any(
213
+ isinstance(value, bool) or not isinstance(value, (int, float))
214
+ for value in allocations_value
215
+ ):
216
+ raise ValueError("every allocation must be a number")
217
+ allocations = [float(value) for value in allocations_value]
218
+ if any(not math.isfinite(value) or value <= 0.0 for value in allocations):
219
+ raise ValueError("every allocation must be a positive finite number")
220
+ if not math.isclose(sum(allocations), 1.0, rel_tol=0.0, abs_tol=1e-9):
221
+ raise ValueError("allocations must sum to 1.0")
222
+
223
+ daily_eligible_units = require_number(params, "daily_eligible_units")
224
+ if daily_eligible_units <= 0.0:
225
+ raise ValueError("daily_eligible_units must be greater than 0")
226
+
227
+ min_runtime_days = optional_integer(params, "min_runtime_days", 7)
228
+ max_runtime_days = optional_integer(params, "max_runtime_days", 60)
229
+ align_to_full_weeks = optional_boolean(params, "align_to_full_weeks", True)
230
+ if min_runtime_days < 1 or max_runtime_days < min_runtime_days:
231
+ raise ValueError("runtime bounds are invalid")
232
+
233
+ number_of_comparisons = len(allocations) - 1
234
+ alpha = 0.05
235
+ multiple_comparison_method = (
236
+ "bonferroni" if number_of_comparisons > 1 else "none"
237
+ )
238
+ alpha_per_comparison = alpha / number_of_comparisons
239
+ z_alpha = normal_ppf(1.0 - alpha_per_comparison / 2.0)
240
+ z_power = normal_ppf(power)
241
+
242
+ absolute_effect = mde if mde_type == "absolute" else abs(baseline) * mde
243
+ signed_effect = absolute_effect if effect_direction == "increase" else -absolute_effect
244
+ treatment_value = baseline + signed_effect
245
+ warnings: list[str] = []
246
+
247
+ if metric_type == "proportion":
248
+ if not 0.0 < baseline < 1.0:
249
+ raise ValueError("proportion baseline must be between 0 and 1")
250
+ if not 0.0 < treatment_value < 1.0:
251
+ raise ValueError("proportion MDE produces a treatment rate outside 0..1")
252
+ else:
253
+ control_stddev = require_number(params, "standard_deviation")
254
+ treatment_stddev = (
255
+ require_number(params, "treatment_standard_deviation")
256
+ if "treatment_standard_deviation" in params
257
+ else control_stddev
258
+ )
259
+ if control_stddev <= 0.0 or treatment_stddev <= 0.0:
260
+ raise ValueError("continuous standard deviations must be greater than 0")
261
+
262
+ control_allocation = allocations[0]
263
+ pair_requirements: list[dict[str, Any]] = []
264
+ total_scale = 0.0
265
+
266
+ for index, treatment_allocation in enumerate(allocations[1:], start=1):
267
+ ratio = treatment_allocation / control_allocation
268
+ if metric_type == "proportion":
269
+ control_required, treatment_required = pair_sample_proportion(
270
+ baseline, treatment_value, ratio, z_alpha, z_power
271
+ )
272
+ else:
273
+ control_required, treatment_required = pair_sample_continuous(
274
+ absolute_effect,
275
+ ratio,
276
+ z_alpha,
277
+ z_power,
278
+ control_stddev,
279
+ treatment_stddev,
280
+ )
281
+ pair_scale = max(
282
+ control_required / control_allocation,
283
+ treatment_required / treatment_allocation,
284
+ )
285
+ total_scale = max(total_scale, pair_scale)
286
+ pair_requirements.append(
287
+ {
288
+ "treatment_index": index,
289
+ "allocation_ratio_to_control": ratio,
290
+ "control_required": control_required,
291
+ "treatment_required": treatment_required,
292
+ }
293
+ )
294
+
295
+ total_required = math.ceil(total_scale)
296
+ group_targets = [math.ceil(total_required * allocation) for allocation in allocations]
297
+
298
+ max_available_traffic: float | None = None
299
+ if "max_available_traffic" in params:
300
+ max_available_traffic = require_number(params, "max_available_traffic")
301
+ if not 0.0 < max_available_traffic <= 1.0:
302
+ raise ValueError("max_available_traffic must be greater than 0 and at most 1")
303
+
304
+ traffic_evaluations: list[dict[str, Any]] = []
305
+ if "experiment_traffic" in params:
306
+ experiment_traffic = require_number(params, "experiment_traffic")
307
+ if not 0.0 < experiment_traffic <= 1.0:
308
+ raise ValueError("experiment_traffic must be greater than 0 and at most 1")
309
+ if (
310
+ max_available_traffic is not None
311
+ and experiment_traffic > max_available_traffic + 1e-12
312
+ ):
313
+ raise ValueError("experiment_traffic exceeds max_available_traffic")
314
+ traffic_selection_mode = "provided"
315
+ effective_daily_units, raw_duration_days, duration_days = calculate_duration(
316
+ total_required,
317
+ daily_eligible_units,
318
+ experiment_traffic,
319
+ min_runtime_days,
320
+ align_to_full_weeks,
321
+ )
322
+ traffic_evaluations.append(
323
+ {
324
+ "experiment_traffic": experiment_traffic,
325
+ "effective_daily_units": effective_daily_units,
326
+ "raw_duration_days": raw_duration_days,
327
+ "recommended_duration_days": duration_days,
328
+ "feasible_within_max_runtime": duration_days <= max_runtime_days,
329
+ }
330
+ )
331
+ else:
332
+ if max_available_traffic is None:
333
+ raise ValueError(
334
+ "provide experiment_traffic or max_available_traffic for traffic recommendation"
335
+ )
336
+ traffic_selection_mode = "recommended"
337
+ selected: dict[str, Any] | None = None
338
+ for candidate in traffic_candidates(params, max_available_traffic):
339
+ effective, raw_days, adjusted_days = calculate_duration(
340
+ total_required,
341
+ daily_eligible_units,
342
+ candidate,
343
+ min_runtime_days,
344
+ align_to_full_weeks,
345
+ )
346
+ evaluation = {
347
+ "experiment_traffic": candidate,
348
+ "effective_daily_units": effective,
349
+ "raw_duration_days": raw_days,
350
+ "recommended_duration_days": adjusted_days,
351
+ "feasible_within_max_runtime": adjusted_days <= max_runtime_days,
352
+ }
353
+ traffic_evaluations.append(evaluation)
354
+ if selected is None and evaluation["feasible_within_max_runtime"]:
355
+ selected = evaluation
356
+ if selected is None:
357
+ selected = traffic_evaluations[-1]
358
+ experiment_traffic = float(selected["experiment_traffic"])
359
+ effective_daily_units = float(selected["effective_daily_units"])
360
+ raw_duration_days = int(selected["raw_duration_days"])
361
+ duration_days = int(selected["recommended_duration_days"])
362
+
363
+ feasible_within_max = duration_days <= max_runtime_days
364
+ if not feasible_within_max:
365
+ warnings.append(
366
+ f"Required duration ({duration_days} days) exceeds max_runtime_days ({max_runtime_days})."
367
+ )
368
+ if raw_duration_days < min_runtime_days:
369
+ warnings.append(
370
+ "The statistical target is reached before the minimum runtime; keep the full minimum period."
371
+ )
372
+ if number_of_comparisons > 1:
373
+ warnings.append(
374
+ "Planning uses Bonferroni correction, while the current native report labels significance at unadjusted p < 0.05 with a 95% confidence interval. Use alpha_per_comparison for the preregistered final decision."
375
+ )
376
+
377
+ return {
378
+ "ok": True,
379
+ "data": {
380
+ "metric_type": metric_type,
381
+ "baseline": baseline,
382
+ "mde": mde,
383
+ "mde_type": mde_type,
384
+ "effect_direction": effect_direction,
385
+ "absolute_effect": absolute_effect,
386
+ "signed_effect": signed_effect,
387
+ "treatment_value": treatment_value,
388
+ "confidence_level": confidence,
389
+ "power": power,
390
+ "power_source": power_source,
391
+ "alpha": alpha,
392
+ "family_alpha": alpha,
393
+ "alpha_per_comparison": alpha_per_comparison,
394
+ "multiple_comparison_method": multiple_comparison_method,
395
+ "number_of_treatment_comparisons": number_of_comparisons,
396
+ "allocations": allocations,
397
+ "group_sample_targets": group_targets,
398
+ "total_sample_target": total_required,
399
+ "pair_requirements": pair_requirements,
400
+ "daily_eligible_units": daily_eligible_units,
401
+ "experiment_traffic": experiment_traffic,
402
+ "selected_experiment_traffic": experiment_traffic,
403
+ "recommended_experiment_traffic": (
404
+ experiment_traffic
405
+ if traffic_selection_mode == "recommended" and feasible_within_max
406
+ else None
407
+ ),
408
+ "traffic_selection_mode": traffic_selection_mode,
409
+ "max_available_traffic": max_available_traffic,
410
+ "traffic_candidates_evaluated": traffic_evaluations,
411
+ "effective_daily_units": effective_daily_units,
412
+ "raw_duration_days": raw_duration_days,
413
+ "recommended_duration_days": duration_days,
414
+ "aligned_to_full_weeks": align_to_full_weeks,
415
+ "feasible_within_max_runtime": feasible_within_max,
416
+ "warnings": warnings,
417
+ },
418
+ }
419
+
420
+
421
+ def main() -> int:
422
+ try:
423
+ if len(sys.argv) != 2:
424
+ raise ValueError("usage: calculate_experiment_plan.py '<json-object>'")
425
+ raw = sys.argv[1]
426
+ params = json.load(sys.stdin) if raw == "-" else json.loads(raw)
427
+ if not isinstance(params, dict):
428
+ raise ValueError("input must be a JSON object")
429
+ print(json.dumps(calculate(params), ensure_ascii=False, indent=2))
430
+ return 0
431
+ except (ValueError, KeyError, json.JSONDecodeError) as error:
432
+ print(
433
+ json.dumps(
434
+ {
435
+ "ok": False,
436
+ "error": {
437
+ "type": "validation",
438
+ "code": "INVALID_INPUT",
439
+ "message": str(error),
440
+ },
441
+ },
442
+ ensure_ascii=False,
443
+ indent=2,
444
+ )
445
+ )
446
+ return 2
447
+
448
+
449
+ if __name__ == "__main__":
450
+ raise SystemExit(main())
@@ -0,0 +1,149 @@
1
+ ---
2
+ name: ae-experiment-insight
3
+ description: "Diagnose and interpret AE/TE A/B experiments from configuration and report evidence through a defensible decision. Use when the user asks what an experiment means, whether it can roll out, why a result is not significant, why group sizes or exposure are wrong, why treatment results conflict, whether the report is trustworthy, or what to do next. Covers SRM, duration sufficiency, novelty effects, metric conflicts, missing or anomalous data, design reasonableness, data reliability, metric interpretation, trend and segment analysis, root-cause hypotheses, and rollout recommendations. All platform discovery and reads must use ae-cli."
4
+ ---
5
+
6
+ # AE Experiment Insight and Diagnosis
7
+
8
+ Validate the evidence before interpreting the effect. Produce a decision only when the experiment is trustworthy enough to support one.
9
+
10
+ ## Platform and reference routing
11
+
12
+ Use `ae-cli` for every AE/TE platform interaction and follow
13
+ [`references/platform-operations.md`](references/platform-operations.md);
14
+ never substitute another platform access path.
15
+
16
+ For diagnosis, read
17
+ [`references/diagnostic-playbook.md`](references/diagnostic-playbook.md).
18
+ Before a rollout decision, read
19
+ [`references/decision-framework.md`](references/decision-framework.md).
20
+
21
+ ## Workflow
22
+
23
+ ### 1. Restore experiment context
24
+
25
+ Resolve the exact project and experiment. Collect:
26
+
27
+ - hypothesis and intended decision;
28
+ - control and treatment definitions;
29
+ - assignment unit, layer, targeting, traffic, and group allocation;
30
+ - Feature and group values;
31
+ - primary, secondary, and diagnostic metrics;
32
+ - planned MDE, alpha, power, duration, and stopping rule;
33
+ - actual start/end time and material configuration changes.
34
+
35
+ Without the original hypothesis or success rule, explain observed effects but do not retroactively invent success criteria.
36
+
37
+ ### 2. Check run integrity
38
+
39
+ Establish the experiment's actual state, complete runtime periods, and material
40
+ mid-run Feature, traffic, audience, metric, or identity changes. Treat
41
+ configuration history as evidence. Evaluate allocation, exposure, sample
42
+ sufficiency, and design quality through the diagnostic playbook rather than
43
+ duplicating its rules here.
44
+
45
+ ### 3. Run data diagnostics
46
+
47
+ Apply `diagnostic-playbook.md` in causal order and complete its diagnostic
48
+ checklist. Use
49
+ [`scripts/analyze_experiment.py`](scripts/analyze_experiment.py) for SRM and
50
+ group comparisons; do not calculate p-values or confidence intervals mentally.
51
+
52
+ ### 4. Interpret effects
53
+
54
+ For each pre-registered metric, interpret the verified metric contract from
55
+ the diagnostic playbook and report:
56
+
57
+ - verified source event, aggregation, analysis unit, denominator or population,
58
+ filters, and attribution window;
59
+ - control and treatment values;
60
+ - absolute and relative difference;
61
+ - confidence interval and p-value from deterministic calculation or the verified platform report;
62
+ - achieved sample versus planned sample;
63
+ - relation to MDE and business threshold;
64
+ - statistical and practical significance.
65
+
66
+ Distinguish:
67
+
68
+ - statistically significant and practically valuable;
69
+ - statistically significant but too small to matter;
70
+ - directionally positive but underpowered;
71
+ - no detectable effect within the current precision;
72
+ - significantly negative;
73
+ - invalid or inconclusive because of data quality.
74
+
75
+ Do not translate `p > alpha` into “no effect.” State that the current data did not establish an effect and describe the compatible interval.
76
+
77
+ ### 5. Analyze trends and segments
78
+
79
+ Check trend stability and abnormal dates using complete comparable periods.
80
+ Apply the trend and segment rules in `decision-framework.md`.
81
+
82
+ ### 6. Diagnose causes
83
+
84
+ For every suspected cause, provide:
85
+
86
+ - observed evidence;
87
+ - inference and uncertainty;
88
+ - competing explanation;
89
+ - the exact platform query or product check that would distinguish them;
90
+ - remediation if confirmed.
91
+
92
+ Do not produce a list of generic causes detached from evidence.
93
+ In the final report, express the distinguishing check as a concise user-facing
94
+ verification action. Include the raw `ae-cli` command only when the user
95
+ explicitly asks for commands, an audit trail, or debugging details.
96
+
97
+ ### 7. Make the decision
98
+
99
+ Apply `decision-framework.md` and return one decision supported by the trust
100
+ assessment, primary effect, duration, trends, segments, and diagnostic results.
101
+
102
+ Never start, pause, end, change traffic, delete, or roll out an experiment unless the user separately asks for that platform action. Those actions are outside interpretation and require explicit target-and-impact confirmation.
103
+
104
+ ## Output requirements
105
+
106
+ - Use the explicitly requested language, otherwise the language of the user's latest substantive message. Localize all user-facing prose, headings, labels, statuses, conclusions, warnings, limitations, and next actions; treat section names in this Skill as semantic guidance and remove unintended mixed-language output.
107
+ - Keep code, commands, raw IDs, event/property/metric names, Feature keys, SDK/API names, and official enum values unchanged when translation would alter their technical meaning.
108
+ - Lead with the decision status.
109
+ - Use an adaptive report, not a fixed numbered template. Organize the default
110
+ user-facing hierarchy as: conclusion, core impact, experiment decision, and
111
+ product optimization or next action.
112
+ - Keep data reliability, core metrics, and attention metrics inside the
113
+ **core impact** section:
114
+ - **Data reliability validation**: summarize the diagnostic checklist and
115
+ state the overall result as reliable, limited, or unreliable. Keep normal
116
+ checks compact and expand only warnings, failures, or unverified checks.
117
+ - **Core metrics**: present the primary metric's control and treatment
118
+ values, absolute and relative effect, interval and p-value, relation to MDE
119
+ or the business threshold, and business meaning. Use causal wording only
120
+ when the trust assessment supports it.
121
+ - **Attention metrics**: present secondary and diagnostic metrics only when
122
+ they affect the decision, explain the mechanism, or reveal material risk.
123
+ Do not promote a post-hoc metric into the success criterion.
124
+ - For a brief answer, collapse these subsections into compact paragraphs under
125
+ core impact; do not promote them into unrelated top-level sections.
126
+ - Treat labels such as `material blockers`, `trust blockers`, diagnostic
127
+ severity names, and framework labels such as `Gate 1` or `Gate 2` as
128
+ internal taxonomy. Never expose them as report headings or append their
129
+ English forms after localized headings. Use natural
130
+ user-facing wording such as “数据可靠性验证” or “当前为什么不能下结论” when
131
+ those concepts need a heading.
132
+ - Place metric trends and metric-related segments under the relevant core or
133
+ attention metric. Add broader context, cause diagnosis, assumptions, or
134
+ limitations only when they materially change interpretation.
135
+ - Use a product-optimization section only when trustworthy evidence supports a
136
+ specific product change. Otherwise use a next-validation or next-action
137
+ section.
138
+ - Include only content supported by the request and evidence. Do not add a section merely because it appeared in a previous report.
139
+ - Separate platform observations, deterministic calculations, and inferred causes. State uncertainty and evidence gaps explicitly.
140
+ - Do not return an unexplained raw table.
141
+
142
+ ## Failure behavior
143
+
144
+ - Ambiguous project or experiment: show resolved candidates and ask; do not guess.
145
+ - Missing report capability: preserve the `ae-cli` gap and request a platform export with required fields.
146
+ - Permission failure: stop dependent queries and report the missing permission.
147
+ - Empty successful report: say no matching data; do not relabel it as a transport failure.
148
+ - Partial success: use available evidence and enumerate failures.
149
+ - Conflicting sources: prefer raw assignment/exposure evidence for data-quality diagnosis and explain the discrepancy.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Experiment Insight & Diagnosis"
3
+ short_description: "Validate AE experiments and recommend the next decision"
4
+ default_prompt: "Use $ae-experiment-insight to diagnose an AE experiment and recommend the next decision."