@lexq/cli 0.1.49 → 0.1.50

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/dist/index.js CHANGED
@@ -315,6 +315,19 @@ function registerStatusCommand(program) {
315
315
  });
316
316
  }
317
317
 
318
+ // src/types/constants.ts
319
+ var MAX_ROUNDING_SCALE = 34;
320
+ var MIN_TRAFFIC_RATE = 1;
321
+ var MAX_TRAFFIC_RATE = 99;
322
+ var REPLAY_WINDOW_MAX_RECORDS = 5e4;
323
+ var PROFILE_SAMPLE_PERMILLE = 10;
324
+ var LATENCY_WINDOW_MILLIS = 6e4;
325
+ var TAIL_MIN_OBS = 3;
326
+ var SLOW_MULTIPLIER = 10;
327
+ var MIN_SAMPLES = 100;
328
+ var MIN_COHORT_SIZE = 3;
329
+ var PROFILE_DEFAULT_WINDOW_HOURS = 24;
330
+
318
331
  // src/commands/groups.ts
319
332
  import "commander";
320
333
  import dedent3 from "dedent";
@@ -528,11 +541,14 @@ ${data.length} total`);
528
541
  stop Stop the test and revert to 100% live version
529
542
  adjust Change the traffic percentage
530
543
 
531
- The traffic rate (1-99) determines what percentage goes to the challenger.
544
+ The traffic rate (${MIN_TRAFFIC_RATE}-${MAX_TRAFFIC_RATE}) determines what percentage goes to the challenger.
532
545
  The remaining traffic continues to the current live version.
533
546
  `
534
547
  );
535
- abTest.command("start").description("Start an A/B test").requiredOption("--group-id <groupId>", "Policy group ID").requiredOption("--version-id <versionId>", "Challenger version ID").requiredOption("--traffic-rate <rate>", "Traffic rate for challenger (1-99)").addHelpText(
548
+ abTest.command("start").description("Start an A/B test").requiredOption("--group-id <groupId>", "Policy group ID").requiredOption("--version-id <versionId>", "Challenger version ID").requiredOption(
549
+ "--traffic-rate <rate>",
550
+ `Traffic rate for challenger (${MIN_TRAFFIC_RATE}-${MAX_TRAFFIC_RATE})`
551
+ ).addHelpText(
536
552
  "after",
537
553
  dedent3`
538
554
 
@@ -590,7 +606,10 @@ ${data.length} total`);
590
606
  process.exit(1);
591
607
  }
592
608
  });
