@lexq/cli 0.1.19 → 0.1.21

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/README.md CHANGED
@@ -1,13 +1,34 @@
1
1
  # LexQ CLI
2
2
 
3
- > CLI and MCP server for [LexQ](https://lexq.io) — a rule engine with built-in simulation.
4
- > Manage policies, simulate rules, and deploy from the terminal. Built for humans and AI agents.
3
+ > **The decision platform for engineering teams.**
4
+ > CLI and MCP server for [LexQ](https://lexq.io) move business rules out of
5
+ > your deploy pipeline, prove every change with simulation, and ship without
6
+ > a git push.
5
7
 
6
8
  **[Website](https://lexq.io)** · **[Docs](https://docs.lexq.io)** · **[Console](https://console.lexq.io)**
7
9
 
8
10
  [![npm](https://img.shields.io/npm/v/@lexq/cli)](https://www.npmjs.com/package/@lexq/cli)
9
11
  [![License](https://img.shields.io/github/license/lexq-io/lexq-cli)](LICENSE)
10
12
 
13
+ ---
14
+
15
+ ## Why LexQ?
16
+
17
+ Your business rules — pricing, promotions, fee logic, eligibility checks —
18
+ change often. But every change ships through the same PR → review → staging
19
+ → deploy cycle as your core application code. A one-line discount rule
20
+ takes two weeks.
21
+
22
+ This is an architectural problem, not a process problem. Business logic
23
+ that changes weekly shouldn't live in code that ships quarterly.
24
+
25
+ **LexQ separates the two.** Rules live outside your application. You change
26
+ them in a visual console or through this CLI. You prove every change with
27
+ batch simulation against real execution data. You deploy without touching
28
+ your codebase.
29
+
30
+ Every decision leaves a full audit trace.
31
+
11
32
  ## Install
12
33
 
13
34
  ```bash
@@ -55,7 +76,7 @@ lexq rules create --group-id <GROUP_ID> --version-id <VERSION_ID> --json '{
55
76
  }]
56
77
  }'
57
78
 
58
- # 6. Test
79
+ # 6. Test against your data before shipping
59
80
  lexq analytics dry-run --version-id <VERSION_ID> --debug --mock \
60
81
  --json '{"facts":{"customer_tier":"VIP","payment_amount":100000}}'
61
82
 
@@ -64,106 +85,16 @@ lexq deploy publish --group-id <GROUP_ID> --version-id <VERSION_ID> --memo "v1"
64
85
  lexq deploy live --group-id <GROUP_ID> --version-id <VERSION_ID> --memo "Initial deploy"
65
86
  ```
66
87
 
67
- ## Commands
68
-
69
- ```
70
- lexq auth login | logout | whoami
71
- lexq status API health check
72
- lexq serve Run as MCP stdio server (--mcp)
73
- lexq groups list | get | create | update | delete
74
- lexq groups ab-test start | stop | adjust
75
- lexq versions list | get | create | update | delete | clone
76
- lexq rules list | get | create | update | delete | reorder | toggle
77
- lexq facts list | create | update | delete | action-metadata
78
- lexq deploy publish | live | rollback | undeploy | history | detail | overview | deployable | diff
79
- lexq analytics dry-run | dry-run-compare | requirements
80
- lexq analytics simulation start | status | list | cancel | export
81
- lexq analytics dataset upload | template
82
- lexq history list | get | stats
83
- lexq integrations list | get | save | delete | config-spec
84
- lexq logs list | get | action | bulk-action
85
- lexq webhook-subscriptions list | get | save | delete | test
86
- ```
87
-
88
- ## Global Options
89
-
90
- | Flag | Description |
91
- |--------------------------|--------------------------------------------|
92
- | `--format <json\|table>` | Output format (default: `json`) |
93
- | `--api-key <key>` | Override stored API key |
94
- | `--base-url <url>` | Override API base URL |
95
- | `--dry-run` | Preview the HTTP request without executing |
96
- | `--verbose` | Show request/response details |
97
- | `--no-color` | Disable colored output |
98
-
99
- ## AI Agent Skills
100
-
101
- LexQ CLI ships with **AI Agent Skills** — structured documentation that AI coding agents can read to autonomously manage
102
- policies.
103
-
104
- ```
105
- skills/
106
- ├── lexq-shared/SKILL.md Core concepts, auth, workflow
107
- ├── lexq-groups/SKILL.md Policy groups, conflict resolution, A/B testing
108
- ├── lexq-rules/SKILL.md Condition syntax, action types, mutex
109
- ├── lexq-simulation/SKILL.md Dry run, batch simulation, compare
110
- ├── lexq-execution/SKILL.md Execution history, stats, failure logs,
111
- │ integrations, webhook subscriptions
112
- └── lexq-recipes/SKILL.md End-to-end recipes
113
-
114
- .claude/CLAUDE.md Claude Code project context
115
- AGENTS.md Universal agent guide (Cursor, Windsurf, Gemini CLI, Cline)
116
- CONTEXT.md Platform architecture & glossary
117
- ```
118
-
119
- ### For AI Agent Developers
120
-
121
- Skills are included in the npm package. After installing `@lexq/cli`, agents can read skills from:
122
-
123
- ```
124
- node_modules/@lexq/cli/skills/
125
- node_modules/@lexq/cli/AGENTS.md
126
- node_modules/@lexq/cli/CONTEXT.md
127
- ```
128
-
129
- Or reference them directly in your project by copying the `skills/` directory.
130
-
131
- ## Configuration
132
-
133
- Config is stored at `~/.lexq/config.json`:
134
-
135
- ```json
136
- {
137
- "apiKey": "sk_live_xxxxxxxxxxxxx",
138
- "baseUrl": "https://api.lexq.io/api/v1/partners",
139
- "format": "json"
140
- }
141
- ```
142
-
143
- ## Development
88
+ ## For AI Agents — 63 MCP Tools
144
89
 
145
- ```bash
146
- git clone https://github.com/lexq-io/lexq-cli.git
147
- cd lexq-cli
148
- pnpm install
149
- pnpm build
150
- pnpm start -- groups list
151
- ```
152
-
153
- ```bash
154
- pnpm typecheck # Type check
155
- pnpm lint # ESLint
156
- bash tests/e2e.sh # CLI E2E tests (requires API key)
157
- bash tests/test-engine-api.sh # Engine API integration tests
158
- ```
159
-
160
- ## MCP Server Mode
161
-
162
- LexQ exposes **63 policy engine tools** via MCP. Two connection methods:
90
+ LexQ is designed to be AI-native. The entire policy engine API — all 63 tools
91
+ is exposed via Model Context Protocol. Claude, Cursor, and other MCP-compatible
92
+ agents can create, simulate, and deploy rules autonomously, with human approval
93
+ before production.
163
94
 
164
95
  ### Claude.ai (Cloud — no install)
165
96
 
166
- 1. Go to **Settings → Connectors → Add Custom Integration**
97
+ 1. **Settings → Connectors → Add Custom Integration**
167
98
  2. Enter: `https://mcp.lexq.io`
168
99
  3. Sign in with your LexQ account and select an API key
169
100
  4. Done — 63 tools available in every conversation
@@ -182,7 +113,7 @@ For any MCP client that supports remote servers:
182
113
  }
183
114
  ```
184
115
 
185
- OAuth 2.1 authentication is required. Your AI client will handle authorization automatically.
116
+ OAuth 2.1 authentication is handled automatically by your client.
186
117
 
187
118
  ### Local (stdio)
188
119
 
@@ -192,9 +123,7 @@ Run LexQ CLI as a local MCP server:
192
123
  lexq serve --mcp
193
124
  ```
194
125
 
195
- This starts a stdio MCP server exposing 63 tools — the full LexQ API — to any MCP-compatible client.
196
-
197
- ### Claude Desktop
126
+ #### Claude Desktop
198
127
 
199
128
  Add to `claude_desktop_config.json`:
200
129
 
@@ -214,7 +143,7 @@ Add to `claude_desktop_config.json`:
214
143
  }
215
144
  ```
216
145
 
217
- ### VS Code / Cursor
146
+ #### VS Code / Cursor
218
147
 
219
148
  `.vscode/mcp.json`:
220
149
 
@@ -234,7 +163,96 @@ Add to `claude_desktop_config.json`:
234
163
  }
235
164
  ```
236
165
 
237
- > **Prerequisite:** `lexq auth login` must have been run once to store an API key in `~/.lexq/config.json`.
166
+ > **Prerequisite:** `lexq auth login` must have been run once to store an
167
+ > API key in `~/.lexq/config.json`.
168
+
169
+ ## AI Agent Skills
170
+
171
+ LexQ CLI ships with **AI Agent Skills** — structured documentation agents
172
+ read to understand *how* to use the tools, not just *what* they are.
173
+
174
+ ```
175
+ skills/
176
+ ├── lexq-shared/SKILL.md Core concepts, auth, workflow
177
+ ├── lexq-groups/SKILL.md Policy groups, conflict resolution, A/B testing
178
+ ├── lexq-rules/SKILL.md Condition syntax, action types, mutex
179
+ ├── lexq-simulation/SKILL.md Dry run, batch simulation, compare
180
+ ├── lexq-execution/SKILL.md Execution history, stats, failure logs
181
+ └── lexq-recipes/SKILL.md End-to-end recipes
182
+
183
+ .claude/CLAUDE.md Claude Code project context
184
+ AGENTS.md Universal agent guide (Cursor, Windsurf, Gemini CLI, Cline)
185
+ CONTEXT.md Platform architecture & glossary
186
+ ```
187
+
188
+ After installing `@lexq/cli`, agents can read skills from:
189
+
190
+ ```
191
+ node_modules/@lexq/cli/skills/
192
+ node_modules/@lexq/cli/AGENTS.md
193
+ node_modules/@lexq/cli/CONTEXT.md
194
+ ```
195
+
196
+ ## Commands
197
+
198
+ ```
199
+ lexq auth login | logout | whoami
200
+ lexq status API health check
201
+ lexq serve Run as MCP stdio server (--mcp)
202
+ lexq groups list | get | create | update | delete
203
+ lexq groups ab-test start | stop | adjust
204
+ lexq versions list | get | create | update | delete | clone
205
+ lexq rules list | get | create | update | delete | reorder | toggle
206
+ lexq facts list | create | update | delete | action-metadata
207
+ lexq deploy publish | live | rollback | undeploy | history | detail | overview | deployable | diff
208
+ lexq analytics dry-run | dry-run-compare | requirements
209
+ lexq analytics simulation start | status | list | cancel | export
210
+ lexq analytics dataset upload | template
211
+ lexq history list | get | stats
212
+ lexq integrations list | get | save | delete | config-spec
213
+ lexq logs list | get | action | bulk-action
214
+ lexq webhook-subscriptions list | get | save | delete | test
215
+ ```
216
+
217
+ ## Global Options
218
+
219
+ | Flag | Description |
220
+ |--------------------------|--------------------------------------------|
221
+ | `--format <json\|table>` | Output format (default: `json`) |
222
+ | `--api-key <key>` | Override stored API key |
223
+ | `--base-url <url>` | Override API base URL |
224
+ | `--dry-run` | Preview the HTTP request without executing |
225
+ | `--verbose` | Show request/response details |
226
+ | `--no-color` | Disable colored output |
227
+
228
+ ## Configuration
229
+
230
+ Config is stored at `~/.lexq/config.json`:
231
+
232
+ ```json
233
+ {
234
+ "apiKey": "YOUR_API_KEY",
235
+ "baseUrl": "https://api.lexq.io/api/v1/partners",
236
+ "format": "json"
237
+ }
238
+ ```
239
+
240
+ ## Development
241
+
242
+ ```bash
243
+ git clone https://github.com/lexq-io/lexq-cli.git
244
+ cd lexq-cli
245
+ pnpm install
246
+ pnpm build
247
+ pnpm start -- groups list
248
+ ```
249
+
250
+ ```bash
251
+ pnpm typecheck # Type check
252
+ pnpm lint # ESLint
253
+ bash tests/e2e.sh # CLI E2E tests (requires API key)
254
+ bash tests/test-engine-api.sh # Engine API integration tests
255
+ ```
238
256
 
239
257
  ## License
240
258
 
package/dist/index.js CHANGED
@@ -222,20 +222,25 @@ function registerAuthCommands(program) {
222
222
  `
223
223
  ).action(async () => {
224
224
  try {
225
+ const globalOpts = program.opts();
225
226
  const config = loadConfig();
226
- if (!config.apiKey) {
227
+ const apiKey = globalOpts.apiKey ?? config.apiKey;
228
+ const baseUrl = globalOpts.baseUrl ?? config.baseUrl;
229
+ if (!apiKey) {
227
230
  console.error('Not authenticated. Run "lexq auth login" first.');
228
231
  process.exit(1);
229
232
  }
230
233
  const info = await apiRequest("GET", "whoami", {
231
- apiKey: config.apiKey,
232
- baseUrl: config.baseUrl
234
+ apiKey,
235
+ baseUrl,
236
+ dryRun: globalOpts.dryRun,
237
+ verbose: globalOpts.verbose
233
238
  });
234
- const masked = config.apiKey.length > 8 ? config.apiKey.substring(0, 4) + "****" + config.apiKey.substring(config.apiKey.length - 4) : "****";
239
+ const masked = apiKey.length > 8 ? apiKey.substring(0, 4) + "****" + apiKey.substring(apiKey.length - 4) : "****";
235
240
  printJson({
236
241
  ...info,
237
242
  apiKey: masked,
238
- baseUrl: config.baseUrl
243
+ baseUrl
239
244
  });
240
245
  } catch (error) {
241
246
  printError(error);
@@ -871,7 +876,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
871
876
  "valueType": "STRING"
872
877
  },
873
878
  "actions": [
874
- { "type": "DISCOUNT", "parameters": { "method": "PERCENTAGE", "rate": 20, "refVar": "payment_amount" } }
879
+ { "type": "MUTATE_FACT", "parameters": { "refVar": "payment_amount", "operator": "SUB", "method": "PERCENTAGE", "rate": 20 } }
875
880
  ]
876
881
  }'
877
882
 
@@ -880,7 +885,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
880
885
  LESS_THAN, LESS_THAN_OR_EQUAL, CONTAINS, IN, NOT_IN
881
886
 
882
887
  Action Types:
883
- DISCOUNT, POINT, COUPON_ISSUE, BLOCK, NOTIFICATION, WEBHOOK, SET_FACT, ADD_TAG
888
+ MUTATE_FACT, INCREMENT_FACT, EMIT_EVENT, BLOCK, EMIT_NOTIFICATION, EMIT_WEBHOOK, SET_FACT, ADD_TAG
884
889
 
885
890
  Value Types: STRING, NUMBER, BOOLEAN, LIST_STRING, LIST_NUMBER
886
891
 
@@ -920,7 +925,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
920
925
  $ lexq rules update --group-id <gid> --version-id <vid> --id <rid> --json '{
921
926
  "name": "VIP 25% Discount",
922
927
  "actions": [
923
- { "type": "DISCOUNT", "parameters": { "method": "PERCENTAGE", "rate": 25, "refVar": "payment_amount" } }
928
+ { "type": "MUTATE_FACT", "parameters": { "refVar": "payment_amount", "operator": "SUB", "method": "PERCENTAGE", "rate": 25 } }
924
929
  ]
925
930
  }'
926
931
  `
@@ -3119,18 +3124,20 @@ function registerRuleTools(server, callApi) {
3119
3124
  Actions: [{ type, parameters }]
3120
3125
 
3121
3126
  Action parameter schemas:
3122
- - DISCOUNT: { refVar: string, method: "PERCENTAGE"|"AMOUNT", rate?: number (when PERCENTAGE), value?: number (when AMOUNT) }
3123
- - POINT: { refVar: string, targetVar: string, method: "PERCENTAGE"|"AMOUNT", rate?: number (when PERCENTAGE), value?: number (when AMOUNT), integrationId: uuid }
3124
- - COUPON_ISSUE: { couponId: string, integrationId: uuid }
3127
+ - MUTATE_FACT: { refVar: string, operator: "ASSIGN"|"ADD"|"SUB"|"MUL"|"DIV", method: "PERCENTAGE"|"AMOUNT", rate?: number (when PERCENTAGE), value?: number (when AMOUNT), rounding?: RoundingOption } Constraints: DIV + PERCENTAGE is invalid (use MUL with rate/100 inverse). DIV + AMOUNT requires value !== 0.
3128
+ - INCREMENT_FACT: { targetVar: string, method: "PERCENTAGE"|"AMOUNT", refVar?: string (required when PERCENTAGE), rate?: number (when PERCENTAGE), value?: number (when AMOUNT), rounding?: RoundingOption } Note: external system call (e.g. point system sync) is NOT a primitive responsibility. Compose [INCREMENT_FACT, EMIT_EVENT] chain instead.
3129
+ - EMIT_EVENT: { integrationId: uuid, eventPayload: object (Map<string,unknown>, ≥1 entry) } eventPayload is passed through to the integration provider as-is. Domain-specific keys (couponId, ticketId, etc.) are routed by the provider, not validated by the engine.
3125
3130
  - BLOCK: { reason: string }
3126
- - NOTIFICATION: { channel: "SMS"|"EMAIL"|"PUSH", targetVar: string, templateId: string, integrationId: uuid }
3127
- - WEBHOOK: { url: string, method: "POST", payloadTemplate?: object } payloadTemplate is optional. Without it, all facts are sent as-is. With it, the object is sent as the HTTP body with {{variables}} replaced at execution time. Variables: {{fact.xxx}}, {{output.xxx}}, {{timestamp}}, {{ruleName}}, {{groupName}}, {{versionNo}}, {{xxx}} (shorthand).
3131
+ - EMIT_NOTIFICATION: { integrationId: uuid, targetVar: string, notificationPayload: object (Map<string,unknown>, ≥1 entry) } targetVar identifies the recipient fact (e.g. phone_number / email / device_token). notificationPayload (channel, templateId, body, variables, etc.) is passed through to the provider.
3132
+ - EMIT_WEBHOOK: { url: string, method: "POST", payloadTemplate?: object } payloadTemplate is optional. Without it, all facts are sent as-is. With it, the object is sent as the HTTP body with {{variables}} replaced at execution time. Variables: {{fact.xxx}}, {{output.xxx}}, {{timestamp}}, {{ruleName}}, {{groupName}}, {{versionNo}}, {{xxx}} (shorthand).
3128
3133
  Platform examples:
3129
3134
  Slack: { "text": "Rule {{ruleName}} fired — {{fact.customer_tier}}" }
3130
3135
  Discord: { "content": "Rule {{ruleName}} fired — {{fact.customer_tier}}" }
3131
3136
  Generic: { "event": "rule_matched", "rule": "{{ruleName}}", "amount": "{{output.payment_amount}}" }
3132
3137
  - SET_FACT: { key: string, value: string|number|boolean }
3133
3138
  - ADD_TAG: { tag: string, targetVar: string }
3139
+
3140
+ RoundingOption (optional, MUTATE_FACT / INCREMENT_FACT only): { scale: integer (0..16), 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).
3134
3141
  `,
3135
3142
  inputSchema: {
3136
3143
  groupId: z3.string().uuid().describe("Policy group ID"),
@@ -3437,9 +3444,16 @@ function registerAnalyticsTools(server, callApi) {
3437
3444
  title: "Dry Run",
3438
3445
  description: dedent14`
3439
3446
  Execute a single dry run against a version. Tests how rules evaluate given input facts without side effects.
3447
+
3448
+ Returns:
3449
+ inputFacts — normalized input facts
3450
+ mutatedFacts — input facts changed by rule actions (e.g. DISCOUNT mutates payment_amount)
3451
+ generatedVariables — new variables created by rules (e.g. last_discount_amount)
3452
+ executionTraces — per-rule match status
3453
+ decisionTraces — per-rule decision (SELECTED / BLOCKED_MUTEX / etc.)
3454
+
3440
3455
  Example input: { "facts": { "payment_amount": 100000, "customer_tier": "VIP" } }
3441
- Always dry-run before publishing to validate rule behavior.
3442
- `,
3456
+ Always dry-run before publishing to validate rule behavior.`,
3443
3457
  inputSchema: {
3444
3458
  versionId: z6.string().uuid().describe("Policy version ID to test against"),
3445
3459
  facts: z6.string().describe('JSON string of facts object, e.g. {"payment_amount":100000}'),
@@ -3458,7 +3472,13 @@ function registerAnalyticsTools(server, callApi) {
3458
3472
  "lexq_dry_run_compare",
3459
3473
  {
3460
3474
  title: "Dry Run Compare",
3461
- description: "Compare dry run results between two versions using the same input facts. Useful for validating changes.",
3475
+ description: dedent14`
3476
+ Compare dry run results between two versions using the same input facts. Useful for validating changes.
3477
+
3478
+ Returns:
3479
+ resultA / resultB — full DryRunResponse for each version
3480
+ diff.mutatedDiff — changes in mutatedFacts between A and B (key → {before, after})
3481
+ diff.generatedDiff — changes in generatedVariables between A and B`,
3462
3482
  inputSchema: {
3463
3483
  versionIdA: z6.string().uuid().describe("Baseline version ID"),
3464
3484
  versionIdB: z6.string().uuid().describe("Candidate version ID"),
@@ -293,18 +293,20 @@ function registerRuleTools(server, callApi) {
293
293
  Actions: [{ type, parameters }]
294
294
 
295
295
  Action parameter schemas:
296
- - DISCOUNT: { refVar: string, method: "PERCENTAGE"|"AMOUNT", rate?: number (when PERCENTAGE), value?: number (when AMOUNT) }
297
- - POINT: { refVar: string, targetVar: string, method: "PERCENTAGE"|"AMOUNT", rate?: number (when PERCENTAGE), value?: number (when AMOUNT), integrationId: uuid }
298
- - COUPON_ISSUE: { couponId: string, integrationId: uuid }
296
+ - MUTATE_FACT: { refVar: string, operator: "ASSIGN"|"ADD"|"SUB"|"MUL"|"DIV", method: "PERCENTAGE"|"AMOUNT", rate?: number (when PERCENTAGE), value?: number (when AMOUNT), rounding?: RoundingOption } Constraints: DIV + PERCENTAGE is invalid (use MUL with rate/100 inverse). DIV + AMOUNT requires value !== 0.
297
+ - INCREMENT_FACT: { targetVar: string, method: "PERCENTAGE"|"AMOUNT", refVar?: string (required when PERCENTAGE), rate?: number (when PERCENTAGE), value?: number (when AMOUNT), rounding?: RoundingOption } Note: external system call (e.g. point system sync) is NOT a primitive responsibility. Compose [INCREMENT_FACT, EMIT_EVENT] chain instead.
298
+ - EMIT_EVENT: { integrationId: uuid, eventPayload: object (Map<string,unknown>, ≥1 entry) } eventPayload is passed through to the integration provider as-is. Domain-specific keys (couponId, ticketId, etc.) are routed by the provider, not validated by the engine.
299
299
  - BLOCK: { reason: string }
300
- - NOTIFICATION: { channel: "SMS"|"EMAIL"|"PUSH", targetVar: string, templateId: string, integrationId: uuid }
301
- - WEBHOOK: { url: string, method: "POST", payloadTemplate?: object } payloadTemplate is optional. Without it, all facts are sent as-is. With it, the object is sent as the HTTP body with {{variables}} replaced at execution time. Variables: {{fact.xxx}}, {{output.xxx}}, {{timestamp}}, {{ruleName}}, {{groupName}}, {{versionNo}}, {{xxx}} (shorthand).
300
+ - EMIT_NOTIFICATION: { integrationId: uuid, targetVar: string, notificationPayload: object (Map<string,unknown>, ≥1 entry) } targetVar identifies the recipient fact (e.g. phone_number / email / device_token). notificationPayload (channel, templateId, body, variables, etc.) is passed through to the provider.
301
+ - EMIT_WEBHOOK: { url: string, method: "POST", payloadTemplate?: object } payloadTemplate is optional. Without it, all facts are sent as-is. With it, the object is sent as the HTTP body with {{variables}} replaced at execution time. Variables: {{fact.xxx}}, {{output.xxx}}, {{timestamp}}, {{ruleName}}, {{groupName}}, {{versionNo}}, {{xxx}} (shorthand).
302
302
  Platform examples:
303
303
  Slack: { "text": "Rule {{ruleName}} fired — {{fact.customer_tier}}" }
304
304
  Discord: { "content": "Rule {{ruleName}} fired — {{fact.customer_tier}}" }
305
305
  Generic: { "event": "rule_matched", "rule": "{{ruleName}}", "amount": "{{output.payment_amount}}" }
306
306
  - SET_FACT: { key: string, value: string|number|boolean }
307
307
  - ADD_TAG: { tag: string, targetVar: string }
308
+
309
+ RoundingOption (optional, MUTATE_FACT / INCREMENT_FACT only): { scale: integer (0..16), 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).
308
310
  `,
309
311
  inputSchema: {
310
312
  groupId: z3.string().uuid().describe("Policy group ID"),
@@ -611,9 +613,16 @@ function registerAnalyticsTools(server, callApi) {
611
613
  title: "Dry Run",
612
614
  description: dedent2`
613
615
  Execute a single dry run against a version. Tests how rules evaluate given input facts without side effects.
616
+
617
+ Returns:
618
+ inputFacts — normalized input facts
619
+ mutatedFacts — input facts changed by rule actions (e.g. DISCOUNT mutates payment_amount)
620
+ generatedVariables — new variables created by rules (e.g. last_discount_amount)
621
+ executionTraces — per-rule match status
622
+ decisionTraces — per-rule decision (SELECTED / BLOCKED_MUTEX / etc.)
623
+
614
624
  Example input: { "facts": { "payment_amount": 100000, "customer_tier": "VIP" } }
615
- Always dry-run before publishing to validate rule behavior.
616
- `,
625
+ Always dry-run before publishing to validate rule behavior.`,
617
626
  inputSchema: {
618
627
  versionId: z6.string().uuid().describe("Policy version ID to test against"),
619
628
  facts: z6.string().describe('JSON string of facts object, e.g. {"payment_amount":100000}'),
@@ -632,7 +641,13 @@ function registerAnalyticsTools(server, callApi) {
632
641
  "lexq_dry_run_compare",
633
642
  {
634
643
  title: "Dry Run Compare",
635
- description: "Compare dry run results between two versions using the same input facts. Useful for validating changes.",
644
+ description: dedent2`
645
+ Compare dry run results between two versions using the same input facts. Useful for validating changes.
646
+
647
+ Returns:
648
+ resultA / resultB — full DryRunResponse for each version
649
+ diff.mutatedDiff — changes in mutatedFacts between A and B (key → {before, after})
650
+ diff.generatedDiff — changes in generatedVariables between A and B`,
636
651
  inputSchema: {
637
652
  versionIdA: z6.string().uuid().describe("Baseline version ID"),
638
653
  versionIdB: z6.string().uuid().describe("Candidate version ID"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lexq/cli",
3
- "version": "0.1.19",
3
+ "version": "0.1.21",
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": {