@lexq/cli 0.1.52 → 0.1.54
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/AGENTS.md
CHANGED
|
@@ -129,8 +129,9 @@ Place this file in the project root. The IDE will auto-discover it.
|
|
|
129
129
|
|
|
130
130
|
### Claude Code
|
|
131
131
|
|
|
132
|
-
Reads this file
|
|
133
|
-
guide.
|
|
132
|
+
Reads this file through the root `CLAUDE.md`, which is a one-line import rather than a copy. There is no
|
|
133
|
+
Claude-specific document to keep in sync. The CLI guide is this file plus `skills/`; `CONTEXT.md` covers platform
|
|
134
|
+
architecture separately and ships alongside.
|
|
134
135
|
|
|
135
136
|
### Gemini CLI
|
|
136
137
|
|
package/dist/index.js
CHANGED
|
@@ -2359,6 +2359,7 @@ import "commander";
|
|
|
2359
2359
|
import dedent9 from "dedent";
|
|
2360
2360
|
|
|
2361
2361
|
// src/types/enums.ts
|
|
2362
|
+
var ValueType = ["STRING", "NUMBER", "BOOLEAN", "LIST_STRING", "LIST_NUMBER"];
|
|
2362
2363
|
var ConflictResolutionMode = ["NONE", "EXCLUSIVE", "MAX_N"];
|
|
2363
2364
|
var ConflictResolutionStrategy = ["HIGHEST_PRIORITY"];
|
|
2364
2365
|
var ProfileCacheState = ["HIT", "MISS"];
|
|
@@ -3405,7 +3406,7 @@ function registerGroupTools(server, callApi) {
|
|
|
3405
3406
|
"lexq_ab_test_start",
|
|
3406
3407
|
{
|
|
3407
3408
|
title: "Start A/B Test",
|
|
3408
|
-
description: "Start an A/B test on a policy group. Requires a challenger version ID and traffic rate.",
|
|
3409
|
+
description: "Start an A/B test on a policy group. Requires a challenger version ID and traffic rate. The split is computed from context.trafficKey on each execution request; requests that omit it never reach the challenger and the test stays at 0%.",
|
|
3409
3410
|
inputSchema: {
|
|
3410
3411
|
groupId: z.string().uuid().describe("Policy group ID"),
|
|
3411
3412
|
testVersionId: z.string().uuid().describe("Challenger version ID to test"),
|
|
@@ -3722,6 +3723,7 @@ import { z as z4 } from "zod";
|
|
|
3722
3723
|
var FACT_KEY_PATTERN = /^[a-zA-Z][a-zA-Z0-9_]*$/;
|
|
3723
3724
|
|
|
3724
3725
|
// src/mcp/tools/facts.ts
|
|
3726
|
+
var VALUE_TYPES = ValueType;
|
|
3725
3727
|
function registerFactTools(server, callApi) {
|
|
3726
3728
|
server.registerTool(
|
|
3727
3729
|
"lexq_facts_list",
|
|
@@ -3748,7 +3750,7 @@ function registerFactTools(server, callApi) {
|
|
|
3748
3750
|
inputSchema: {
|
|
3749
3751
|
key: z4.string().regex(FACT_KEY_PATTERN).describe("Variable key. Any casing; must start with a letter."),
|
|
3750
3752
|
name: z4.string().describe("Display name"),
|
|
3751
|
-
type: z4.enum(
|
|
3753
|
+
type: z4.enum(VALUE_TYPES).describe("Value type"),
|
|
3752
3754
|
description: z4.string().optional().describe("Description"),
|
|
3753
3755
|
isRequired: z4.boolean().default(false).describe("Whether this fact is required for rule evaluation"),
|
|
3754
3756
|
isPii: z4.boolean().default(false).describe(
|
|
@@ -3762,11 +3764,14 @@ function registerFactTools(server, callApi) {
|
|
|
3762
3764
|
"lexq_facts_update",
|
|
3763
3765
|
{
|
|
3764
3766
|
title: "Update Fact Definition",
|
|
3765
|
-
description: "Update a fact definition.
|
|
3767
|
+
description: "Update a fact definition. The key is immutable. The type can change only while no rule references the fact; if any does, the call fails with FD-007 and reports the count. Only the fields you send are changed. System facts accept name, description, and PII only.",
|
|
3766
3768
|
inputSchema: {
|
|
3767
3769
|
factId: z4.string().uuid().describe("Fact definition ID"),
|
|
3768
3770
|
name: z4.string().optional().describe("Display name"),
|
|
3769
3771
|
description: z4.string().optional().describe("Description"),
|
|
3772
|
+
type: z4.enum(VALUE_TYPES).optional().describe(
|
|
3773
|
+
"Value type. Omit to leave it unchanged. Changing it fails with FD-007 while any rule references the fact."
|
|
3774
|
+
),
|
|
3770
3775
|
isRequired: z4.boolean().optional().describe("Required flag"),
|
|
3771
3776
|
isPii: z4.boolean().optional().describe("PII flag \u2014 enables/disables masking (changeable even on system facts)")
|
|
3772
3777
|
}
|
|
@@ -3777,7 +3782,7 @@ function registerFactTools(server, callApi) {
|
|
|
3777
3782
|
"lexq_facts_delete",
|
|
3778
3783
|
{
|
|
3779
3784
|
title: "Delete Fact Definition",
|
|
3780
|
-
description: "Delete a fact definition. System facts cannot be deleted.",
|
|
3785
|
+
description: "Delete a fact definition. System facts cannot be deleted. Neither can a fact that any rule references: that call fails with FD-006 and reports the count. Remove the references first.",
|
|
3781
3786
|
inputSchema: {
|
|
3782
3787
|
factId: z4.string().uuid().describe("Fact definition ID")
|
|
3783
3788
|
}
|
package/dist/mcp/register.js
CHANGED
|
@@ -15,6 +15,7 @@ function registerStatusTools(server, callApi) {
|
|
|
15
15
|
import { z } from "zod";
|
|
16
16
|
|
|
17
17
|
// src/types/enums.ts
|
|
18
|
+
var ValueType = ["STRING", "NUMBER", "BOOLEAN", "LIST_STRING", "LIST_NUMBER"];
|
|
18
19
|
var ConflictResolutionMode = ["NONE", "EXCLUSIVE", "MAX_N"];
|
|
19
20
|
var ConflictResolutionStrategy = ["HIGHEST_PRIORITY"];
|
|
20
21
|
var FailureStatus = ["PENDING", "RESOLVED", "IGNORED"];
|
|
@@ -128,7 +129,7 @@ function registerGroupTools(server, callApi) {
|
|
|
128
129
|
"lexq_ab_test_start",
|
|
129
130
|
{
|
|
130
131
|
title: "Start A/B Test",
|
|
131
|
-
description: "Start an A/B test on a policy group. Requires a challenger version ID and traffic rate.",
|
|
132
|
+
description: "Start an A/B test on a policy group. Requires a challenger version ID and traffic rate. The split is computed from context.trafficKey on each execution request; requests that omit it never reach the challenger and the test stays at 0%.",
|
|
132
133
|
inputSchema: {
|
|
133
134
|
groupId: z.string().uuid().describe("Policy group ID"),
|
|
134
135
|
testVersionId: z.string().uuid().describe("Challenger version ID to test"),
|
|
@@ -514,6 +515,7 @@ import { z as z4 } from "zod";
|
|
|
514
515
|
var FACT_KEY_PATTERN = /^[a-zA-Z][a-zA-Z0-9_]*$/;
|
|
515
516
|
|
|
516
517
|
// src/mcp/tools/facts.ts
|
|
518
|
+
var VALUE_TYPES = ValueType;
|
|
517
519
|
function registerFactTools(server, callApi) {
|
|
518
520
|
server.registerTool(
|
|
519
521
|
"lexq_facts_list",
|
|
@@ -540,7 +542,7 @@ function registerFactTools(server, callApi) {
|
|
|
540
542
|
inputSchema: {
|
|
541
543
|
key: z4.string().regex(FACT_KEY_PATTERN).describe("Variable key. Any casing; must start with a letter."),
|
|
542
544
|
name: z4.string().describe("Display name"),
|
|
543
|
-
type: z4.enum(
|
|
545
|
+
type: z4.enum(VALUE_TYPES).describe("Value type"),
|
|
544
546
|
description: z4.string().optional().describe("Description"),
|
|
545
547
|
isRequired: z4.boolean().default(false).describe("Whether this fact is required for rule evaluation"),
|
|
546
548
|
isPii: z4.boolean().default(false).describe(
|
|
@@ -554,11 +556,14 @@ function registerFactTools(server, callApi) {
|
|
|
554
556
|
"lexq_facts_update",
|
|
555
557
|
{
|
|
556
558
|
title: "Update Fact Definition",
|
|
557
|
-
description: "Update a fact definition.
|
|
559
|
+
description: "Update a fact definition. The key is immutable. The type can change only while no rule references the fact; if any does, the call fails with FD-007 and reports the count. Only the fields you send are changed. System facts accept name, description, and PII only.",
|
|
558
560
|
inputSchema: {
|
|
559
561
|
factId: z4.string().uuid().describe("Fact definition ID"),
|
|
560
562
|
name: z4.string().optional().describe("Display name"),
|
|
561
563
|
description: z4.string().optional().describe("Description"),
|
|
564
|
+
type: z4.enum(VALUE_TYPES).optional().describe(
|
|
565
|
+
"Value type. Omit to leave it unchanged. Changing it fails with FD-007 while any rule references the fact."
|
|
566
|
+
),
|
|
562
567
|
isRequired: z4.boolean().optional().describe("Required flag"),
|
|
563
568
|
isPii: z4.boolean().optional().describe("PII flag \u2014 enables/disables masking (changeable even on system facts)")
|
|
564
569
|
}
|
|
@@ -569,7 +574,7 @@ function registerFactTools(server, callApi) {
|
|
|
569
574
|
"lexq_facts_delete",
|
|
570
575
|
{
|
|
571
576
|
title: "Delete Fact Definition",
|
|
572
|
-
description: "Delete a fact definition. System facts cannot be deleted.",
|
|
577
|
+
description: "Delete a fact definition. System facts cannot be deleted. Neither can a fact that any rule references: that call fails with FD-006 and reports the count. Remove the references first.",
|
|
573
578
|
inputSchema: {
|
|
574
579
|
factId: z4.string().uuid().describe("Fact definition ID")
|
|
575
580
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lexq/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.54",
|
|
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": {
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"enums:check": "node scripts/gen-enums.mjs --check",
|
|
32
32
|
"constants:check": "node scripts/check-constants.mjs",
|
|
33
33
|
"surface:check": "node scripts/check-public-surface.mjs",
|
|
34
|
+
"ab-key:check": "node scripts/check-ab-key.mjs",
|
|
34
35
|
"start": "node dist/index.js",
|
|
35
36
|
"test:decimals": "pnpm build && node tests/exact-decimals.mjs",
|
|
36
37
|
"test:fact-key": "node tests/fact-key.mjs",
|
|
@@ -159,6 +159,33 @@ lexq groups create --json '{
|
|
|
159
159
|
|
|
160
160
|
Split traffic between the current live version and a test version.
|
|
161
161
|
|
|
162
|
+
### Traffic Identity Key
|
|
163
|
+
|
|
164
|
+
**Every execution request has to carry `context.trafficKey`.** That value is what the split is
|
|
165
|
+
computed from: the server hashes it together with the group ID and assigns the request to a
|
|
166
|
+
bucket, so the same key value always reaches the same version for that group.
|
|
167
|
+
|
|
168
|
+
Send it in `context`, not in `facts`. Routing never reads `facts`.
|
|
169
|
+
|
|
170
|
+
```json
|
|
171
|
+
{
|
|
172
|
+
"facts": { "paymentAmount": 150000, "customerTier": "VIP" },
|
|
173
|
+
"context": { "trafficKey": "customer-8f3a21" }
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
If the key is missing or empty, no request is ever assigned to the test version. The test still
|
|
178
|
+
reports as running and execution logs keep accumulating, so **the failure does not surface in the
|
|
179
|
+
response** — only a server-side warning records it. This is the most common reason a test sits
|
|
180
|
+
at 0%.
|
|
181
|
+
|
|
182
|
+
Pick a value that is stable per experiment unit (a customer ID, a merchant ID, a session ID) and
|
|
183
|
+
opaque — the whole `context` object is stored on the execution ledger, so send a hash or a
|
|
184
|
+
surrogate key rather than an email address. A value that changes every request, such as a request
|
|
185
|
+
ID, scatters one user across both versions and destroys the comparison.
|
|
186
|
+
|
|
187
|
+
Batch calls read `sharedContext.trafficKey` instead, and the whole batch runs on one version.
|
|
188
|
+
|
|
162
189
|
### Start A/B Test
|
|
163
190
|
|
|
164
191
|
```bash
|
|
@@ -191,11 +218,12 @@ This reverts all traffic to the main version. The test version remains ACTIVE bu
|
|
|
191
218
|
```
|
|
192
219
|
1. Create two versions (v1 live, v2 DRAFT with changes)
|
|
193
220
|
2. Publish v2: lexq deploy publish --group-id <gid> --version-id <v2id>
|
|
194
|
-
3.
|
|
195
|
-
4.
|
|
196
|
-
5.
|
|
197
|
-
6.
|
|
198
|
-
7.
|
|
221
|
+
3. Confirm your execution requests send context.trafficKey
|
|
222
|
+
4. Start A/B: lexq groups ab-test start --group-id <gid> --version-id <v2id> --traffic-rate 10
|
|
223
|
+
5. Monitor: lexq history stats (compare metrics)
|
|
224
|
+
6. Adjust traffic gradually: 10% → 30% → 50%
|
|
225
|
+
7. Promote winner: lexq deploy live --group-id <gid> --version-id <v2id>
|
|
226
|
+
8. Stop test: lexq groups ab-test stop --group-id <gid> --force
|
|
199
227
|
```
|
|
200
228
|
|
|
201
229
|
## Common Patterns
|
|
@@ -190,6 +190,7 @@ lexq analytics dry-run --version-id <v2id> --debug --json '{
|
|
|
190
190
|
lexq deploy publish --group-id <gid> --version-id <v2id> --memo "15% discount test"
|
|
191
191
|
|
|
192
192
|
# 5. Start A/B test at 10% traffic
|
|
193
|
+
# Your execution requests must carry context.trafficKey, or the test stays at 0%.
|
|
193
194
|
lexq groups ab-test start --group-id <gid> --version-id <v2id> --traffic-rate 10
|
|
194
195
|
|
|
195
196
|
# 6. Monitor (check execution stats periodically)
|
|
@@ -141,7 +141,7 @@ Prefixes you will encounter through the CLI:
|
|
|
141
141
|
| `A-` | Auth | Invalid or missing API key |
|
|
142
142
|
| `P-` | Policy group/version | Not found, wrong lifecycle state, already live |
|
|
143
143
|
| `ACT-` | Action parameters | Missing or invalid action parameter |
|
|
144
|
-
| `FD-` | Fact definitions | Duplicate key, system fact immutable
|
|
144
|
+
| `FD-` | Fact definitions | Duplicate key, system fact immutable, fact still referenced by rules |
|
|
145
145
|
| `AN-` | Analytics | Dry-run or simulation failure |
|
|
146
146
|
| `FL-` | Failure logs | Log not found |
|
|
147
147
|
| `WH-` | Webhook subscriptions | Invalid URL, delivery failure |
|
|
@@ -60,6 +60,11 @@ Response includes:
|
|
|
60
60
|
|
|
61
61
|
**Always run this before dry-run.** Copy `exampleRequest.facts` as your starting template and fill in real values.
|
|
62
62
|
|
|
63
|
+
`exampleRequest.context` comes back empty, and it stays empty even when the group has an A/B test
|
|
64
|
+
running. Live execution requests against such a group also need `context.trafficKey` or the test
|
|
65
|
+
version receives no traffic — see the A/B testing section of the groups skill. Dry-run itself is
|
|
66
|
+
unaffected: it always evaluates the version you name, so routing never applies here.
|
|
67
|
+
|
|
63
68
|
## 2. Dry Run (Single Input)
|
|
64
69
|
|
|
65
70
|
Test a single set of facts against a version:
|