statelyai 0.15.1 → 0.17.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.
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,178 @@
1
+ import { createRequire, registerHooks } from "node:module";
2
+ import { readFile, realpath } from "node:fs/promises";
3
+ import { dirname, resolve } from "node:path";
4
+ import { pathToFileURL } from "node:url";
5
+ import { parentPort, workerData } from "node:worker_threads";
6
+ import ts from "typescript";
7
+ import { register } from "tsx/esm/api";
8
+ import { createXStateSourceCoverageGraph, extractXStateSourceCoverage, getXStateSourceCoverageEventCases, instrumentXStateSourceCoverage } from "@statelyai/sdk/source-coverage";
9
+
10
+ //#region src/sourceCoverageRunner.ts
11
+ /** Executes trusted source with ordinary local permissions; called in a worker. */
12
+ async function runSourceCoverageFile(options) {
13
+ const fileName = await realpath(resolve(options.sourceFile));
14
+ const source = await readFile(fileName, "utf8");
15
+ const manifest = await extractXStateSourceCoverage(source, { fileName });
16
+ if (!options.execute) return {
17
+ schemaVersion: 1,
18
+ mode: "extract",
19
+ status: manifest.complete ? "complete" : "incomplete",
20
+ manifest,
21
+ exitCode: manifest.complete ? 0 : 2
22
+ };
23
+ if (!manifest.complete) throw Object.assign(/* @__PURE__ */ new Error("Source extraction is incomplete."), { diagnostics: manifest.diagnostics });
24
+ const projectRequire = createRequire(fileName);
25
+ const load = async (name) => import(pathToFileURL(projectRequire.resolve(name)).href);
26
+ const graph = await load("@statelyai/graph");
27
+ const testing = await load("@xstate/test");
28
+ if (typeof graph.getSourceCoverageWitnesses !== "function" || typeof testing.createSourceCoverageSession !== "function") throw new Error("Source coverage requires @statelyai/graph with source-coverage planning and @xstate/test with createSourceCoverageSession. Upgrade these project dependencies.");
29
+ const runtimeName = options.runtime ?? "xstate";
30
+ const runtime = await load(runtimeName);
31
+ const runtimePackage = projectRequire(`${runtimeName}/package.json`);
32
+ const runtimeId = `${runtimePackage.name}@${runtimePackage.version}`;
33
+ if (typeof runtime.getMicrosteps !== "function") throw new Error("The selected runtime does not expose microstep execution.");
34
+ const instrumented = await instrumentXStateSourceCoverage(source, { fileName });
35
+ const transpiled = ts.transpileModule(instrumented.source, {
36
+ fileName,
37
+ compilerOptions: {
38
+ target: ts.ScriptTarget.ES2022,
39
+ module: ts.ModuleKind.ESNext
40
+ },
41
+ reportDiagnostics: true
42
+ });
43
+ if (transpiled.diagnostics?.some((diagnostic) => diagnostic.category === ts.DiagnosticCategory.Error)) throw new Error("The instrumented module could not be compiled.");
44
+ const sourceUrl = pathToFileURL(fileName).href;
45
+ const unregister = register({ tsconfig: ts.findConfigFile(dirname(fileName), ts.sys.fileExists) ?? false });
46
+ const hooks = registerHooks({ load(url, context, nextLoad) {
47
+ if (url === sourceUrl) return {
48
+ format: "module",
49
+ source: transpiled.outputText,
50
+ shortCircuit: true
51
+ };
52
+ return nextLoad(url, context);
53
+ } });
54
+ try {
55
+ const module = await import(sourceUrl);
56
+ const selected = module[options.exportName ?? "machine"];
57
+ const control = module[instrumented.controlExport];
58
+ if (!selected || !control) throw new Error("Expected the selected machine export and source coverage control.");
59
+ const input = options.inputFile ? JSON.parse(await readFile(options.inputFile, "utf8")) : void 0;
60
+ const machineIndex = options.machineIndex ?? 0;
61
+ if (!Number.isSafeInteger(machineIndex) || machineIndex < 0 || machineIndex >= manifest.machineCount) throw new Error("machineIndex is outside the extracted machine range.");
62
+ const selectedManifest = {
63
+ ...manifest,
64
+ obligations: manifest.obligations.filter((obligation) => obligation.machineIndex === machineIndex)
65
+ };
66
+ const createSession = (input) => {
67
+ const machine = typeof selected === "function" ? selected(input) : selected;
68
+ if (!machine.eventSchema || !machine.sources) throw new Error("Source coverage execution currently requires an XState v6 machine.");
69
+ return testing.createSourceCoverageSession(machine, control, {
70
+ input,
71
+ runtimeId,
72
+ validateEvent: async (event, snapshot) => {
73
+ const candidate = event;
74
+ if (typeof candidate.type === "string" && candidate.type.startsWith("xstate.") && typeof runtime.getNextTransitions === "function" && runtime.getNextTransitions(snapshot).some((transition) => transition.eventType === candidate.type && Object.entries(transition.matches ?? {}).every(([key, value]) => Object.is(candidate[key], value)))) return;
75
+ const validation = await machine.eventSchema["~standard"].validate(event);
76
+ if (validation.issues) throw new Error(`Event validation failed: ${JSON.stringify(validation.issues)}`);
77
+ }
78
+ });
79
+ };
80
+ let result;
81
+ if (options.replayFile) {
82
+ const saved = JSON.parse(await readFile(options.replayFile, "utf8"));
83
+ const witnesses = Array.isArray(saved) ? saved : saved.witnesses;
84
+ if (!Array.isArray(witnesses)) throw new Error("Replay file must contain a witness array or a report with witnesses.");
85
+ const actual = [];
86
+ for (const witness of witnesses) actual.push((await graph.getSourceCoverageReplay(manifest, witness, createSession)).witness);
87
+ result = {
88
+ report: graph.getSourceCoverageReport(selectedManifest, actual),
89
+ witnesses: actual,
90
+ exploration: { replayed: actual.length }
91
+ };
92
+ } else {
93
+ if (!options.casesFile) throw new Error("Execution requires --cases or --replay.");
94
+ const cases = JSON.parse(await readFile(options.casesFile, "utf8"));
95
+ if (!Array.isArray(cases) || cases.some((event) => !event || typeof event.type !== "string")) throw new Error("Cases must be an array of event objects with string types.");
96
+ const suggest = options.suggestFile ? (await import(pathToFileURL(resolve(options.suggestFile)).href)).default : void 0;
97
+ if (options.suggestFile && typeof suggest !== "function") throw new Error("Suggestion module must default-export a function.");
98
+ result = await graph.getSourceCoverageWitnesses({
99
+ manifest,
100
+ machineIndex,
101
+ input,
102
+ createSession,
103
+ events: cases,
104
+ maxStates: options.maxStates,
105
+ maxDepth: options.maxDepth,
106
+ maxExecutions: options.maxExecutions,
107
+ suggestEvents: async (args) => {
108
+ const boundaries = getXStateSourceCoverageEventCases({
109
+ ...selectedManifest,
110
+ obligations: args.uncovered
111
+ }, args.state.snapshot, cases);
112
+ const suggestions = await suggest?.({
113
+ ...args,
114
+ sourceHash: manifest.source.hash
115
+ }) ?? [];
116
+ if (!Array.isArray(suggestions) || suggestions.some((event) => !event || typeof event.type !== "string")) throw new Error("Suggested cases must be event objects.");
117
+ return [...boundaries, ...suggestions];
118
+ }
119
+ });
120
+ if (options.shrink !== false) {
121
+ const minimized = [];
122
+ for (const witness of result.witnesses) minimized.push((await graph.getMinimizedSourceCoverageWitness(manifest, witness, createSession, { maxExecutions: 100 })).witness);
123
+ result.witnesses = graph.getReducedSourceCoverageWitnesses(selectedManifest, minimized);
124
+ }
125
+ const verified = [];
126
+ for (const witness of result.witnesses) verified.push((await graph.getSourceCoverageReplay(manifest, witness, createSession)).witness);
127
+ result.witnesses = verified;
128
+ result.report = graph.getSourceCoverageReport(selectedManifest, verified);
129
+ }
130
+ const projection = await createXStateSourceCoverageGraph(source, result.report, {
131
+ fileName,
132
+ machineIndex,
133
+ profile: "xstate-v6-alpha"
134
+ });
135
+ const machine = typeof selected === "function" ? selected(input) : selected;
136
+ const schemas = Object.entries(machine.schemas?.events ?? {});
137
+ const typeOnlyEvents = schemas.filter(([, schema]) => schema?.["~standard"]?.vendor === "xstate.types").map(([type]) => type);
138
+ const runtimeSchemaEvents = schemas.filter(([, schema]) => schema?.["~standard"]?.vendor && schema["~standard"].vendor !== "xstate.types").map(([type]) => type);
139
+ const unconstrainedEvents = [...new Set(result.witnesses.flatMap((witness) => witness.events.map((event) => event.type)))].filter((type) => !schemas.some(([key]) => key === type));
140
+ return {
141
+ schemaVersion: 1,
142
+ mode: options.replayFile ? "replay" : "execute",
143
+ status: result.report.complete ? "complete" : "incomplete",
144
+ machineIndex,
145
+ runtimeId,
146
+ effectsExecuted: false,
147
+ payloadValidation: {
148
+ typeOnlyEvents,
149
+ runtimeSchemaEvents,
150
+ unconstrainedEvents,
151
+ modeledRuntimeEvents: result.witnesses.flatMap((witness) => witness.events.filter((event) => event.type.startsWith("xstate.")))
152
+ },
153
+ manifest: selectedManifest,
154
+ ...result,
155
+ graph: projection.graph,
156
+ exitCode: result.report.complete ? 0 : 1
157
+ };
158
+ } finally {
159
+ hooks.deregister();
160
+ await unregister();
161
+ }
162
+ }
163
+
164
+ //#endregion
165
+ //#region src/coverageWorker.ts
166
+ if (parentPort) runSourceCoverageFile(workerData).then((result) => parentPort.postMessage({
167
+ ok: true,
168
+ result
169
+ }), (error) => parentPort.postMessage({
170
+ ok: false,
171
+ error: {
172
+ message: error instanceof Error ? error.message : String(error),
173
+ diagnostics: error?.diagnostics
174
+ }
175
+ }));
176
+
177
+ //#endregion
178
+ export { };
package/dist/index.d.mts CHANGED
@@ -69,10 +69,10 @@ declare const behaviorCiResultsSchema: z.ZodArray<z.ZodObject<{
69
69
  file: z.ZodString;
70
70
  fullName: z.ZodString;
71
71
  outcome: z.ZodEnum<{
72
- error: "error";
73
72
  passed: "passed";
74
73
  failed: "failed";
75
74
  skipped: "skipped";
75
+ error: "error";
76
76
  }>;
77
77
  detail: z.ZodString;
78
78
  }, z.core.$strict>>;