593
- abTest.command("adjust").description("Adjust A/B test traffic rate").requiredOption("--group-id <groupId>", "Policy group ID").requiredOption("--traffic-rate <rate>", "New traffic rate (1-99)").addHelpText(
609
+ abTest.command("adjust").description("Adjust A/B test traffic rate").requiredOption("--group-id <groupId>", "Policy group ID").requiredOption(
610
+ "--traffic-rate <rate>",
611
+ `New traffic rate (${MIN_TRAFFIC_RATE}-${MAX_TRAFFIC_RATE})`
612
+ ).addHelpText(
594
613
  "after",
595
614
  dedent3`
596
615
 
@@ -2359,11 +2378,17 @@ var WebhookPayloadFormat = ["GENERIC", "SLACK"];
2359
2378
  var ms = (nanos) => nanos == null ? "\u2013" : (nanos / 1e6).toFixed(2);
2360
2379
  var msWithUnit = (nanos) => nanos == null ? "\u2013" : `${(nanos / 1e6).toFixed(2)}ms`;
2361
2380
  function registerProfileCommands(program) {
2362
- program.command("profile <groupId>").description("Per-rule latency profile with relative slow-rule flags").option("--rule <ruleId>", "Single-rule detail (distributions + 60s window series)").option("--version <versionId>", "Version to inspect (default: live version)").option("--from <instant>", "Window start, ISO-8601 instant (default: 24h ago)").option("--to <instant>", "Window end, ISO-8601 instant (default: now)").option("--cache <state>", "Cache dimension for the rule table: HIT | MISS (default: HIT)").addHelpText(
2381
+ program.command("profile <groupId>").description("Per-rule latency profile with relative slow-rule flags").option(
2382
+ "--rule <ruleId>",
2383
+ `Single-rule detail (distributions + ${LATENCY_WINDOW_MILLIS / 1e3}s window series)`
2384
+ ).option("--version <versionId>", "Version to inspect (default: live version)").option(
2385
+ "--from <instant>",
2386
+ `Window start, ISO-8601 instant (default: ${PROFILE_DEFAULT_WINDOW_HOURS}h ago)`
2387
+ ).option("--to <instant>", "Window end, ISO-8601 instant (default: now)").option("--cache <state>", "Cache dimension for the rule table: HIT | MISS (default: HIT)").addHelpText(
2363
2388
  "after",
2364
2389
  dedent9`
2365
2390
 
2366
- Slow-rule judgment is relative only: flagged = p50 ≥ 10× the median of
2391
+ Slow-rule judgment is relative only: flagged = p50 ≥ ${SLOW_MULTIPLIER}× the median of
2367
2392
  per-rule p50s within the group. Absolute ms thresholds are intentionally
2368
2393
  not supported. Each percentile is withheld (–) unless n×(1−q) ≥ 3 —
2369
2394
  p50 from n ≥ 6, p95 from n ≥ 60, p99 from n ≥ 300. TOTAL is recorded
@@ -2643,7 +2668,7 @@ function registerReplayCommands(program) {
2643
2668
  process.exit(1);
2644
2669
  }
2645
2670
  });
