@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 +44 -13
- package/dist/mcp/register.js +24 -7
- package/package.json +2 -1
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 (
|
|
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(
|
|
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(
|
|
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(
|
|
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 ≥
|
|
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>",
|
|
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
|
|
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 =
|
|
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 +
|
|
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(
|
|
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:
|
|
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(
|
|
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(
|
|
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 } })
|
package/dist/mcp/register.js
CHANGED
|
@@ -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
|
|
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 =
|
|
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 +
|
|
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(
|
|
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:
|
|
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(
|
|
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(
|
|
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.
|
|
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",
|