@@ -129,6 +129,53 @@ declare class BehaviorCalibrateCommand extends Command {
129
129
  run(): Promise<void>;
130
130
  }
131
131
  //#endregion
132
+ //#region src/sourceCoverageRunner.d.ts
133
+ interface SourceCoverageRunOptions {
134
+ sourceFile: string;
135
+ execute?: boolean;
136
+ casesFile?: string;
137
+ inputFile?: string;
138
+ replayFile?: string;
139
+ runtime?: string;
140
+ exportName?: string;
141
+ machineIndex?: number;
142
+ maxStates?: number;
143
+ maxDepth?: number;
144
+ maxExecutions?: number;
145
+ shrink?: boolean;
146
+ /** Trusted callback module supplying additional event cases from coverage gaps. */
147
+ suggestFile?: string;
148
+ }
149
+ //#endregion
150
+ //#region src/sourceCoverageCommand.d.ts
151
+ declare function runSourceCoverageWorker(options: SourceCoverageRunOptions, timeoutMs?: number, workerUrl?: URL): Promise<any>;
152
+ declare class SourceCoverageCommand extends Command {
153
+ static enableJsonFlag: boolean;
154
+ static summary: string;
155
+ static description: string;
156
+ static args: {
157
+ source: _oclif_core_interfaces0.Arg<string, Record<string, unknown>>;
158
+ };
159
+ static flags: {
160
+ execute: _oclif_core_interfaces0.BooleanFlag<boolean>;
161
+ cases: _oclif_core_interfaces0.OptionFlag<string | undefined, _oclif_core_interfaces0.CustomOptions>;
162
+ input: _oclif_core_interfaces0.OptionFlag<string | undefined, _oclif_core_interfaces0.CustomOptions>;
163
+ replay: _oclif_core_interfaces0.OptionFlag<string | undefined, _oclif_core_interfaces0.CustomOptions>;
164
+ runtime: _oclif_core_interfaces0.OptionFlag<string, _oclif_core_interfaces0.CustomOptions>;
165
+ export: _oclif_core_interfaces0.OptionFlag<string, _oclif_core_interfaces0.CustomOptions>;
166
+ "machine-index": _oclif_core_interfaces0.OptionFlag<number, _oclif_core_interfaces0.CustomOptions>;
167
+ "max-states": _oclif_core_interfaces0.OptionFlag<number, _oclif_core_interfaces0.CustomOptions>;
168
+ "max-depth": _oclif_core_interfaces0.OptionFlag<number, _oclif_core_interfaces0.CustomOptions>;
169
+ "max-executions": _oclif_core_interfaces0.OptionFlag<number, _oclif_core_interfaces0.CustomOptions>;
170
+ timeout: _oclif_core_interfaces0.OptionFlag<number, _oclif_core_interfaces0.CustomOptions>;
171
+ shrink: _oclif_core_interfaces0.BooleanFlag<boolean>;
172
+ suggest: _oclif_core_interfaces0.OptionFlag<string | undefined, _oclif_core_interfaces0.CustomOptions>;
173
+ report: _oclif_core_interfaces0.OptionFlag<string, _oclif_core_interfaces0.CustomOptions>;
174
+ graph: _oclif_core_interfaces0.OptionFlag<string | undefined, _oclif_core_interfaces0.CustomOptions>;
175
+ };
176
+ run(): Promise<void>;
177
+ }
178
+ //#endregion
132
179
  //#region src/testCommand.d.ts
