gamealgo-cli 0.1.5 → 0.1.6

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/cli/README.md CHANGED
@@ -161,15 +161,17 @@ gamealgo experiment strategy publish strategy.yaml --yes
161
161
 
162
162
  版本升级判断、Strategy 最低版本和托管实验覆盖率门槛的完整说明可通过 `gamealgo docs experiment-integration-versions` 获取。
163
163
 
164
- 创建手动实验 Run:
164
+ 默认创建托管实验 Run。除非开发者具备专业的实验设计和统计判断能力,并且会持续人工管理实验,否则不要使用手动实验:
165
165
 
166
166
  ```yaml
167
167
  # run.yaml
168
- displayName: 广告频率第一轮
169
- type: manual
168
+ displayName: 广告频率托管实验
170
169
  platform: ios
171
170
  objectiveTemplate: ltv_proxy@1
172
171
  baselineVariantId: control
172
+ minWindowDays: 7
173
+ maxWindowDays: 14
174
+ maxVariantsPerRound: 2
173
175
  variants:
174
176
  - variantId: control
175
177
  weight: 1
@@ -191,10 +193,14 @@ variants:
191
193
  gamealgo experiment run create ad_frequency run.yaml --yes
192
194
  ```
193
195
 
196
+ CLI 在未声明 `type` 时默认提交 `managed`。托管实验由平台管理轮次、在参考周期后判断是否需要延长,并在证据明确时完成后续轮次和推全;缺少 `minWindowDays` 或 `maxWindowDays` 会直接拒绝创建,不会静默套用周期。
197
+
194
198
  Run 不会继承 Strategy 的脚本。每个需要执行脚本的 variant 都必须显式配置 `script.versionId`;省略 `script` 明确表示该组是 config-only。即使多个 variant 使用同一份脚本,也要在每个 variant 上重复填写同一个不可变 `versionId`。
195
199
 
196
200
  如果要把当前默认参数也放进实验,需要显式配置 `variantId: default`。`default` 会快照 Strategy 当前默认参数,因此不能写 `config`;如果默认组需要执行脚本,也必须在 `default` 上显式写 `script.versionId`。否则默认参数不参与流量。所有有流量的实验组数量必须至少为 2。
197
201
 
202
+ Run 创建后,用户不能修改 variant、权重或启停状态,否则同一用户可能换组,前后数据也无法按同一实验口径比较。托管实验只允许平台在轮次结束后按既定流程切换下一轮候选,用户不能干预流量。需要改变候选或分流时,先取消当前 Run,再创建新 Run。
203
+
198
204
  查看、评估和完成实验闭环:
199
205
 
200
206
  ```bash
@@ -207,7 +213,7 @@ gamealgo experiment run report xrun_xxxxxxxxxxxxxxxx
207
213
  gamealgo experiment run promote xrun_xxxxxxxxxxxxxxxx --variant slower_ads --message "采用低频广告组" --yes
208
214
  ```
209
215
 
210
- `baselineVariantId` 是本次评估的对照组,必须指向一个有流量的实验组。平台按小时更新“当前评估”;`report` 返回已生成的正式报告。`promote` 会把指定 variant 写回 Strategy 默认参数,并结束当前 Run。取消实验用:
216
+ `baselineVariantId` 是本次评估的对照组,必须指向一个有流量的实验组。平台按小时更新“当前评估”;`report` 返回已生成的正式报告。`promote` 可在手动或托管 Run 仍处于运行中时提前人工推全,不要求平台已经给出正式 winner。它会立即结束 Run,把指定 variant 的 `config` / `script` 写回 Strategy 默认参数,并淘汰其他实验组;必须通过 `--message` 记录人工推全原因。取消实验用:
211
217
 
212
218
  `objectiveTemplate` 必须显式选择:
213
219
 
@@ -229,6 +235,22 @@ gamealgo experiment run objective xrun_xxxxxxxxxxxxxxxx \
229
235
  gamealgo experiment run cancel xrun_xxxxxxxxxxxxxxxx --message "实验停止" --yes
230
236
  ```
231
237
 
238
+ ### 高级用法:手动实验
239
+
240
+ 手动实验不会自动延长、形成最终结论或推全。只有开发者能够自行决定采集周期、判断统计证据并负责完成实验闭环时才使用。必须通过以下任一方式显式开启:
241
+
242
+ ```bash
243
+ gamealgo experiment run create ad_frequency manual-run.yaml --manual --yes
244
+ ```
245
+
246
+ 或者在 Run 文件中明确填写:
247
+
248
+ ```yaml
249
+ type: manual
250
+ ```
251
+
252
+ 手动 Run 不配置 `minWindowDays`、`maxWindowDays` 和 `startCondition`。其分流同样在整个 Run 内固定;需要调整分流时,取消当前 Run 后重新创建。
253
+
232
254
  回滚默认参数会创建一个新的参数版本,不会改写历史版本:
233
255
 
234
256
  ```bash