2646
- replay.command("start").description("Submit a window replay job (blast radius)").requiredOption("--version-id <versionId>", "Candidate version to re-evaluate against").requiredOption("--from <date>", "Window start date (yyyy-MM-dd)").requiredOption("--to <date>", "Window end date (yyyy-MM-dd)").option("--max-records <number>", "Sample cap (hard cap 50k)").addHelpText(
2671
+ replay.command("start").description("Submit a window replay job (blast radius)").requiredOption("--version-id <versionId>", "Candidate version to re-evaluate against").requiredOption("--from <date>", "Window start date (yyyy-MM-dd)").requiredOption("--to <date>", "Window end date (yyyy-MM-dd)").option("--max-records <number>", `Sample cap (hard cap ${REPLAY_WINDOW_MAX_RECORDS / 1e3}k)`).addHelpText(
2647
2672
  "after",
2648
2673
  dedent11`
2649
2674
 
@@ -3594,7 +3619,7 @@ function registerRuleTools(server, callApi) {
3594
3619
  subsequent actions and subsequent winning rules still run. Enforcement is the caller's
3595
3620
  responsibility; the decision surfaces as the is_blocked fact.
3596
3621
 
3597
- RoundingOption (optional, MUTATE_FACT only): { scale: integer (0..34), mode?: "HALF_UP"|"HALF_DOWN"|"HALF_EVEN"|"FLOOR"|"CEILING"|"DOWN"|"UP" } mode defaults to HALF_UP. When omitted, calculator output is preserved at full precision (lossless).
3622
+ RoundingOption (optional, MUTATE_FACT only): { scale: integer (0..${MAX_ROUNDING_SCALE}), mode?: "HALF_UP"|"HALF_DOWN"|"HALF_EVEN"|"FLOOR"|"CEILING"|"DOWN"|"UP" } mode defaults to HALF_UP. When omitted, calculator output is preserved at full precision (lossless).
3598
3623
  `,
3599
3624
  inputSchema: {
3600
3625
  groupId: z3.string().uuid().describe("Policy group ID"),
@@ -4172,7 +4197,7 @@ function registerAnalyticsTools(server, callApi) {
4172
4197
 
4173
4198
  // src/mcp/tools/profile.ts
4174
4199
  import { z as z7 } from "zod";
4175
- var RELATIVE_THRESHOLD = "flagged = p50 \u2265 10\xD7 median of per-rule p50s within the group; absolute thresholds are intentionally not supported.";
4200
+ var RELATIVE_THRESHOLD = `flagged = p50 \u2265 ${SLOW_MULTIPLIER}\xD7 median of per-rule p50s within the group; absolute thresholds are intentionally not supported.`;
4176
4201
  function profileParams(opts) {
4177
4202
  const params = {};
4178
4203
  if (opts.versionId) params.versionId = opts.versionId;
@@ -4186,11 +4211,13 @@ function registerProfileTools(server, callApi) {
4186
4211
  "lexq_profile_overview",
4187
4212
  {
4188
4213
  title: "Group Latency Profile",
4189
- description: "Per-rule latency profile of a policy group over a time window: group TOTAL distribution split by cache state (HIT = compiled ruleset cache hit, MISS = deep-load + compile), a per-rule CONDITION/ACTION percentile table, and slow-rule flags. " + RELATIVE_THRESHOLD + " Every percentile is accompanied by its sample count n; a percentile is withheld (null) unless n\xD7(1\u2212q) \u2265 3 (p50 needs n \u2265 6, p95 n \u2265 60, p99 n \u2265 300 \u2014 display gate, separate from the n \u2265 100 judgment gate). Baselines report INSUFFICIENT_COHORT when fewer than 3 rules qualify. Rule detail comes from a deterministic 1% sample of calls; TOTAL is recorded for every call. Defaults: last 24h, live version, cacheState HIT.",
4214
+ description: "Per-rule latency profile of a policy group over a time window: group TOTAL distribution split by cache state (HIT = compiled ruleset cache hit, MISS = deep-load + compile), a per-rule CONDITION/ACTION percentile table, and slow-rule flags. " + RELATIVE_THRESHOLD + ` Every percentile is accompanied by its sample count n; a percentile is withheld (null) unless n\xD7(1\u2212q) \u2265 ${TAIL_MIN_OBS} (p50 needs n \u2265 6, p95 n \u2265 60, p99 n \u2265 300 \u2014 display gate, separate from the n \u2265 ${MIN_SAMPLES} judgment gate). Baselines report INSUFFICIENT_COHORT when fewer than ${MIN_COHORT_SIZE} rules qualify. Rule detail comes from a deterministic ${PROFILE_SAMPLE_PERMILLE / 10}% sample of calls; TOTAL is recorded for every call. Defaults: last ${PROFILE_DEFAULT_WINDOW_HOURS}h, live version, cacheState HIT.`,
4190
4215
  inputSchema: {
4191
4216
  groupId: z7.string().uuid().describe("Policy group ID"),
4192
4217
  versionId: z7.string().uuid().optional().describe("Version to inspect (default: live version)"),
4193
- from: z7.string().optional().describe("Window start, ISO-8601 instant (e.g. 2026-07-01T00:00:00Z). Default: 24h ago"),
4218
+ from: z7.string().optional().describe(
4219
+ `Window start, ISO-8601 instant (e.g. 2026-07-01T00:00:00Z). Default: ${PROFILE_DEFAULT_WINDOW_HOURS}h ago`
4220
+ ),
4194
4221
  to: z7.string().optional().describe("Window end, ISO-8601 instant. Default: now"),
4195
4222
  cacheState: z7.enum(["HIT", "MISS"]).optional().describe("Cache dimension for the rule table and judgment (default: HIT)")
4196
4223
  }
@@ -4203,12 +4230,14 @@ function registerProfileTools(server, callApi) {
4203
4230
  "lexq_profile_rule",
4204
4231
  {
4205
4232
  title: "Rule Latency Detail",
4206
- description: "Single-rule latency detail: merged phase \xD7 cacheState distributions plus a per-window time series (60s windows). Missing windows are genuine gaps \u2014 never interpolated. Series points carry each window's own values; percentiles in merged distributions are withheld (null) unless n\xD7(1\u2212q) \u2265 3 (p50 n \u2265 6, p95 n \u2265 60, p99 n \u2265 300). " + RELATIVE_THRESHOLD,
4233
+ description: `Single-rule latency detail: merged phase \xD7 cacheState distributions plus a per-window time series (${LATENCY_WINDOW_MILLIS / 1e3}s windows). Missing windows are genuine gaps \u2014 never interpolated. Series points carry each window's own values; percentiles in merged distributions are withheld (null) unless n\xD7(1\u2212q) \u2265 ${TAIL_MIN_OBS} (p50 n \u2265 6, p95 n \u2265 60, p99 n \u2265 300). ` + RELATIVE_THRESHOLD,
4207
4234
  inputSchema: {
4208
4235
  groupId: z7.string().uuid().describe("Policy group ID"),
4209
4236
  ruleId: z7.string().uuid().describe("Rule ID (from lexq_profile_overview)"),
4210
4237
  versionId: z7.string().uuid().optional().describe("Version to inspect (default: live version)"),
4211
- from: z7.string().optional().describe("Window start, ISO-8601 instant. Default: 24h ago"),
4238
+ from: z7.string().optional().describe(
4239
+ `Window start, ISO-8601 instant. Default: ${PROFILE_DEFAULT_WINDOW_HOURS}h ago`
4240
+ ),
4212
4241
  to: z7.string().optional().describe("Window end, ISO-8601 instant. Default: now")
4213
4242
  }
4214
4243
  },
@@ -4242,7 +4271,9 @@ function registerReplayTools(server, callApi) {
4242
4271
  candidateVersionId: z8.string().uuid().describe("Version to re-evaluate against"),
4243
4272
  from: z8.string().describe("Window start date (yyyy-MM-dd)"),
4244
4273
  to: z8.string().describe("Window end date (yyyy-MM-dd)"),
4245
- maxRecords: z8.number().int().min(1).optional().describe("Sample cap (server default applies; hard cap 50k)")
4274
+ maxRecords: z8.number().int().min(1).optional().describe(
4275
+ `Sample cap (server default applies; hard cap ${REPLAY_WINDOW_MAX_RECORDS / 1e3}k)`
4276
+ )
4246
4277
  }
4247
4278
  },
4248
4279
  async ({ candidateVersionId, from, to, maxRecords }) => callApi("POST", "replay/jobs", { body: { candidateVersionId, from, to, maxRecords } })
@@ -309,6 +309,17 @@ function registerVersionTools(server, callApi) {
309
309
  );
310
310
  }
311
311
 
312
+ // src/types/constants.ts
313
+ var MAX_ROUNDING_SCALE = 34;
314
+ var REPLAY_WINDOW_MAX_RECORDS = 5e4;
315
+ var PROFILE_SAMPLE_PERMILLE = 10;
316
+ var LATENCY_WINDOW_MILLIS = 6e4;
317
+ var TAIL_MIN_OBS = 3;
318
+ var SLOW_MULTIPLIER = 10;
319
+ var MIN_SAMPLES = 100;
320
+ var MIN_COHORT_SIZE = 3;
321
+ var PROFILE_DEFAULT_WINDOW_HOURS = 24;
322
+
312
323
  // src/mcp/tools/rules.ts
313
324
  import { z as z3 } from "zod";
314
325
  import dedent from "dedent";
@@ -401,7 +412,7 @@ function registerRuleTools(server, callApi) {
401
412
  subsequent actions and subsequent winning rules still run. Enforcement is the caller's
402
413
  responsibility; the decision surfaces as the is_blocked fact.
403
414
 
404
- RoundingOption (optional, MUTATE_FACT only): { scale: integer (0..34), mode?: "HALF_UP"|"HALF_DOWN"|"HALF_EVEN"|"FLOOR"|"CEILING"|"DOWN"|"UP" } mode defaults to HALF_UP. When omitted, calculator output is preserved at full precision (lossless).
415
+ RoundingOption (optional, MUTATE_FACT only): { scale: integer (0..${MAX_ROUNDING_SCALE}), mode?: "HALF_UP"|"HALF_DOWN"|"HALF_EVEN"|"FLOOR"|"CEILING"|"DOWN"|"UP" } mode defaults to HALF_UP. When omitted, calculator output is preserved at full precision (lossless).
405
416
  `,
406
417
  inputSchema: {
407
418
  groupId: z3.string().uuid().describe("Policy group ID"),
@@ -979,7 +990,7 @@ function registerAnalyticsTools(server, callApi) {
979
990
 
980
991
  // src/mcp/tools/profile.ts
981
992
  import { z as z7 } from "zod";
982
- var RELATIVE_THRESHOLD = "flagged = p50 \u2265 10\xD7 median of per-rule p50s within the group; absolute thresholds are intentionally not supported.";
993
+ var RELATIVE_THRESHOLD = `flagged = p50 \u2265 ${SLOW_MULTIPLIER}\xD7 median of per-rule p50s within the group; absolute thresholds are intentionally not supported.`;
983
994
  function profileParams(opts) {
984
995
  const params = {};
985
996
  if (opts.versionId) params.versionId = opts.versionId;
@@ -993,11 +1004,13 @@ function registerProfileTools(server, callApi) {
993
1004
  "lexq_profile_overview",
994
1005
  {
995
1006
  title: "Group Latency Profile",
996
- description: "Per-rule latency profile of a policy group over a time window: group TOTAL distribution split by cache state (HIT = compiled ruleset cache hit, MISS = deep-load + compile), a per-rule CONDITION/ACTION percentile table, and slow-rule flags. " + RELATIVE_THRESHOLD + " Every percentile is accompanied by its sample count n; a percentile is withheld (null) unless n\xD7(1\u2212q) \u2265 3 (p50 needs n \u2265 6, p95 n \u2265 60, p99 n \u2265 300 \u2014 display gate, separate from the n \u2265 100 judgment gate). Baselines report INSUFFICIENT_COHORT when fewer than 3 rules qualify. Rule detail comes from a deterministic 1% sample of calls; TOTAL is recorded for every call. Defaults: last 24h, live version, cacheState HIT.",
1007
+ description: "Per-rule latency profile of a policy group over a time window: group TOTAL distribution split by cache state (HIT = compiled ruleset cache hit, MISS = deep-load + compile), a per-rule CONDITION/ACTION percentile table, and slow-rule flags. " + RELATIVE_THRESHOLD + ` Every percentile is accompanied by its sample count n; a percentile is withheld (null) unless n\xD7(1\u2212q) \u2265 ${TAIL_MIN_OBS} (p50 needs n \u2265 6, p95 n \u2265 60, p99 n \u2265 300 \u2014 display gate, separate from the n \u2265 ${MIN_SAMPLES} judgment gate). Baselines report INSUFFICIENT_COHORT when fewer than ${MIN_COHORT_SIZE} rules qualify. Rule detail comes from a deterministic ${PROFILE_SAMPLE_PERMILLE / 10}% sample of calls; TOTAL is recorded for every call. Defaults: last ${PROFILE_DEFAULT_WINDOW_HOURS}h, live version, cacheState HIT.`,
997
1008
  inputSchema: {
998
1009
  groupId: z7.string().uuid().describe("Policy group ID"),
999
1010
  versionId: z7.string().uuid().optional().describe("Version to inspect (default: live version)"),
1000
- from: z7.string().optional().describe("Window start, ISO-8601 instant (e.g. 2026-07-01T00:00:00Z). Default: 24h ago"),
1011
+ from: z7.string().optional().describe(
1012
+ `Window start, ISO-8601 instant (e.g. 2026-07-01T00:00:00Z). Default: ${PROFILE_DEFAULT_WINDOW_HOURS}h ago`
1013
+ ),
1001
1014
  to: z7.string().optional().describe("Window end, ISO-8601 instant. Default: now"),
1002
1015
  cacheState: z7.enum(["HIT", "MISS"]).optional().describe("Cache dimension for the rule table and judgment (default: HIT)")
1003
1016
  }
@@ -1010,12 +1023,14 @@ function registerProfileTools(server, callApi) {
1010
1023
  "lexq_profile_rule",
1011
1024
  {
1012
1025
  title: "Rule Latency Detail",
1013
- description: "Single-rule latency detail: merged phase \xD7 cacheState distributions plus a per-window time series (60s windows). Missing windows are genuine gaps \u2014 never interpolated. Series points carry each window's own values; percentiles in merged distributions are withheld (null) unless n\xD7(1\u2212q) \u2265 3 (p50 n \u2265 6, p95 n \u2265 60, p99 n \u2265 300). " + RELATIVE_THRESHOLD,
1026
+ description: `Single-rule latency detail: merged phase \xD7 cacheState distributions plus a per-window time series (${LATENCY_WINDOW_MILLIS / 1e3}s windows). Missing windows are genuine gaps \u2014 never interpolated. Series points carry each window's own values; percentiles in merged distributions are withheld (null) unless n\xD7(1\u2212q) \u2265 ${TAIL_MIN_OBS} (p50 n \u2265 6, p95 n \u2265 60, p99 n \u2265 300). ` + RELATIVE_THRESHOLD,
1014
1027
  inputSchema: {
1015
1028
  groupId: z7.string().uuid().describe("Policy group ID"),
1016
1029
  ruleId: z7.string().uuid().describe("Rule ID (from lexq_profile_overview)"),
1017
1030
  versionId: z7.string().uuid().optional().describe("Version to inspect (default: live version)"),
1018
- from: z7.string().optional().describe("Window start, ISO-8601 instant. Default: 24h ago"),
1031
+ from: z7.string().optional().describe(
1032
+ `Window start, ISO-8601 instant. Default: ${PROFILE_DEFAULT_WINDOW_HOURS}h ago`
1033
+ ),
1019
1034
  to: z7.string().optional().describe("Window end, ISO-8601 instant. Default: now")
1020
1035
  }
1021
1036
  },
@@ -1049,7 +1064,9 @@ function registerReplayTools(server, callApi) {
1049
1064
  candidateVersionId: z8.string().uuid().describe("Version to re-evaluate against"),
1050
1065
  from: z8.string().describe("Window start date (yyyy-MM-dd)"),
1051
1066
  to: z8.string().describe("Window end date (yyyy-MM-dd)"),
1052
- maxRecords: z8.number().int().min(1).optional().describe("Sample cap (server default applies; hard cap 50k)")
1067
+ maxRecords: z8.number().int().min(1).optional().describe(
1068
+ `Sample cap (server default applies; hard cap ${REPLAY_WINDOW_MAX_RECORDS / 1e3}k)`
1069
+ )
1053
1070
  }
1054
1071
  },
1055
1072
  async ({ candidateVersionId, from, to, maxRecords }) => callApi("POST", "replay/jobs", { body: { candidateVersionId, from, to, maxRecords } })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lexq/cli",
3
- "version": "0.1.49",
3
+ "version": "0.1.50",
4
4
  "description": "LexQ CLI — manage policies, simulate rules, and deploy from the terminal. Built for humans and AI agents.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -29,6 +29,7 @@
29
29
  "typecheck": "tsc --noEmit",
30
30
  "enums": "node scripts/gen-enums.mjs",
31
31
  "enums:check": "node scripts/gen-enums.mjs --check",
32
+ "constants:check": "node scripts/check-constants.mjs",
32
33
  "start": "node dist/index.js",
33
34
  "test:decimals": "pnpm build && node tests/exact-decimals.mjs",
34
35
  "prepublishOnly": "pnpm build",