133
180
  declare class TestCommand extends Command {
134
181
  static enableJsonFlag: boolean;
@@ -321,7 +368,7 @@ declare function createStatelyProjectConfig(options: {
321
368
  defaultXStateVersion?: number;
322
369
  }): StatelyProjectConfig;
323
370
  //#endregion
324
- //#region ../../node_modules/.pnpm/@statelyai+graph@2.1.0_@statelyai+layout@0.4.3_cytoscape@3.34.0_d3-force@3.0.0_d3-hiera_2afdfa88fae2fa10712f99651d0f58d6/node_modules/@statelyai/graph/dist/types-BAEQTwK_.d.mts
371
+ //#region ../../node_modules/.pnpm/@statelyai+graph@2.1.0_@statelyai+layout@0.5.0_cytoscape@3.34.0_d3-force@3.0.0_d3-hiera_b5a11f1847a44c5e54d10b16dab0c7b9/node_modules/@statelyai/graph/dist/types-BAEQTwK_.d.mts
325
372
  //#region src/types.d.ts
326
373
  /**
327
374
  * Directedness of a graph or an individual edge.
@@ -885,6 +932,7 @@ declare const COMMANDS: {
885
932
  'behavior:check': typeof BehaviorCheckCommand;
886
933
  'behavior:adopt': typeof BehaviorAdoptCommand;
887
934
  test: typeof TestCommand;
935
+ coverage: typeof SourceCoverageCommand;
888
936
  verify: typeof VerifyCommand;
889
937
  plan: typeof PlanCommand;
890
938
  diff: typeof DiffCommand;
@@ -910,8 +958,8 @@ declare const verificationRunReportSchema: z.ZodObject<{
910
958
  version: z.ZodLiteral<1>;
911
959
  suite: z.ZodString;
912
960
  status: z.ZodEnum<{
913
- failed: "failed";
914
961
  passed: "passed";
962
+ failed: "failed";
915
963
  }>;
916
964
  verdict: z.ZodEnum<{
917
965
  inconclusive: "inconclusive";
@@ -1016,8 +1064,8 @@ declare const verificationRunReportSchema: z.ZodObject<{
1016
1064
  type: z.ZodLiteral<"path-result">;
1017
1065
  id: z.ZodString;
1018
1066
  status: z.ZodEnum<{
1019
- failed: "failed";
1020
1067
  passed: "passed";
1068
+ failed: "failed";
1021
1069
  }>;
1022
1070
  witnessedStates: z.ZodArray<z.ZodString>;
1023
1071
  witnessedTransitions: z.ZodArray<z.ZodString>;
@@ -1028,8 +1076,8 @@ declare const verificationRunReportSchema: z.ZodObject<{
1028
1076
  state: z.ZodString;
1029
1077
  event: z.ZodString;
1030
1078
  status: z.ZodEnum<{
1031
- failed: "failed";
1032
1079
  passed: "passed";
1080
+ failed: "failed";
1033
1081
  }>;
1034
1082
  }, z.core.$strip>>>;
1035
1083
  divergences: z.ZodArray<z.ZodObject<{
@@ -1073,8 +1121,8 @@ declare const verificationRunReportSchema: z.ZodObject<{
1073
1121
  predicateResults: z.ZodArray<z.ZodObject<{
1074
1122
  state: z.ZodString;
1075
1123
  status: z.ZodEnum<{
1076
- failed: "failed";
1077
1124
  passed: "passed";
1125
+ failed: "failed";
1078
1126
  }>;
1079
1127
  signals: z.ZodArray<z.ZodString>;
1080
1128
  message: z.ZodOptional<z.ZodString>;
@@ -1088,8 +1136,8 @@ declare const verificationRunReportSchema: z.ZodObject<{
1088
1136
  predicateResults: z.ZodArray<z.ZodObject<{
1089
1137
  state: z.ZodString;
1090
1138
  status: z.ZodEnum<{
1091
- failed: "failed";
1092
1139
  passed: "passed";
1140
+ failed: "failed";
1093
1141
  }>;
1094
1142
  signals: z.ZodArray<z.ZodString>;
1095
1143
  message: z.ZodOptional<z.ZodString>;
@@ -1159,8 +1207,8 @@ declare const verificationRunReportSchema: z.ZodObject<{
1159
1207
  predicateResults: z.ZodArray<z.ZodObject<{
1160
1208
  state: z.ZodString;
1161
1209
  status: z.ZodEnum<{
1162
- failed: "failed";
1163
1210
  passed: "passed";
1211
+ failed: "failed";
1164
1212
  }>;
1165
1213
  signals: z.ZodArray<z.ZodString>;
1166
1214
  message: z.ZodOptional<z.ZodString>;
@@ -1174,8 +1222,8 @@ declare const verificationRunReportSchema: z.ZodObject<{
1174
1222
  predicateResults: z.ZodArray<z.ZodObject<{
1175
1223
  state: z.ZodString;
1176
1224
  status: z.ZodEnum<{
1177
- failed: "failed";
1178
1225
  passed: "passed";
1226
+ failed: "failed";
1179
1227
  }>;
1180
1228
  signals: z.ZodArray<z.ZodString>;
1181
1229
  message: z.ZodOptional<z.ZodString>;
@@ -1229,8 +1277,8 @@ declare const verificationRequestSchema: z.ZodObject<{
1229
1277
  version: z.ZodLiteral<1>;
1230
1278
  suite: z.ZodString;
1231
1279
  status: z.ZodEnum<{
1232
- failed: "failed";
1233
1280
  passed: "passed";
1281
+ failed: "failed";
1234
1282
  }>;
1235
1283
  verdict: z.ZodEnum<{
1236
1284
  inconclusive: "inconclusive";
@@ -1335,8 +1383,8 @@ declare const verificationRequestSchema: z.ZodObject<{
1335
1383
  type: z.ZodLiteral<"path-result">;
1336
1384
  id: z.ZodString;
1337
1385
  status: z.ZodEnum<{
1338
- failed: "failed";
1339
1386
  passed: "passed";
1387
+ failed: "failed";
1340
1388
  }>;
1341
1389
  witnessedStates: z.ZodArray<z.ZodString>;
1342
1390
  witnessedTransitions: z.ZodArray<z.ZodString>;
@@ -1347,8 +1395,8 @@ declare const verificationRequestSchema: z.ZodObject<{
1347
1395
  state: z.ZodString;
1348
1396
  event: z.ZodString;
1349
1397
  status: z.ZodEnum<{
1350
- failed: "failed";
1351
1398
  passed: "passed";
1399
+ failed: "failed";
1352
1400
  }>;
1353
1401
  }, z.core.$strip>>>;
1354
1402
  divergences: z.ZodArray<z.ZodObject<{
@@ -1392,8 +1440,8 @@ declare const verificationRequestSchema: z.ZodObject<{
1392
1440
  predicateResults: z.ZodArray<z.ZodObject<{
1393
1441
  state: z.ZodString;
1394
1442
  status: z.ZodEnum<{
1395
- failed: "failed";
1396
1443
  passed: "passed";
1444
+ failed: "failed";
1397
1445
  }>;
1398
1446
  signals: z.ZodArray<z.ZodString>;
1399
1447
  message: z.ZodOptional<z.ZodString>;
@@ -1407,8 +1455,8 @@ declare const verificationRequestSchema: z.ZodObject<{
1407
1455
  predicateResults: z.ZodArray<z.ZodObject<{
1408
1456
  state: z.ZodString;
1409
1457
  status: z.ZodEnum<{
1410
- failed: "failed";
1411
1458
  passed: "passed";
1459
+ failed: "failed";
1412
1460
  }>;
1413
1461
  signals: z.ZodArray<z.ZodString>;
1414
1462
  message: z.ZodOptional<z.ZodString>;
@@ -1478,8 +1526,8 @@ declare const verificationRequestSchema: z.ZodObject<{
1478
1526
  predicateResults: z.ZodArray<z.ZodObject<{
1479
1527
  state: z.ZodString;
1480
1528
  status: z.ZodEnum<{
1481
- failed: "failed";
1482
1529
  passed: "passed";
1530
+ failed: "failed";
1483
1531
  }>;
1484
1532
  signals: z.ZodArray<z.ZodString>;
1485
1533
  message: z.ZodOptional<z.ZodString>;
@@ -1493,8 +1541,8 @@ declare const verificationRequestSchema: z.ZodObject<{
1493
1541
  predicateResults: z.ZodArray<z.ZodObject<{
1494
1542
  state: z.ZodString;
1495
1543
  status: z.ZodEnum<{
1496
- failed: "failed";
1497
1544
  passed: "passed";
1545
+ failed: "failed";
1498
1546
  }>;
1499
1547
  signals: z.ZodArray<z.ZodString>;
1500
1548
  message: z.ZodOptional<z.ZodString>;
@@ -1822,4 +1870,4 @@ declare function verifyProject(options?: VerifyProjectOptions): Promise<{
1822
1870
  /** One case per recorded path/probe. Unattributed divergences remain separate cases. */
1823
1871
  declare function formatVerificationJUnit(report: VerificationRunReport): string;
1824
1872
  //#endregion
1825
- export { ApiKeyResolution, type BehaviorCiAdapter, type BehaviorCiConfig, type BehaviorCiResult, COMMANDS, CredentialStatus, DiscoveredMachine, InitProjectOptions, InitProjectResult, type LinkProjectOptions, LinkedPullTarget, OAuthLoginDiscovery, ProjectMachineStatus, ProjectStatusMachine, ProjectStatusResult, PushCandidateClassification, RemoteProjectMachineTarget, ResolveApiKeyOptions, ResolvedConfiguredPullTargets, ScanProjectSourcesOptions, type VerificationExecutor, type VerificationRequest, type VerificationRunReport, type VerifyProjectOptions, adoptBehavior, behaviorCiConfigSchema, behaviorCiResultsSchema, briefReport, buildOAuthLoginStart, calibrateBehavior, changesReport, checkBehavior, classifyPushCandidates, createStatelyProjectConfig, discoverLinkedPullTargets, discoverMachinesInSource, discoverOAuthLogin, discoverRemoteProjectMachineTargets, formatDiscoveredMachines, formatMissingNewMachineDirMessage, formatPlanSummary, formatProjectStatus, formatStoredCredentialSource, formatVerificationJUnit, getEnvApiKey, getEnvCredential, inferInitProjectName, initProject, isFileGitDirty, linkProject, pendingReport, readGraphFile, resolveApiKey, resolveConfiguredPullTargets, resolveCredentialStatus, resolveOpenRootDir, resolveProjectStatus, resolveVerificationRequest, resourceId, run, scanProjectMachines, scanProjectSources, verifyProject, verifyReport };
1873
+ export { ApiKeyResolution, type BehaviorCiAdapter, type BehaviorCiConfig, type BehaviorCiResult, COMMANDS, CredentialStatus, DiscoveredMachine, InitProjectOptions, InitProjectResult, type LinkProjectOptions, LinkedPullTarget, OAuthLoginDiscovery, ProjectMachineStatus, ProjectStatusMachine, ProjectStatusResult, PushCandidateClassification, RemoteProjectMachineTarget, ResolveApiKeyOptions, ResolvedConfiguredPullTargets, ScanProjectSourcesOptions, SourceCoverageCommand, type SourceCoverageRunOptions, type VerificationExecutor, type VerificationRequest, type VerificationRunReport, type VerifyProjectOptions, adoptBehavior, behaviorCiConfigSchema, behaviorCiResultsSchema, briefReport, buildOAuthLoginStart, calibrateBehavior, changesReport, checkBehavior, classifyPushCandidates, createStatelyProjectConfig, discoverLinkedPullTargets, discoverMachinesInSource, discoverOAuthLogin, discoverRemoteProjectMachineTargets, formatDiscoveredMachines, formatMissingNewMachineDirMessage, formatPlanSummary, formatProjectStatus, formatStoredCredentialSource, formatVerificationJUnit, getEnvApiKey, getEnvCredential, inferInitProjectName, initProject, isFileGitDirty, linkProject, pendingReport, readGraphFile, resolveApiKey, resolveConfiguredPullTargets, resolveCredentialStatus, resolveOpenRootDir, resolveProjectStatus, resolveVerificationRequest, resourceId, run, runSourceCoverageWorker, scanProjectMachines, scanProjectSources, verifyProject, verifyReport };
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { A as formatVerificationJUnit, B as calibrateBehavior, C as run, D as resourceId, E as linkProject, F as readGraphFile, I as verifyReport, L as adoptBehavior, M as briefReport, N as changesReport, O as resolveVerificationRequest, P as pendingReport, R as behaviorCiConfigSchema, S as resolveProjectStatus, T as scanProjectSources, V as checkBehavior, _ as isFileGitDirty, a as discoverMachinesInSource, b as resolveCredentialStatus, c as formatDiscoveredMachines, d as formatProjectStatus, f as formatStoredCredentialSource, g as initProject, h as inferInitProjectName, i as discoverLinkedPullTargets, j as createStatelyProjectConfig, k as verifyProject, l as formatMissingNewMachineDirMessage, m as getEnvCredential, n as buildOAuthLoginStart, o as discoverOAuthLogin, p as getEnvApiKey, r as classifyPushCandidates, s as discoverRemoteProjectMachineTargets, t as COMMANDS, u as formatPlanSummary, v as resolveApiKey, w as scanProjectMachines, x as resolveOpenRootDir, y as resolveConfiguredPullTargets, z as behaviorCiResultsSchema } from "./cli-CE7KhcOJ.mjs";
1
+ import { A as formatVerificationJUnit, B as behaviorCiConfigSchema, C as run, D as resourceId, E as linkProject, F as readGraphFile, H as calibrateBehavior, I as verifyReport, L as SourceCoverageCommand, M as briefReport, N as changesReport, O as resolveVerificationRequest, P as pendingReport, R as runSourceCoverageWorker, S as resolveProjectStatus, T as scanProjectSources, U as checkBehavior, V as behaviorCiResultsSchema, _ as isFileGitDirty, a as discoverMachinesInSource, b as resolveCredentialStatus, c as formatDiscoveredMachines, d as formatProjectStatus, f as formatStoredCredentialSource, g as initProject, h as inferInitProjectName, i as discoverLinkedPullTargets, j as createStatelyProjectConfig, k as verifyProject, l as formatMissingNewMachineDirMessage, m as getEnvCredential, n as buildOAuthLoginStart, o as discoverOAuthLogin, p as getEnvApiKey, r as classifyPushCandidates, s as discoverRemoteProjectMachineTargets, t as COMMANDS, u as formatPlanSummary, v as resolveApiKey, w as scanProjectMachines, x as resolveOpenRootDir, y as resolveConfiguredPullTargets, z as adoptBehavior } from "./cli-BBG--n3O.mjs";
2
2
 
3
- export { COMMANDS, adoptBehavior, behaviorCiConfigSchema, behaviorCiResultsSchema, briefReport, buildOAuthLoginStart, calibrateBehavior, changesReport, checkBehavior, classifyPushCandidates, createStatelyProjectConfig, discoverLinkedPullTargets, discoverMachinesInSource, discoverOAuthLogin, discoverRemoteProjectMachineTargets, formatDiscoveredMachines, formatMissingNewMachineDirMessage, formatPlanSummary, formatProjectStatus, formatStoredCredentialSource, formatVerificationJUnit, getEnvApiKey, getEnvCredential, inferInitProjectName, initProject, isFileGitDirty, linkProject, pendingReport, readGraphFile, resolveApiKey, resolveConfiguredPullTargets, resolveCredentialStatus, resolveOpenRootDir, resolveProjectStatus, resolveVerificationRequest, resourceId, run, scanProjectMachines, scanProjectSources, verifyProject, verifyReport };
3
+ export { COMMANDS, SourceCoverageCommand, adoptBehavior, behaviorCiConfigSchema, behaviorCiResultsSchema, briefReport, buildOAuthLoginStart, calibrateBehavior, changesReport, checkBehavior, classifyPushCandidates, createStatelyProjectConfig, discoverLinkedPullTargets, discoverMachinesInSource, discoverOAuthLogin, discoverRemoteProjectMachineTargets, formatDiscoveredMachines, formatMissingNewMachineDirMessage, formatPlanSummary, formatProjectStatus, formatStoredCredentialSource, formatVerificationJUnit, getEnvApiKey, getEnvCredential, inferInitProjectName, initProject, isFileGitDirty, linkProject, pendingReport, readGraphFile, resolveApiKey, resolveConfiguredPullTargets, resolveCredentialStatus, resolveOpenRootDir, resolveProjectStatus, resolveVerificationRequest, resourceId, run, runSourceCoverageWorker, scanProjectMachines, scanProjectSources, verifyProject, verifyReport };
@@ -0,0 +1,25 @@
1
+ ---
2
+ name: setup-behavior-ci
3
+ description: Set up deterministic Stately behavior CI from existing Vitest tests or authored executable model-based campaigns, with reviewed requirements, calibrated mappings, real application adapters and immutable evidence history.
4
+ ---
5
+
6
+ Set up the selected feature from real source and authoritative intent. CI runs the existing CLI and tests without an LLM. Author the model and bindings for the user; do not ask them to maintain hashes.
7
+
8
+ Choose the mode before setup. Preserve the existing selected-Vitest mode when connecting current tests to an estimated model. For an executable model-based campaign, read [executable mode](references/executable.md), author the model, generators, real adapter and requirement assertions, then apply the shared calibration/adoption rules below. Do not treat estimated graph coverage as executed requirement evidence.
9
+
10
+ This skill ships with `statelyai` at `dist/skills/setup-behavior-ci/SKILL.md`. Resolve its exported `statelyai/skills/setup-behavior-ci/SKILL.md` path from a local install; copy the complete skill directory into the target agent's skill location when installation is requested. Its relative assets/references remain usable outside this monorepo. Repository `.agents/skills/setup-behavior-ci` is the sole maintained source; the package build copies it deterministically.
11
+
12
+ For selected-Vitest mode:
13
+
14
+ 1. Discover the feature's existing test files, native runner command/configuration, source dependencies and authoritative requirements. Run the actual bounded Vitest selection once with `--reporter=json --outputFile=<fresh temporary file>`. Read native `testResults[].name` and `assertionResults[].fullName`; copy exact titles into mappings. Map every case in the selected native command, including existing public tests. Compare the complete `(file, fullName)` inventory against config mappings and require exact equality before proceeding. Label ancillary observed smoke checks as observations rather than invented requirements. Preserve existing behavioral assertions. Don't substitute toy tests or invent reporter titles.
15
+ 2. Read [the contract](references/contract.md). Generate a short `spec.json` with meaningful stable requirement/check IDs, acceptance criteria, source authority/citations, and `retire: []`. Requirements inferred from implementation must be labeled inference. Estimate the behavior as XGraph JSON in `model.json`; describe states/transitions that matter, including retained edge cases. Entity mappings aid investigation; they do not prove coverage. Review generated meaning against source/spec and existing assertions. The model is an estimate, not executable test truth.
16
+ 3. Independently challenge the estimated model with external inputs. Inventory all seven categories in [plan](assets/plan.json): user, host-update, async-result, cancellation, timer, server-event, lifecycle. Trace each relevant state owner and ingress in source, not just the selected tests. Combine operations across owners, such as host replacement after selection, cancellation before completion, disposal during work, and timer expiry during retry. Map discovered obligations to stable requirement/check/entity IDs, or record a cited gap or reasoned not-applicable entry. Keep cited intent, observations, inferences and assumptions distinct. Existing passing tests do not settle requirement conflicts.
17
+ 4. Copy/adapt [config](assets/config.json) and the self-contained [Vitest adapter](assets/vitest-adapter.mjs). Select bounded source, test producer, runner configuration, and meaningful dependency files. Paths are repository-relative, including check file paths; command cwd may be a package. Include project/package manifests and lockfiles for the selected dependency scope. The CLI automatically includes root and command-cwd manifests/locks, config/spec/model/adapter/mapped tests and exact baseline files. Materialize external/user intent in checked requirements documents and list authoritative intent files in `plan.authorityInputs`; edits to them must invalidate calibration. Put runner/producer configuration modules in `plan.runnerInputs` so changes require recalibration. Pin CI Node and package-manager versions to the calibrated versions; original and current OS/architecture remain evidence. Limit all selected files together to 200. No broad workspace directory or `node_modules` selection. Keep state/output outside inputs.
18
+ 5. Adapt [workflow](assets/workflow.yml) to the repository's package manager, lockfile, installation, CLI pin/build, selected command and paths. Use hosted runners by default, `contents: read`, unconditional artifact upload including hidden files and job summary. Keep `GITHUB_SHA` tied to actual checkout. Never run an LLM, auto-adopt, push, or add auth credentials in CI. Include the actual workflow path and Vitest config in config inputs before staging and checking. Existing trusted self-hosted runner workflows require same-repository trusted branch triggers, not arbitrary fork code.
19
+ 6. Stage generated input files with `git add` before the first local run. `baseline: null` bootstraps a public FileStore model and behavior record. Run `statelyai behavior calibrate --config <config> --output <fresh ignored directory>` against the unchanged implementation first. The CLI executes the real native runner and generates a self-contained calibration receipt. Inspect every mismatch against authoritative requirements. Correct a wrong model/test assumption or fix the implementation, then recalibrate. If a known implementation defect must remain red, add a cited, reasoned `implementation-defect` disposition to the plan before recalibration. Unresolved calibration mismatches block adoption. Never copy observed bugs into the requirements automatically. Inspect summary, native report, argv/exit/logs, manifest, model bindings and current gaps. A passing run is exit 0; mapped test failures/missing/skipped checks are 1; invalid evidence is 2. Fix setup errors; do not shrink obligations to make red pass.
20
+ 7. Explicitly run `statelyai behavior adopt <inspected output> --config <config>`. Adopt red as well as green records before the next evolution so failures remain in history. Commit generated config/spec/model/adapter/plan, the installed calibration receipt and the complete stateRoot `.statelyai` tree. Adoption updates baseline; no manual hashes. For dirty local runs, bound bytes must still match when adopting. `git add -f` may be needed when repository ignores `.statelyai`. Never commit output logs/runstore.
21
+ 8. Verify the generated setup with the built/installed CLI and real tests. Check a deliberate assertion failure in a selected existing test produces exit 1, a missing required mapping cannot pass, and a restored run passes while history still shows failure. Any unmapped selected case, including a passing case, is ERROR and cannot be adopted; fix the mapping inventory before application repair. Never turn that ERROR into a claimed behavioral FAIL. Keep experimental changes isolated. Remote execution is a separate verification step requiring actual run/log/artifact evidence. On a remote red or green run, download and inspect the artifact, adopt it against its exact checked parent, then commit the advanced state before the next run.
22
+
23
+ Normal CI runs `behavior check` with the checked receipt. Changed model/spec/plan/mappings/adapter/producer or runner identity requires explicit native recalibration and inspected adoption. Implementation changes run against the stable calibrated contract. Calibration binds its original revision and files; it does not pin all future app revisions. Schema 1 remains readable but has no setup assurance claim.
24
+
25
+ On later requirement changes, update spec and model estimate, preserve IDs for the same obligation, and use an explicit reasoned retirement to remove one. Omitted requirements/checks remain active. Refresh mappings from actual reporter output when tests change. Adopt refuses divergent ancestry; inspect both refs and rerun from the chosen checked baseline rather than rewriting artifact history.
@@ -0,0 +1,3 @@
1
+ interface:
2
+ display_name: 'Setup Behavior CI'
3
+ short_description: 'Set up Vitest or executable campaign behavior CI'
@@ -0,0 +1,203 @@
1
+ const statuses = new Set(["passed", "discrepancy", "inconclusive"]);
2
+ const nonempty = (value) => typeof value === "string" && value.trim().length > 0;
3
+ const count = (value) => Number.isSafeInteger(value) && value >= 0;
4
+ const fail = (message) => {
5
+ throw new Error(`Invalid campaign evidence: ${message}`);
6
+ };
7
+
8
+ export const executionFullName = "campaign.execution";
9
+ export const assertionFullName = (requirementId, checkId) =>
10
+ `campaign.assertion ${JSON.stringify([requirementId, checkId])}`;
11
+
12
+ export default function normalize(envelope) {
13
+ const { file, report } = envelope ?? {};
14
+ if (
15
+ !nonempty(file) ||
16
+ file.startsWith("/") ||
17
+ file.includes("\\") ||
18
+ file.split("/").some((part) => !part || part === "." || part === "..") ||
19
+ !file.endsWith(".mjs")
20
+ )
21
+ fail("file must be a repository-relative campaign .mjs path");
22
+ if (report?.formatVersion !== 1 || report.mode !== "campaign" || !statuses.has(report.status))
23
+ fail("expected a native full-campaign report");
24
+ if (
25
+ !Array.isArray(report.phases) ||
26
+ report.phases.length !== 2 ||
27
+ report.phases[0]?.phase !== "paths" ||
28
+ report.phases[1]?.phase !== "property" ||
29
+ report.phases.some((phase) => !statuses.has(phase.status))
30
+ )
31
+ fail("expected paths and property phases in order");
32
+ const status = report.phases.some((phase) => phase.status === "inconclusive")
33
+ ? "inconclusive"
34
+ : report.phases.some((phase) => phase.status === "discrepancy")
35
+ ? "discrepancy"
36
+ : "passed";
37
+ if (status !== report.status) fail("native verdict conflicts with phases");
38
+
39
+ if (
40
+ !nonempty(report.campaign?.id) ||
41
+ !nonempty(report.campaign.intent) ||
42
+ !nonempty(report.campaign.scope) ||
43
+ !nonempty(report.campaign.modelVersion) ||
44
+ !Array.isArray(report.campaign.assumptions) ||
45
+ !report.campaign.assumptions.every(nonempty) ||
46
+ !Array.isArray(report.campaign.evidence) ||
47
+ !report.campaign.evidence.every(nonempty) ||
48
+ !/^[a-f0-9]{64}$/.test(report.provenance?.entrySha256 ?? "") ||
49
+ report.provenance?.hashScope !== "entry-module-only" ||
50
+ !nonempty(report.provenance.node) ||
51
+ !["runner", "xstate", "xstateTest", "fastCheck"].every((key) =>
52
+ nonempty(report.provenance.versions?.[key]),
53
+ ) ||
54
+ !Number.isSafeInteger(report.budget?.seed) ||
55
+ !["numRuns", "maxCommands", "pathLimit", "timeoutMs"].every(
56
+ (key) => count(report.budget?.[key]) && report.budget[key] > 0,
57
+ )
58
+ )
59
+ fail("native campaign metadata, provenance or budget missing");
60
+ for (const phase of report.phases) {
61
+ if (phase.status !== "passed" && !nonempty(phase.message)) fail("native diagnostic missing");
62
+ if (phase.status !== "passed" && phase.requirementGaps === undefined) continue;
63
+ const coverage = phase.coverage;
64
+ if (
65
+ !coverage ||
66
+ !count(coverage.totals?.runs) ||
67
+ coverage.totals.runs === 0 ||
68
+ !coverage.exploration ||
69
+ typeof coverage.exploration.truncated !== "boolean" ||
70
+ !Array.isArray(coverage.exploration.truncationReasons) ||
71
+ (coverage.exploration.truncated &&
72
+ (coverage.exploration.truncationReasons.length === 0 ||
73
+ coverage.exploration.truncationReasons.some(
74
+ (reason) => phase.phase !== "property" || reason !== "maximum sequence length reached",
75
+ ))) ||
76
+ !Array.isArray(coverage.temporal?.failed) ||
77
+ coverage.temporal.failed.length > 0 ||
78
+ !Array.isArray(coverage.temporal?.inconclusive) ||
79
+ coverage.temporal.inconclusive.length > 0
80
+ )
81
+ fail("passing or coverage-gap native phase lacks completed execution evidence");
82
+ const assertions = [
83
+ "sutComparisons",
84
+ "oracleComparisons",
85
+ "invariantChecks",
86
+ "temporalChecks",
87
+ ].map((key) => coverage.totals[key]);
88
+ assertions.push(phase.additionalAssertions ?? 0);
89
+ if (!assertions.every(count) || assertions.reduce((total, value) => total + value, 0) === 0)
90
+ fail("passing or coverage-gap native phase lacks observable assertions");
91
+ }
92
+
93
+ const declarations = report.campaign?.requirements;
94
+ if (!Array.isArray(declarations) || declarations.length === 0)
95
+ fail("nonempty campaign.requirements declarations required");
96
+ const requirements = new Map();
97
+ const checks = new Set();
98
+ for (const requirement of declarations) {
99
+ if (
100
+ !nonempty(requirement?.id) ||
101
+ requirements.has(requirement.id) ||
102
+ !Array.isArray(requirement.checks) ||
103
+ requirement.checks.length === 0
104
+ )
105
+ fail("duplicate or malformed requirement declaration");
106
+ const entries = new Map();
107
+ for (const id of requirement.checks) {
108
+ if (!nonempty(id) || checks.has(id)) fail("duplicate or malformed assertion declaration");
109
+ checks.add(id);
110
+ entries.set(id, { passed: 0, failed: 0 });
111
+ }
112
+ requirements.set(requirement.id, entries);
113
+ }
114
+ for (const phase of report.phases) {
115
+ if (!Array.isArray(phase.requirements) || phase.requirements.length !== requirements.size)
116
+ fail(`missing requirement inventory in ${phase.phase}`);
117
+ const seenRequirements = new Set();
118
+ for (const requirement of phase.requirements) {
119
+ const expected = requirements.get(requirement?.id);
120
+ if (
121
+ !expected ||
122
+ seenRequirements.has(requirement.id) ||
123
+ !Array.isArray(requirement.checks) ||
124
+ requirement.checks.length !== expected.size
125
+ )
126
+ fail(`requirement inventory mismatch in ${phase.phase}`);
127
+ seenRequirements.add(requirement.id);
128
+ const seenChecks = new Set();
129
+ for (const check of requirement.checks) {
130
+ const total = expected.get(check?.id);
131
+ if (!total || seenChecks.has(check.id) || !count(check.passed) || !count(check.failed))
132
+ fail(`assertion inventory or counts invalid in ${phase.phase}`);
133
+ if (phase.status === "passed" && check.failed > 0)
134
+ fail("passed phase contains failed assertions");
135
+ seenChecks.add(check.id);
136
+ total.passed += check.passed;
137
+ total.failed += check.failed;
138
+ if (!count(total.passed) || !count(total.failed)) fail("assertion counts overflow");
139
+ }
140
+ }
141
+ }
142
+ const results = [];
143
+ for (const [requirementId, entries] of requirements) {
144
+ for (const [checkId, counts] of entries) {
145
+ results.push({
146
+ file,
147
+ fullName: assertionFullName(requirementId, checkId),
148
+ outcome: counts.failed ? "failed" : counts.passed ? "passed" : "skipped",
149
+ detail:
150
+ `Assertion ${checkId}: ${counts.passed} passed, ${counts.failed} failed invocations across paths and property, including shrinking.`.slice(
151
+ 0,
152
+ 4000,
153
+ ),
154
+ });
155
+ }
156
+ }
157
+ const missing = results.filter((result) => result.outcome === "skipped");
158
+ const inconclusive = report.phases.filter((phase) => phase.status === "inconclusive");
159
+ const discrepancy = report.phases.some((phase) => phase.status === "discrepancy");
160
+ const missingIds = [...requirements.values()].flatMap((entries) =>
161
+ [...entries].filter(([, counts]) => counts.passed + counts.failed === 0).map(([id]) => id),
162
+ );
163
+ for (const phase of report.phases) {
164
+ if (
165
+ phase.requirementGaps !== undefined &&
166
+ (discrepancy ||
167
+ phase.phase !== "property" ||
168
+ phase.status !== "inconclusive" ||
169
+ !Array.isArray(phase.requirementGaps) ||
170
+ phase.requirementGaps.length === 0 ||
171
+ results.some((result) => result.outcome === "failed") ||
172
+ new Set(phase.requirementGaps).size !== phase.requirementGaps.length ||
173
+ JSON.stringify([...phase.requirementGaps].sort()) !==
174
+ JSON.stringify([...missingIds].sort()))
175
+ )
176
+ fail("requirement gap marker conflicts with unevaluated assertions");
177
+ }
178
+ const coverageOnly =
179
+ inconclusive.length > 0 && inconclusive.every((phase) => phase.requirementGaps !== undefined);
180
+ const detail = report.phases
181
+ .filter((phase) => phase.status !== "passed")
182
+ .map((phase) => `${phase.phase}: ${phase.message}`)
183
+ .join("\n");
184
+ results.push({
185
+ file,
186
+ fullName: executionFullName,
187
+ outcome:
188
+ inconclusive.length && !coverageOnly
189
+ ? "error"
190
+ : discrepancy
191
+ ? "failed"
192
+ : missing.length
193
+ ? "skipped"
194
+ : "passed",
195
+ detail: (
196
+ detail ||
197
+ (missing.length
198
+ ? "Required assertions were not executed."
199
+ : "Native paths and property execution passed.")
200
+ ).slice(0, 4000),
201
+ });
202
+ return results;
203
+ }