@lexq/cli 1.1.7 → 2.0.0
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 +49 -20
- package/dist/mcp/register.js +17 -10
- package/package.json +8 -3
- package/skills/lexq-recipes/SKILL.md +11 -4
package/dist/index.js
CHANGED
|
@@ -1773,7 +1773,13 @@ function registerDeployCommands(program) {
|
|
|
1773
1773
|
process.exit(1);
|
|
1774
1774
|
}
|
|
1775
1775
|
});
|
|
1776
|
-
deploy.command("rollback").description("Rollback to the previous deployed version").requiredOption("--group-id <groupId>", "Policy group ID").requiredOption("--memo <memo>", "Rollback reason").option(
|
|
1776
|
+
deploy.command("rollback").description("Rollback to the previous deployed version").requiredOption("--group-id <groupId>", "Policy group ID").requiredOption("--memo <memo>", "Rollback reason").option(
|
|
1777
|
+
"--expected-current-version-id <versionId>",
|
|
1778
|
+
"currentVersionId from your last read of the group"
|
|
1779
|
+
).option(
|
|
1780
|
+
"--expected-current-deployment-id <deploymentId>",
|
|
1781
|
+
"currentDeploymentId from your last read of the group"
|
|
1782
|
+
).option("--force", "Skip confirmation prompt").addHelpText(
|
|
1777
1783
|
"after",
|
|
1778
1784
|
dedent7`
|
|
1779
1785
|
|
|
@@ -1781,8 +1787,20 @@ function registerDeployCommands(program) {
|
|
|
1781
1787
|
Only available if the previous version is still ACTIVE and its effective end
|
|
1782
1788
|
has not passed (P-058).
|
|
1783
1789
|
|
|
1790
|
+
Run lexq groups get first: currentVersionName is live now and
|
|
1791
|
+
previousVersionName is what a rollback restores. Pass the group's
|
|
1792
|
+
currentVersionId and currentDeploymentId with the two --expected-* options.
|
|
1793
|
+
The server checks only the values you pass. If anyone deployed, rolled back
|
|
1794
|
+
or undeployed in between, the rollback is rejected with 409 and nothing
|
|
1795
|
+
changes: P-062 if another version or no version is live, P-063 if the same
|
|
1796
|
+
version is live but from a newer deployment. Read the group again before
|
|
1797
|
+
retrying.
|
|
1798
|
+
|
|
1784
1799
|
Example:
|
|
1785
|
-
$ lexq
|
|
1800
|
+
$ lexq groups get --id <gid>
|
|
1801
|
+
$ lexq deploy rollback --group-id <gid> --memo "High error rate" \
|
|
1802
|
+
--expected-current-version-id <currentVersionId> \
|
|
1803
|
+
--expected-current-deployment-id <currentDeploymentId> --force
|
|
1786
1804
|
`
|
|
1787
1805
|
).action(async (opts) => {
|
|
1788
1806
|
try {
|
|
@@ -1802,7 +1820,11 @@ function registerDeployCommands(program) {
|
|
|
1802
1820
|
baseUrl: globalOpts.baseUrl,
|
|
1803
1821
|
dryRun: globalOpts.dryRun,
|
|
1804
1822
|
verbose: globalOpts.verbose,
|
|
1805
|
-
body: {
|
|
1823
|
+
body: {
|
|
1824
|
+
memo: opts.memo,
|
|
1825
|
+
expectedCurrentVersionId: opts.expectedCurrentVersionId,
|
|
1826
|
+
expectedCurrentDeploymentId: opts.expectedCurrentDeploymentId
|
|
1827
|
+
}
|
|
1806
1828
|
});
|
|
1807
1829
|
console.log(`\u2713 Group ${opts.groupId} rolled back.`);
|
|
1808
1830
|
} catch (error) {
|
|
@@ -2159,7 +2181,7 @@ function registerAnalyticsCommands(program) {
|
|
|
2159
2181
|
dedent8`
|
|
2160
2182
|
|
|
2161
2183
|
Examples:
|
|
2162
|
-
$ lexq analytics dry-run --version-id <vid> --debug
|
|
2184
|
+
$ lexq analytics dry-run --version-id <vid> --debug \
|
|
2163
2185
|
--json '{"facts": {"paymentAmount": 150000, "customerTier": "VIP"}}'
|
|
2164
2186
|
|
|
2165
2187
|
$ lexq analytics dry-run --version-id <vid> --file test-input.json
|
|
@@ -3526,7 +3548,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
3526
3548
|
|
|
3527
3549
|
// src/commands/serve.ts
|
|
3528
3550
|
import "commander";
|
|
3529
|
-
import
|
|
3551
|
+
import dedent18 from "dedent";
|
|
3530
3552
|
|
|
3531
3553
|
// src/mcp/server.ts
|
|
3532
3554
|
import { readFileSync as readFileSync3 } from "fs";
|
|
@@ -3665,7 +3687,7 @@ function registerGroupTools(server, callApi) {
|
|
|
3665
3687
|
{
|
|
3666
3688
|
title: "Get Policy Group",
|
|
3667
3689
|
annotations: { readOnlyHint: true },
|
|
3668
|
-
description: "Get a single policy group by ID.",
|
|
3690
|
+
description: "Get a single policy group by ID. Its currentVersionId and currentDeploymentId are what lexq_deploy_rollback expects, and previousVersionName is the version a rollback restores.",
|
|
3669
3691
|
inputSchema: z2.object({
|
|
3670
3692
|
groupId: z2.string().uuid().describe("Policy group ID")
|
|
3671
3693
|
})
|
|
@@ -4246,6 +4268,7 @@ function registerFactTools(server, callApi) {
|
|
|
4246
4268
|
}
|
|
4247
4269
|
|
|
4248
4270
|
// src/mcp/tools/deploy.ts
|
|
4271
|
+
import dedent16 from "dedent";
|
|
4249
4272
|
import { z as z6 } from "zod";
|
|
4250
4273
|
function registerDeployTools(server, callApi) {
|
|
4251
4274
|
server.registerTool(
|
|
@@ -4283,14 +4306,20 @@ function registerDeployTools(server, callApi) {
|
|
|
4283
4306
|
{
|
|
4284
4307
|
title: "Rollback Deployment",
|
|
4285
4308
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
4286
|
-
description:
|
|
4309
|
+
description: dedent16`
|
|
4310
|
+
Roll the group back to the version that was live before the current one. Call lexq_groups_get first: currentVersionName is live now, previousVersionName is what a rollback restores, and a rollback is rejected while canRollback is false. A rollback needs a previous version that is still ACTIVE and whose effective end has not passed (P-058).
|
|
4311
|
+
|
|
4312
|
+
Pass the currentVersionId and currentDeploymentId from that call as expectedCurrentVersionId and expectedCurrentDeploymentId. If anyone deployed, rolled back or undeployed in between, the rollback is rejected with 409 and nothing changes: P-062 if another version or no version is live, P-063 if the same version is live but from a newer deployment. Read the group again and check with the user before retrying.
|
|
4313
|
+
`,
|
|
4287
4314
|
inputSchema: z6.object({
|
|
4288
4315
|
groupId: z6.string().uuid().describe("Policy group ID"),
|
|
4316
|
+
expectedCurrentVersionId: z6.string().uuid().describe("currentVersionId from your last read of the group"),
|
|
4317
|
+
expectedCurrentDeploymentId: z6.string().uuid().describe("currentDeploymentId from your last read of the group"),
|
|
4289
4318
|
memo: z6.string().min(1).describe("Rollback reason (required)")
|
|
4290
4319
|
})
|
|
4291
4320
|
},
|
|
4292
|
-
async ({ groupId, memo }) => callApi("POST", `policy-groups/${groupId}/rollback`, {
|
|
4293
|
-
body: { memo }
|
|
4321
|
+
async ({ groupId, expectedCurrentVersionId, expectedCurrentDeploymentId, memo }) => callApi("POST", `policy-groups/${groupId}/rollback`, {
|
|
4322
|
+
body: { memo, expectedCurrentVersionId, expectedCurrentDeploymentId }
|
|
4294
4323
|
})
|
|
4295
4324
|
);
|
|
4296
4325
|
server.registerTool(
|
|
@@ -4390,7 +4419,7 @@ function registerDeployTools(server, callApi) {
|
|
|
4390
4419
|
{
|
|
4391
4420
|
title: "Deployment Overview",
|
|
4392
4421
|
annotations: { readOnlyHint: true },
|
|
4393
|
-
description: "Show current deployment status of all groups \u2014 which version is live, last deployment type, and deployer.",
|
|
4422
|
+
description: "Show current deployment status of all groups \u2014 which version is live, last deployment type, and deployer. Each group also carries currentDeploymentId, previousVersionName (the version a rollback restores) and canRollback, as in lexq_groups_get.",
|
|
4394
4423
|
inputSchema: z6.object({})
|
|
4395
4424
|
},
|
|
4396
4425
|
async () => callApi("GET", "deployments/overview")
|
|
@@ -4426,14 +4455,14 @@ function registerDeployTools(server, callApi) {
|
|
|
4426
4455
|
|
|
4427
4456
|
// src/mcp/tools/analytics.ts
|
|
4428
4457
|
import { z as z7 } from "zod";
|
|
4429
|
-
import
|
|
4458
|
+
import dedent17 from "dedent";
|
|
4430
4459
|
function registerAnalyticsTools(server, callApi) {
|
|
4431
4460
|
server.registerTool(
|
|
4432
4461
|
"lexq_dry_run",
|
|
4433
4462
|
{
|
|
4434
4463
|
title: "Dry Run",
|
|
4435
4464
|
annotations: { readOnlyHint: true },
|
|
4436
|
-
description:
|
|
4465
|
+
description: dedent17`
|
|
4437
4466
|
Execute a single dry run against a version. Tests how rules evaluate given input facts without side effects.
|
|
4438
4467
|
|
|
4439
4468
|
Returns:
|
|
@@ -4463,7 +4492,7 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
4463
4492
|
{
|
|
4464
4493
|
title: "Dry Run Compare",
|
|
4465
4494
|
annotations: { readOnlyHint: true },
|
|
4466
|
-
description:
|
|
4495
|
+
description: dedent17`
|
|
4467
4496
|
Compare dry run results between two versions using the same input facts. Useful for validating changes.
|
|
4468
4497
|
|
|
4469
4498
|
Returns:
|
|
@@ -4501,7 +4530,7 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
4501
4530
|
{
|
|
4502
4531
|
title: "Start Simulation",
|
|
4503
4532
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
4504
|
-
description:
|
|
4533
|
+
description: dedent17`
|
|
4505
4534
|
Start an Impact Simulation against historical, uploaded, or inline data.
|
|
4506
4535
|
|
|
4507
4536
|
dataset.type and dataset.source are BOTH required, and must be paired:
|
|
@@ -4606,7 +4635,7 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
4606
4635
|
{
|
|
4607
4636
|
title: "Upload Dataset",
|
|
4608
4637
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
4609
|
-
description:
|
|
4638
|
+
description: dedent17`
|
|
4610
4639
|
Upload inline CSV or JSON content as a simulation dataset.
|
|
4611
4640
|
The content is uploaded to S3 and a path is returned in the "path" field.
|
|
4612
4641
|
|
|
@@ -5159,7 +5188,7 @@ function startMcpServer() {
|
|
|
5159
5188
|
function registerServeCommand(program) {
|
|
5160
5189
|
program.command("serve").description("Start LexQ as a server for AI agent integrations").option("--mcp", "Start as MCP (Model Context Protocol) server over stdio").addHelpText(
|
|
5161
5190
|
"after",
|
|
5162
|
-
|
|
5191
|
+
dedent18`
|
|
5163
5192
|
|
|
5164
5193
|
Example:
|
|
5165
5194
|
$ lexq serve --mcp
|
|
@@ -5190,11 +5219,11 @@ function registerServeCommand(program) {
|
|
|
5190
5219
|
|
|
5191
5220
|
// src/commands/domain-templates.ts
|
|
5192
5221
|
import "commander";
|
|
5193
|
-
import
|
|
5222
|
+
import dedent19 from "dedent";
|
|
5194
5223
|
function registerDomainTemplateCommands(program) {
|
|
5195
5224
|
const templates = program.command("domain-templates").description("Browse and apply domain templates").addHelpText(
|
|
5196
5225
|
"after",
|
|
5197
|
-
|
|
5226
|
+
dedent19`
|
|
5198
5227
|
|
|
5199
5228
|
A domain template is an industry-specific starter pack of fact
|
|
5200
5229
|
definitions and sample rules. Applying one provisions a ready-to-use
|
|
@@ -5242,7 +5271,7 @@ function registerDomainTemplateCommands(program) {
|
|
|
5242
5271
|
});
|
|
5243
5272
|
templates.command("preview").description("Preview what a domain template provisions").requiredOption("--template <key>", "Domain template key (e.g. ECOMMERCE)").addHelpText(
|
|
5244
5273
|
"after",
|
|
5245
|
-
|
|
5274
|
+
dedent19`
|
|
5246
5275
|
|
|
5247
5276
|
Read-only dry run — shows the fact definitions and sample rules the
|
|
5248
5277
|
template will create. Nothing is provisioned.
|
|
@@ -5271,7 +5300,7 @@ function registerDomainTemplateCommands(program) {
|
|
|
5271
5300
|
});
|
|
5272
5301
|
templates.command("apply").description("Apply a domain template to the current tenant").requiredOption("--template <key>", "Domain template key (e.g. ECOMMERCE)").option("--name <n>", "Custom name for the policy group that gets created").option("--force", "Skip confirmation prompt").addHelpText(
|
|
5273
5302
|
"after",
|
|
5274
|
-
|
|
5303
|
+
dedent19`
|
|
5275
5304
|
|
|
5276
5305
|
Creates the template's fact definitions and a new DRAFT policy group
|
|
5277
5306
|
populated with its sample rules. A fact that already exists with the
|
package/dist/mcp/register.js
CHANGED
|
@@ -84,7 +84,7 @@ function registerGroupTools(server, callApi) {
|
|
|
84
84
|
{
|
|
85
85
|
title: "Get Policy Group",
|
|
86
86
|
annotations: { readOnlyHint: true },
|
|
87
|
-
description: "Get a single policy group by ID.",
|
|
87
|
+
description: "Get a single policy group by ID. Its currentVersionId and currentDeploymentId are what lexq_deploy_rollback expects, and previousVersionName is the version a rollback restores.",
|
|
88
88
|
inputSchema: z2.object({
|
|
89
89
|
groupId: z2.string().uuid().describe("Policy group ID")
|
|
90
90
|
})
|
|
@@ -778,6 +778,7 @@ function registerFactTools(server, callApi) {
|
|
|
778
778
|
}
|
|
779
779
|
|
|
780
780
|
// src/mcp/tools/deploy.ts
|
|
781
|
+
import dedent2 from "dedent";
|
|
781
782
|
import { z as z6 } from "zod";
|
|
782
783
|
function registerDeployTools(server, callApi) {
|
|
783
784
|
server.registerTool(
|
|
@@ -815,14 +816,20 @@ function registerDeployTools(server, callApi) {
|
|
|
815
816
|
{
|
|
816
817
|
title: "Rollback Deployment",
|
|
817
818
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
818
|
-
description:
|
|
819
|
+
description: dedent2`
|
|
820
|
+
Roll the group back to the version that was live before the current one. Call lexq_groups_get first: currentVersionName is live now, previousVersionName is what a rollback restores, and a rollback is rejected while canRollback is false. A rollback needs a previous version that is still ACTIVE and whose effective end has not passed (P-058).
|
|
821
|
+
|
|
822
|
+
Pass the currentVersionId and currentDeploymentId from that call as expectedCurrentVersionId and expectedCurrentDeploymentId. If anyone deployed, rolled back or undeployed in between, the rollback is rejected with 409 and nothing changes: P-062 if another version or no version is live, P-063 if the same version is live but from a newer deployment. Read the group again and check with the user before retrying.
|
|
823
|
+
`,
|
|
819
824
|
inputSchema: z6.object({
|
|
820
825
|
groupId: z6.string().uuid().describe("Policy group ID"),
|
|
826
|
+
expectedCurrentVersionId: z6.string().uuid().describe("currentVersionId from your last read of the group"),
|
|
827
|
+
expectedCurrentDeploymentId: z6.string().uuid().describe("currentDeploymentId from your last read of the group"),
|
|
821
828
|
memo: z6.string().min(1).describe("Rollback reason (required)")
|
|
822
829
|
})
|
|
823
830
|
},
|
|
824
|
-
async ({ groupId, memo }) => callApi("POST", `policy-groups/${groupId}/rollback`, {
|
|
825
|
-
body: { memo }
|
|
831
|
+
async ({ groupId, expectedCurrentVersionId, expectedCurrentDeploymentId, memo }) => callApi("POST", `policy-groups/${groupId}/rollback`, {
|
|
832
|
+
body: { memo, expectedCurrentVersionId, expectedCurrentDeploymentId }
|
|
826
833
|
})
|
|
827
834
|
);
|
|
828
835
|
server.registerTool(
|
|
@@ -922,7 +929,7 @@ function registerDeployTools(server, callApi) {
|
|
|
922
929
|
{
|
|
923
930
|
title: "Deployment Overview",
|
|
924
931
|
annotations: { readOnlyHint: true },
|
|
925
|
-
description: "Show current deployment status of all groups \u2014 which version is live, last deployment type, and deployer.",
|
|
932
|
+
description: "Show current deployment status of all groups \u2014 which version is live, last deployment type, and deployer. Each group also carries currentDeploymentId, previousVersionName (the version a rollback restores) and canRollback, as in lexq_groups_get.",
|
|
926
933
|
inputSchema: z6.object({})
|
|
927
934
|
},
|
|
928
935
|
async () => callApi("GET", "deployments/overview")
|
|
@@ -958,14 +965,14 @@ function registerDeployTools(server, callApi) {
|
|
|
958
965
|
|
|
959
966
|
// src/mcp/tools/analytics.ts
|
|
960
967
|
import { z as z7 } from "zod";
|
|
961
|
-
import
|
|
968
|
+
import dedent3 from "dedent";
|
|
962
969
|
function registerAnalyticsTools(server, callApi) {
|
|
963
970
|
server.registerTool(
|
|
964
971
|
"lexq_dry_run",
|
|
965
972
|
{
|
|
966
973
|
title: "Dry Run",
|
|
967
974
|
annotations: { readOnlyHint: true },
|
|
968
|
-
description:
|
|
975
|
+
description: dedent3`
|
|
969
976
|
Execute a single dry run against a version. Tests how rules evaluate given input facts without side effects.
|
|
970
977
|
|
|
971
978
|
Returns:
|
|
@@ -995,7 +1002,7 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
995
1002
|
{
|
|
996
1003
|
title: "Dry Run Compare",
|
|
997
1004
|
annotations: { readOnlyHint: true },
|
|
998
|
-
description:
|
|
1005
|
+
description: dedent3`
|
|
999
1006
|
Compare dry run results between two versions using the same input facts. Useful for validating changes.
|
|
1000
1007
|
|
|
1001
1008
|
Returns:
|
|
@@ -1033,7 +1040,7 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
1033
1040
|
{
|
|
1034
1041
|
title: "Start Simulation",
|
|
1035
1042
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
1036
|
-
description:
|
|
1043
|
+
description: dedent3`
|
|
1037
1044
|
Start an Impact Simulation against historical, uploaded, or inline data.
|
|
1038
1045
|
|
|
1039
1046
|
dataset.type and dataset.source are BOTH required, and must be paired:
|
|
@@ -1138,7 +1145,7 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
1138
1145
|
{
|
|
1139
1146
|
title: "Upload Dataset",
|
|
1140
1147
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
1141
|
-
description:
|
|
1148
|
+
description: dedent3`
|
|
1142
1149
|
Upload inline CSV or JSON content as a simulation dataset.
|
|
1143
1150
|
The content is uploaded to S3 and a path is returned in the "path" field.
|
|
1144
1151
|
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lexq/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"mcpName": "io.lexq/lexq",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "The Decision Operations Platform for engineering teams",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
8
|
"lexq": "./dist/index.js"
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"test:command-options": "node tests/command-options.mjs",
|
|
45
45
|
"test:value-domain": "pnpm build && node tests/value-domain.mjs",
|
|
46
46
|
"test:fact-update": "pnpm build && node tests/fact-update.mjs",
|
|
47
|
+
"test:rollback": "pnpm build && node tests/rollback.mjs",
|
|
47
48
|
"prepublishOnly": "pnpm build",
|
|
48
49
|
"knip": "knip",
|
|
49
50
|
"format": "prettier --write \"src/**/*.ts\"",
|
|
@@ -57,7 +58,11 @@
|
|
|
57
58
|
"ai-agent",
|
|
58
59
|
"agent-skills",
|
|
59
60
|
"simulation",
|
|
60
|
-
"business-rules"
|
|
61
|
+
"business-rules",
|
|
62
|
+
"developer-tools",
|
|
63
|
+
"decision-management",
|
|
64
|
+
"mcp",
|
|
65
|
+
"mcp-server"
|
|
61
66
|
],
|
|
62
67
|
"author": "LexQ, LLC",
|
|
63
68
|
"license": "Apache-2.0",
|
|
@@ -352,17 +352,24 @@ lexq rules create --group-id <gid> --version-id <vid> --json '{
|
|
|
352
352
|
**Goal:** Something went wrong in production — revert to the previous version.
|
|
353
353
|
|
|
354
354
|
```bash
|
|
355
|
-
# 1.
|
|
356
|
-
|
|
355
|
+
# 1. Read the group: currentVersionName is live now, previousVersionName is what a
|
|
356
|
+
# rollback restores, and a rollback is rejected while canRollback is false
|
|
357
|
+
lexq groups get --id <gid>
|
|
357
358
|
|
|
358
|
-
# 2.
|
|
359
|
-
lexq deploy rollback --group-id <gid> --memo "Reverting due to increased error rate"
|
|
359
|
+
# 2. Roll back with the IDs from step 1
|
|
360
|
+
lexq deploy rollback --group-id <gid> --memo "Reverting due to increased error rate" \
|
|
361
|
+
--expected-current-version-id <currentVersionId> \
|
|
362
|
+
--expected-current-deployment-id <currentDeploymentId>
|
|
360
363
|
|
|
361
364
|
# 3. Verify
|
|
362
365
|
lexq deploy overview
|
|
363
366
|
lexq history stats
|
|
364
367
|
```
|
|
365
368
|
|
|
369
|
+
If anyone deployed, rolled back or undeployed after step 1, step 2 is rejected with `409` and
|
|
370
|
+
nothing changes: `P-062` if another version or no version is live, `P-063` if the same version is
|
|
371
|
+
live but from a newer deployment. Start again from step 1.
|
|
372
|
+
|
|
366
373
|
---
|
|
367
374
|
|
|
368
375
|
## Recipe 7: Monitoring + Failure Triage
|