@@ -243,31 +265,13 @@ gamealgo experiment override list xrun_xxxxxxxxxxxxxxxx
243
265
  gamealgo experiment override delete xrun_xxxxxxxxxxxxxxxx --user user-1 --yes
244
266
  ```
245
267
 
246
- 托管实验也是创建 `type: managed` 的 Run,平台会按轮次自动调整流量并最终推全胜出组:
268
+ 托管实验可以使用 `startCondition` 等待接入版本覆盖率和用户数达到门槛:
247
269
 
248
270
  ```yaml
249
- displayName: 广告频率托管实验
250
- type: managed
251
- platform: ios
252
- objectiveTemplate: ltv_proxy@1
253
- baselineVariantId: alpha
254
- minWindowDays: 7
255
- maxWindowDays: 14
256
- maxVariantsPerRound: 3
257
271
  # 可选:仅托管实验支持。最近 24 小时同时满足两个门槛后才开始首轮。
258
272
  startCondition:
259
273
  minCoverage: 0.8
260
274
  minUsers: 100
261
- variants:
262
- - variantId: alpha
263
- config: { firstAdLevel: 4, interval: 30 }
264
- script: { versionId: sv_xxxxxxxxxxxxxxxx }
265
- - variantId: bravo
266
- config: { firstAdLevel: 5, interval: 30 }
267
- script: { versionId: sv_xxxxxxxxxxxxxxxx }
268
- - variantId: charlie
269
- config: { firstAdLevel: 4, interval: 45 }
270
- script: { versionId: sv_xxxxxxxxxxxxxxxx }
271
275
  ```
272
276
 
273
277
  `minCoverage` 表示指定平台最近 24 小时内,实验接入版本达到 Strategy `requiredIntegrationVersion` 的去重用户占比;`minUsers` 表示同一窗口内的非 Debug 去重用户数。两项可单独配置,同时配置时必须同时满足。等待期间 Strategy 继续下发默认参数,满足门槛后平台才生成首轮时间窗并开始分流。手动实验不支持 `startCondition`,托管实验不配置时仍会立即启动。`experiment run show` 返回 `startConditionSnapshot`,可查看当前用户数、覆盖率和最近检查时间。
package/dist/gamealgo.js CHANGED
@@ -387,7 +387,7 @@ async function handleExperimentRun(client, args, global) {
387
387
  const strategyKey = optionalString(flags.strategy || flags.key) || optionalString(args.shift());
388
388
  const filePath = optionalString(flags.file || flags.input) || optionalString(args.shift());
389
389
  if (!strategyKey || !filePath)
390
- throw new Error("usage: gamealgo experiment run create <strategyKey> <run.yaml> --yes");
390
+ throw new Error("usage: gamealgo experiment run create <strategyKey> <run.yaml> [--manual] --yes");
391
391
  await requireYes(flags, global, "experiment run create");
392
392
  const body = await readObjectFile(filePath, "experiment run");
393
393
  if (!optionalString(body.displayName))
@@ -400,7 +400,24 @@ async function handleExperimentRun(client, args, global) {
400
400
  throw new Error("experiment run requires explicit baselineVariantId");
401
401
  }
402
402
  validateExperimentRunVariantScripts(body.variants);
403
- const runType = optionalString(body.type) || "manual";
403
+ const fileRunType = optionalString(body.type);
404
+ const flagRunType = optionalString(flags.type);
405
+ const manualFlag = flags.manual === true;
406
+ if (fileRunType && fileRunType !== "managed" && fileRunType !== "manual") {
407
+ throw new Error("experiment run type must be managed or manual");
408
+ }
409
+ if (flagRunType && flagRunType !== "managed" && flagRunType !== "manual") {
410
+ throw new Error("--type must be managed or manual");
411
+ }
412
+ if (manualFlag && flagRunType && flagRunType !== "manual") {
413
+ throw new Error("--manual conflicts with --type managed");
414
+ }
415
+ const requestedRunType = manualFlag ? "manual" : flagRunType;
416
+ if (requestedRunType && fileRunType && requestedRunType !== fileRunType) {
417
+ throw new Error(`experiment run type conflict: file uses ${fileRunType}, CLI requests ${requestedRunType}`);
418
+ }
419
+ const runType = requestedRunType || fileRunType || "managed";
420
+ body.type = runType;
404
421
  if (runType !== "managed" && body.startCondition !== undefined) {
405
422
  throw new Error("experiment run startCondition is supported only when type is managed");
406
423
  }
@@ -414,6 +431,9 @@ async function handleExperimentRun(client, args, global) {
414
431
  throw new Error("managed experiment run requires maxWindowDays between minWindowDays and 60");
415
432
  }
416
433
  }
434
+ else {
435
+ process.stderr.write("Warning: creating a manual experiment. The platform will not automatically extend, conclude, or promote it; use manual mode only when the developer will manage the experiment decision.\n");
436
+ }
417
437
  const result = await client.createExperimentV2Run(strategyKey, body);
418
438
  await printResult(result, global);
419
439
  if (!global.json)
@@ -540,23 +560,7 @@ async function handleExperimentRun(client, args, global) {
540
560
  await printResult(await client.cancelExperimentV2Run(runId, message), global);
541
561
  return;
542
562
  }
543
- if (sub === "traffic") {
544
- const flags = parseFlags(args);
545
- const runId = optionalString(flags.run || flags["run-id"] || flags.id) || optionalString(args.shift());
546
- const variantId = optionalString(flags.variant || flags["variant-id"]) || optionalString(args.shift());
547
- if (!runId || !variantId)
548
- throw new Error("usage: gamealgo experiment run traffic <runId> <variantId> [--weight n] [--status running|paused] --message <reason> --yes");
549
- const message = requiredMessage(flags, "experiment run traffic");
550
- await requireYes(flags, global, "experiment run traffic");
551
- const body = { message };
552
- if (flags.weight !== undefined)
553
- body.weight = optionalNonNegativeInteger(flags.weight, "weight");
554
- if (flags.status !== undefined)
555
- body.status = optionalString(flags.status);
556
- await printResult(await client.updateExperimentV2VariantTraffic(runId, variantId, body), global);
557
- return;
558
- }
559
- throw new Error("usage: gamealgo experiment run <create|show|status|evaluate|report|objective|promote|cancel|traffic>");
563
+ throw new Error("usage: gamealgo experiment run <create|show|status|evaluate|report|objective|promote|cancel>");
560
564
  }
561
565
  function requiredMessage(flags, action) {
562
566
  const message = optionalString(flags.message);
@@ -1117,9 +1121,6 @@ class GameAlgoAdminClient {
1117
1121
  async deleteExperimentV2Override(runId, userId) {
1118
1122
  return await this.post(`/admin/v1/games/${encodeURIComponent(await this.gameId())}/experiment-runs-v2/${encodeURIComponent(runId)}/overrides/delete`, { userId });
1119
1123
  }
1120
- async updateExperimentV2VariantTraffic(runId, variantId, body) {
1121
- return await this.patch(`/admin/v1/games/${encodeURIComponent(await this.gameId())}/experiment-runs-v2/${encodeURIComponent(runId)}/variants/${encodeURIComponent(variantId)}/traffic`, body);
1122
- }
1123
1124
  async listConfigFiles() {
1124
1125
  return await this.get(`/admin/v1/games/${encodeURIComponent(await this.gameId())}/config-files`);
1125
1126
  }
@@ -1730,7 +1731,7 @@ Usage:
1730
1731
  gamealgo experiment strategy default ad_frequency strategy.yaml --yes
1731
1732
  gamealgo experiment strategy archive ad_frequency --yes
1732
1733
  gamealgo experiment strategy rollback ad_frequency --version-id xv_xxxxxxxxxxxxxxxx --message "回滚参数" --yes
1733
- gamealgo experiment run create ad_frequency run.yaml --yes
1734
+ gamealgo experiment run create ad_frequency run.yaml [--manual] --yes
1734
1735
  gamealgo experiment run show xrun_xxxxxxxxxxxxxxxx
1735
1736
  gamealgo experiment run status xrun_xxxxxxxxxxxxxxxx
1736
1737
  gamealgo experiment run evaluate xrun_xxxxxxxxxxxxxxxx --from 2026-07-01 --to 2026-07-07 --yes
@@ -1738,7 +1739,6 @@ Usage:
1738
1739
  gamealgo experiment run objective xrun_xxxxxxxxxxxxxxxx --objective active_days@1 --message "改为关注活跃天数" --yes
1739
1740
  gamealgo experiment run promote xrun_xxxxxxxxxxxxxxxx --variant bravo --message "采用胜出组" --yes
1740
1741
  gamealgo experiment run cancel xrun_xxxxxxxxxxxxxxxx --message "停止实验" --yes
1741
- gamealgo experiment run traffic xrun_xxxxxxxxxxxxxxxx bravo --weight 0 --status paused --message "暂停异常组" --yes
1742
1742
  gamealgo experiment override set xrun_xxxxxxxxxxxxxxxx --user user-1 --variant bravo --yes
1743
1743
  gamealgo experiment override list xrun_xxxxxxxxxxxxxxxx
1744
1744
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gamealgo-cli",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "GameAlgo command-line client for AI agents and game developers",
5
5
  "type": "module",
6
6
  "bin": {