@cratis/pi 7.28.0 → 7.29.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/README.md +18 -0
- package/package/corpus/harnesses/pi/extensions/cratis-mcp/configuration.ts +15 -4
- package/package/corpus/harnesses/pi/extensions/cratis-mcp/index.ts +25 -5
- package/package/corpus/harnesses/pi/extensions/cratis-mcp/process.ts +22 -1
- package/package/corpus/rules/profiles.md +26 -11
- package/package/corpus/rules/reactors.md +30 -0
- package/package/corpus/rules/specs.scenarios.csharp.md +41 -0
- package/package/corpus/skills/cratis-application-slice-specifications/SKILL.md +13 -0
- package/package/corpus/skills/cratis-chronicle-reactor/SKILL.md +16 -1
- package/package/corpus/skills/cratis-chronicle-reactor/references/specifications.md +85 -0
- package/package/corpus/skills/cratis-screenplay-toolchain/references/versions.md +11 -0
- package/package/corpus/skills/cratis-specifications-csharp/SKILL.md +13 -0
- package/package/corpus/skills/cratis-specifications-csharp/references/application-scenarios.md +41 -0
- package/package/profile-catalog.json +11 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -29,6 +29,24 @@ yields to the managed installation to avoid duplicate resources and hooks. The P
|
|
|
29
29
|
package manages only Pi; it neither configures other harnesses nor owns a local
|
|
30
30
|
managed corpus.
|
|
31
31
|
|
|
32
|
+
## Screenplay MCP tools
|
|
33
|
+
|
|
34
|
+
The packaged `cratis-mcp` extension starts the Screenplay MCP server
|
|
35
|
+
(`cratis screenplay mcp`) when the resolved profiles include `cratis/screenplay`.
|
|
36
|
+
The backend application profiles compose it, so an application repository gets
|
|
37
|
+
the Screenplay skills and, when the Cratis CLI 3.46.1 or later is installed, the
|
|
38
|
+
Screenplay tools; the server creates no model folder until the first write.
|
|
39
|
+
Installing them does not opt the repository in to model-first work.
|
|
40
|
+
|
|
41
|
+
When Screenplay comes in only through such a profile and no model root is
|
|
42
|
+
configured, a missing CLI, a CLI older than 3.46.1 (which creates the model
|
|
43
|
+
folder at server start) or a server that fails to start leaves the Screenplay
|
|
44
|
+
tools inactive with one informational notice when Pi has a UI, and silently otherwise.
|
|
45
|
+
Selecting any profile that also reaches Screenplay without going through an application
|
|
46
|
+
profile (including `cratis/screenplay`, `cratis/modeling/screenplay-stage`,
|
|
47
|
+
`cratis/full/*` or `cratis`), or configuring `mcpServers.screenplay.root` in
|
|
48
|
+
`.cratis/ai.json`, keeps startup failures visible as errors.
|
|
49
|
+
|
|
32
50
|
## Path guidance and subagents
|
|
33
51
|
|
|
34
52
|
The packaged `cratis-path-guidance` extension attaches a path-scoped rule to the
|
|
@@ -69,14 +69,21 @@ export function selectedServer(project: string, corpus: string) {
|
|
|
69
69
|
});
|
|
70
70
|
const languages = new Set(strings(configuration.languages));
|
|
71
71
|
const selected = new Set<string>();
|
|
72
|
-
const
|
|
73
|
-
|
|
72
|
+
const visited = new Set<string>();
|
|
73
|
+
let nonApplicationSelection = false;
|
|
74
|
+
const select = (id: string, explicit: boolean, throughApplication = false): void => {
|
|
74
75
|
const profile = profiles.find(candidate => candidate.id === id);
|
|
75
76
|
if (!profile) throw new Error(`Unknown Cratis AI profile '${id}'.`);
|
|
76
77
|
const supported = strings(profile.languages);
|
|
77
78
|
if (!explicit && supported.length && !supported.some(language => language === 'language-agnostic' || languages.has(language))) return;
|
|
79
|
+
const applicationPath = throughApplication || id === 'cratis/application' || id.startsWith('cratis/application/');
|
|
80
|
+
// Visit both paths: application composition must not hide an existing, independently selected Screenplay path.
|
|
81
|
+
const key = JSON.stringify([id, applicationPath]);
|
|
82
|
+
if (visited.has(key)) return;
|
|
83
|
+
visited.add(key);
|
|
78
84
|
selected.add(id);
|
|
79
|
-
|
|
85
|
+
if (id === 'cratis/screenplay' && !applicationPath) nonApplicationSelection = true;
|
|
86
|
+
strings(profile.composes).forEach(child => select(child, false, applicationPath));
|
|
80
87
|
};
|
|
81
88
|
requested.forEach(id => select(id, true));
|
|
82
89
|
if (!selected.has('cratis/screenplay')) return undefined;
|
|
@@ -99,5 +106,9 @@ export function selectedServer(project: string, corpus: string) {
|
|
|
99
106
|
const root = object(screenplay) ? screenplay.root : undefined;
|
|
100
107
|
if (root !== undefined && typeof root !== 'string') throw new Error('Screenplay MCP root must be a project-relative string.');
|
|
101
108
|
const physicalProject = realpathSync(project);
|
|
102
|
-
return {
|
|
109
|
+
return {
|
|
110
|
+
project: physicalProject,
|
|
111
|
+
root: root === undefined ? undefined : physicalRoot(physicalProject, root),
|
|
112
|
+
composedOnly: !nonApplicationSelection && root === undefined,
|
|
113
|
+
};
|
|
103
114
|
}
|
|
@@ -7,19 +7,22 @@ import { fileURLToPath } from 'node:url';
|
|
|
7
7
|
import type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';
|
|
8
8
|
import { ConnectionFailure } from './ConnectionFailure.ts';
|
|
9
9
|
import { object, selectedServer } from './configuration.ts';
|
|
10
|
-
import { startConnection } from './process.ts';
|
|
10
|
+
import { startConnection, supportsNoModelStartup } from './process.ts';
|
|
11
11
|
import { discover, failedResult, mapResult } from './protocol.ts';
|
|
12
|
+
import type { DiscoveredTool } from './DiscoveredTool.ts';
|
|
12
13
|
import type { StdioConnection } from './StdioConnection.ts';
|
|
13
14
|
|
|
14
15
|
const extensionDirectory = dirname(fileURLToPath(import.meta.url));
|
|
15
16
|
const bundledCorpus = resolve(extensionDirectory, '..', '..', '..', '..');
|
|
16
17
|
|
|
17
18
|
/** Native tools go through Pi's tool_call restrictions and normal execution/result pipeline. */
|
|
18
|
-
export function registerBridge(pi: ExtensionAPI, corpus: string, connect = startConnection): void {
|
|
19
|
+
export function registerBridge(pi: ExtensionAPI, corpus: string, connect = startConnection, checkVersion = supportsNoModelStartup): void {
|
|
19
20
|
let connection: StdioConnection | undefined;
|
|
20
21
|
let selectionKey: string | undefined;
|
|
21
22
|
let attempted = false;
|
|
22
23
|
let uncertain = false;
|
|
24
|
+
let startupSupported: Promise<boolean> | undefined;
|
|
25
|
+
let unavailableReported = false;
|
|
23
26
|
const registered = new Set<string>();
|
|
24
27
|
const paused = new Set<string>();
|
|
25
28
|
const failures = new Map<string, ReturnType<typeof failedResult>>();
|
|
@@ -47,6 +50,7 @@ export function registerBridge(pi: ExtensionAPI, corpus: string, connect = start
|
|
|
47
50
|
if (!connection || connection.stopped) throw new Error('Screenplay connection is unavailable. No request was retried; explicitly reload after inspecting any uncertain mutation.');
|
|
48
51
|
};
|
|
49
52
|
const refresh = async (context: ExtensionContext, newSession = false): Promise<void> => {
|
|
53
|
+
if (newSession) { startupSupported = undefined; unavailableReported = false; }
|
|
50
54
|
const previouslyActive = new Set([...pi.getActiveTools(), ...paused]);
|
|
51
55
|
let selection: ReturnType<typeof selectedServer>;
|
|
52
56
|
try { selection = currentSelection(context); }
|
|
@@ -60,11 +64,27 @@ export function registerBridge(pi: ExtensionAPI, corpus: string, connect = start
|
|
|
60
64
|
if (!selection) return;
|
|
61
65
|
if (uncertain) throw new Error('Screenplay mutation outcome remains unknown. Explicitly reload, inspect workspace-state, and obtain authorization before any recovery.');
|
|
62
66
|
const modelRoot = selection.root ?? "the project's Screenplay model";
|
|
63
|
-
|
|
64
|
-
|
|
67
|
+
let activeConnection: StdioConnection;
|
|
68
|
+
let tools: DiscoveredTool[];
|
|
65
69
|
try {
|
|
66
|
-
|
|
70
|
+
if (selection.composedOnly && !await (startupSupported ??= checkVersion(selection.project))) {
|
|
71
|
+
throw new Error('Screenplay optional startup requires Cratis CLI 3.46.1 or later.');
|
|
72
|
+
}
|
|
73
|
+
activeConnection = connect(selection.project);
|
|
74
|
+
connection = activeConnection;
|
|
75
|
+
tools = await discover(activeConnection);
|
|
67
76
|
if (connection !== activeConnection || activeConnection.stopped) throw new Error('Screenplay session ended during discovery.');
|
|
77
|
+
} catch (error) {
|
|
78
|
+
dispose();
|
|
79
|
+
// Application-only composition makes MCP optional. Independent profile/root errors and uncertain mutations still surface.
|
|
80
|
+
if (!selection.composedOnly || uncertain) throw error;
|
|
81
|
+
if (!unavailableReported) {
|
|
82
|
+
unavailableReported = true;
|
|
83
|
+
if (context.hasUI) context.ui.notify('Screenplay MCP is unavailable; install/update the Cratis CLI to 3.46.1 or later to enable it.', 'info');
|
|
84
|
+
}
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
68
88
|
const existing = new Set(pi.getAllTools().map(tool => tool.name));
|
|
69
89
|
if (tools.some(tool => existing.has(tool.nativeName) && !registered.has(tool.nativeName))) throw new Error('Screenplay native tool name conflicts with another extension.');
|
|
70
90
|
const oldNames = new Set(registered);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Copyright (c) Cratis. All rights reserved.
|
|
2
2
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
3
|
|
|
4
|
-
import { spawn } from 'node:child_process';
|
|
4
|
+
import { execFile, spawn } from 'node:child_process';
|
|
5
5
|
import { StdioConnection } from './StdioConnection.ts';
|
|
6
6
|
|
|
7
7
|
/** Preserve OS/runtime lookup only; never copy arbitrary credentials or descriptor environment values. */
|
|
@@ -13,6 +13,27 @@ export function childEnvironment(source: NodeJS.ProcessEnv = process.env): NodeJ
|
|
|
13
13
|
return environment;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
/** Older CLIs create a model root at server startup; optional composition must remain footprint-free. */
|
|
17
|
+
export function supportsNoModelStartup(project: string, execute: typeof execFile = execFile): Promise<boolean> {
|
|
18
|
+
return new Promise(resolve => {
|
|
19
|
+
execute('cratis', ['--version'], {
|
|
20
|
+
cwd: project,
|
|
21
|
+
env: childEnvironment(),
|
|
22
|
+
shell: false,
|
|
23
|
+
windowsHide: true,
|
|
24
|
+
timeout: 3000,
|
|
25
|
+
killSignal: 'SIGKILL',
|
|
26
|
+
maxBuffer: 4096,
|
|
27
|
+
encoding: 'utf8',
|
|
28
|
+
}, (error, stdout) => {
|
|
29
|
+
const version = /^\s*(\d+)\.(\d+)\.(\d+)(?:\+[\w.-]+)?\s*$/.exec(stdout);
|
|
30
|
+
if (error || !version) { resolve(false); return; }
|
|
31
|
+
const [, major, minor, patch] = version.map(Number);
|
|
32
|
+
resolve(major > 3 || (major === 3 && (minor > 46 || (minor === 46 && patch >= 1))));
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
16
37
|
export function startConnection(project: string, launch: typeof spawn = spawn): StdioConnection {
|
|
17
38
|
return new StdioConnection(launch('cratis', ['screenplay', 'mcp', '--project-root', project], {
|
|
18
39
|
cwd: project,
|
|
@@ -51,16 +51,21 @@ The complete list of available profiles is defined in [profile-catalog.json](../
|
|
|
51
51
|
|
|
52
52
|
| Profile ID | Description | Automatically Includes |
|
|
53
53
|
|---|---|---|
|
|
54
|
-
| `cratis/application` | Full application stack (C# + React + TypeScript) | `cratis/application/csharp`, `cratis/application/elixir`, `cratis/application/kotlin`, `cratis/application/typescript`, `cratis/arc/core`, `cratis/arc/react`, `cratis/chronicle/core`, `cratis/components`, `cratis/fundamentals`, `cratis/specifications/dotnet`, `cratis/specifications/typescript` |
|
|
55
|
-
| `cratis/application/csharp` | C# backend with Arc + Chronicle | `cratis/arc`, `cratis/arc/react`, `cratis/chronicle`, `cratis/components`, `cratis/fundamentals`, `cratis/language/csharp`, `cratis/specifications/dotnet`, `cratis/specifications/typescript` |
|
|
54
|
+
| `cratis/application` | Full application stack (C# + React + TypeScript) | `cratis/application/csharp`, `cratis/application/elixir`, `cratis/application/java`, `cratis/application/kotlin`, `cratis/application/typescript`, `cratis/arc/core`, `cratis/arc/react`, `cratis/chronicle/core`, `cratis/components`, `cratis/fundamentals`, `cratis/specifications/dotnet`, `cratis/specifications/typescript` |
|
|
55
|
+
| `cratis/application/csharp` | C# backend with Arc + Chronicle | `cratis/arc`, `cratis/arc/react`, `cratis/chronicle`, `cratis/components`, `cratis/fundamentals`, `cratis/language/csharp`, `cratis/screenplay`, `cratis/specifications/dotnet`, `cratis/specifications/typescript` |
|
|
56
56
|
| `cratis/application/react` | React frontend with Cratis Components | `cratis/arc/core`, `cratis/arc/react`, `cratis/components`, `cratis/fundamentals`, `cratis/specifications/dotnet`, `cratis/specifications/typescript` |
|
|
57
|
-
| `cratis/application/typescript` | TypeScript client for Chronicle | `cratis/chronicle/client-typescript`, `cratis/language/typescript`, `cratis/specifications/typescript` |
|
|
58
|
-
| `cratis/application/arc-chronicle` | Arc + Chronicle integration | `cratis/arc/core`, `cratis/chronicle/core`, `cratis/fundamentals`, `cratis/specifications/dotnet` |
|
|
59
|
-
| `cratis/application/arc-only` | Arc without Chronicle | `cratis/arc/core`, `cratis/fundamentals`, `cratis/specifications/dotnet` |
|
|
60
|
-
| `cratis/application/chronicle-dotnet` | Chronicle .NET client | `cratis/chronicle/client-dotnet`, `cratis/chronicle/core`, `cratis/fundamentals`, `cratis/specifications/dotnet` |
|
|
61
|
-
| `cratis/application/elixir` | Elixir Chronicle client | `cratis/chronicle/client-elixir`, `cratis/language/elixir` |
|
|
62
|
-
| `cratis/application/kotlin` | Kotlin Arc + Chronicle application | `cratis/arc/client-kotlin`, `cratis/chronicle/client-kotlin`, `cratis/language/kotlin` |
|
|
63
|
-
| `cratis/application/java` | Java Arc + Chronicle application | `cratis/arc/client-kotlin`, `cratis/chronicle/client-java`, `cratis/language/java` |
|
|
57
|
+
| `cratis/application/typescript` | TypeScript client for Chronicle | `cratis/chronicle/client-typescript`, `cratis/language/typescript`, `cratis/screenplay`, `cratis/specifications/typescript` |
|
|
58
|
+
| `cratis/application/arc-chronicle` | Arc + Chronicle integration | `cratis/arc/core`, `cratis/chronicle/core`, `cratis/fundamentals`, `cratis/screenplay`, `cratis/specifications/dotnet` |
|
|
59
|
+
| `cratis/application/arc-only` | Arc without Chronicle | `cratis/arc/core`, `cratis/fundamentals`, `cratis/screenplay`, `cratis/specifications/dotnet` |
|
|
60
|
+
| `cratis/application/chronicle-dotnet` | Chronicle .NET client | `cratis/chronicle/client-dotnet`, `cratis/chronicle/core`, `cratis/fundamentals`, `cratis/screenplay`, `cratis/specifications/dotnet` |
|
|
61
|
+
| `cratis/application/elixir` | Elixir Chronicle client | `cratis/chronicle/client-elixir`, `cratis/language/elixir`, `cratis/screenplay` |
|
|
62
|
+
| `cratis/application/kotlin` | Kotlin Arc + Chronicle application | `cratis/arc/client-kotlin`, `cratis/chronicle/client-kotlin`, `cratis/language/kotlin`, `cratis/screenplay` |
|
|
63
|
+
| `cratis/application/java` | Java Arc + Chronicle application | `cratis/arc/client-kotlin`, `cratis/chronicle/client-java`, `cratis/language/java`, `cratis/screenplay` |
|
|
64
|
+
|
|
65
|
+
`cratis/application` inherits `cratis/screenplay` through its language-specific
|
|
66
|
+
children. Backend-bearing application profiles include the method skills and MCP
|
|
67
|
+
declaration, not `cratis/stage`; the frontend-only `cratis/application/react`
|
|
68
|
+
profile includes neither Screenplay nor Stage.
|
|
64
69
|
|
|
65
70
|
### Framework Profiles
|
|
66
71
|
|
|
@@ -179,7 +184,8 @@ or gap-fill from its contract; never edit Stage-managed output or weaken protect
|
|
|
179
184
|
Otherwise stay code-first; only the entry-point session may propose a model, at most once
|
|
180
185
|
per session. Master definition: `cratis-screenplay-modeling-lifecycle`. Framework and non-opted-in brownfield work remain
|
|
181
186
|
code-first; infrastructure, clients, adapters and code attachments stay in code.
|
|
182
|
-
|
|
187
|
+
Backend-bearing application profiles automatically compose `cratis/screenplay`;
|
|
188
|
+
installing its skills or registering its MCP server is not model-first opt-in.
|
|
183
189
|
|
|
184
190
|
`cratis/screenplay` carries the **method** and the **language**. Load the lifecycle
|
|
185
191
|
and one phase skill, then the construct skill needed for syntax.
|
|
@@ -228,6 +234,15 @@ conflicts. The model is wherever the project keeps its `.play` files, else under
|
|
|
228
234
|
`src/`, else in a `Screenplay/` folder at the repository root; never under `.cratis/`.
|
|
229
235
|
Project-owned configuration can name a root explicitly.
|
|
230
236
|
|
|
237
|
+
**No-model startup requires Cratis CLI 3.46.1 or later.** With
|
|
238
|
+
`cratis screenplay mcp --project-root <project>`, that version resolves the root
|
|
239
|
+
without creating it; discovery, startup and proposals leave no model folder, and
|
|
240
|
+
the first `apply` write creates it. `cratis ai install` also creates no model
|
|
241
|
+
folder unless the project configured a root. Older CLIs create the resolved model root at
|
|
242
|
+
server startup (`Screenplay/` when no `.play` files, `Source/` or `src/` exist); upgrade before using the automatically selected MCP server in a
|
|
243
|
+
repository without a model. In Pi, when every selected path to Screenplay goes through an application profile and no root is configured, a missing, older or unavailable CLI leaves its MCP tools inactive with one informational UI notice (none in headless mode); an independent non-application path to Screenplay or a configured root still surfaces startup errors. See the toolchain skill's
|
|
244
|
+
[version reference](../skills/cratis-screenplay-toolchain/references/versions.md).
|
|
245
|
+
|
|
231
246
|
## How to Use Profiles
|
|
232
247
|
|
|
233
248
|
### 1. Select Your Profile
|
|
@@ -268,7 +283,7 @@ Profiles can compose other profiles. When you select a parent profile, all child
|
|
|
268
283
|
{
|
|
269
284
|
"schemaVersion": "1.0.0",
|
|
270
285
|
"profiles": [
|
|
271
|
-
"cratis/application/csharp" // Includes: arc, arc/react, chronicle, components, fundamentals, language/csharp, specifications/dotnet, specifications/typescript
|
|
286
|
+
"cratis/application/csharp" // Includes: arc, arc/react, chronicle, components, fundamentals, language/csharp, screenplay, specifications/dotnet, specifications/typescript
|
|
272
287
|
]
|
|
273
288
|
}
|
|
274
289
|
```
|
|
@@ -278,4 +278,34 @@ async Task Because() => await _scenario.Given.ForEventSource(_id).Events(new Inv
|
|
|
278
278
|
[Fact] async Task should_notify_about_the_issued_invoice() => await _notifications.Received(1).Notify(Arg.Is<string>(_ => _.Contains("INV-1001")));
|
|
279
279
|
```
|
|
280
280
|
|
|
281
|
+
### Read-model dependencies in scenarios — Chronicle 19.46.0+
|
|
282
|
+
|
|
283
|
+
`ReactorScenario<TReactor>` does **not** build read models from `Given` events,
|
|
284
|
+
including `[Passive]` models. Seed the handler's read-model parameter explicitly
|
|
285
|
+
before firing the trigger:
|
|
286
|
+
|
|
287
|
+
```csharp
|
|
288
|
+
void Establish() => _scenario.Given.ForEventSourceId(_id).ReadModel(_order);
|
|
289
|
+
async Task Because() => await _scenario.Given.ForEventSource(_id).Events(_orderPlaced);
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
In a separate absence specification, replace the seeding with
|
|
293
|
+
`_scenario.Given.ForEventSourceId(_id).NoReadModel<Order>()` to deliberately pass
|
|
294
|
+
`null` to an `Order?` parameter. Without either setup, resolution to `null` throws
|
|
295
|
+
`ReadModelNotSeededForReactorScenario` (`Cratis.Chronicle.Testing.Reactors`), even
|
|
296
|
+
for a nullable parameter. Its message names the type and missing key, suggests
|
|
297
|
+
both setup methods, and lists other seeded keys for that type when any exist.
|
|
298
|
+
Use the key from `ICanResolveReadModelKey.Resolve` for seeding or absence when
|
|
299
|
+
implemented, not the triggering source id. `NoReadModel<T>()` does not remove a
|
|
300
|
+
seeded instance. This guard applies to `EventStoreForTesting`, not an explicitly
|
|
301
|
+
supplied store of another type; production read-model resolution is unchanged.
|
|
302
|
+
|
|
303
|
+
Also in Chronicle 19.46.0+, `Events(...)` surfaces a synchronously throwing
|
|
304
|
+
handler's own exception, preserving its stack, rather than the reflection
|
|
305
|
+
`TargetInvocationException`. Capture it with `Catch.Exception` and assert the
|
|
306
|
+
handler's exception type. `EventContext.From(...)` now defaults an omitted
|
|
307
|
+
`occurred` to `DateTimeOffset.UtcNow`, not local `Now`; scenario contexts,
|
|
308
|
+
`EventContext.Empty` and `EmptyWithEventSourceId(...)` use that helper. An
|
|
309
|
+
explicit occurrence time is preserved, not normalized to UTC.
|
|
310
|
+
|
|
281
311
|
For reactors that return side-effect events, assert the resulting appends through the scenario's event store; for non-event side effects, assert on the mocked services (as above). See [specs.scenarios.csharp.md](./specs.scenarios.csharp.md) for the full `*Scenario` family.
|
|
@@ -160,6 +160,47 @@ async Task Because() => await _scenario.Given.ForEventSource(_id).Events(new Aut
|
|
|
160
160
|
[Fact] async Task should_notify() => await _service.Received(1).Notify("Jane Austen");
|
|
161
161
|
```
|
|
162
162
|
|
|
163
|
+
#### Read-model setup — Chronicle 19.46.0+
|
|
164
|
+
|
|
165
|
+
In a class-based `ReactorScenario<TReactor>`, `Given` events invoke the reactor;
|
|
166
|
+
they **do not materialize read models**, even `[Passive]` ones. For each
|
|
167
|
+
read-model handler parameter, choose one setup before `Because()`:
|
|
168
|
+
|
|
169
|
+
```csharp
|
|
170
|
+
// Present-state specification: supply the instance the handler must receive.
|
|
171
|
+
void Establish() => _scenario.Given.ForEventSourceId(_id).ReadModel(_author);
|
|
172
|
+
|
|
173
|
+
// Separate absence specification: deliberately let an Author? parameter receive null.
|
|
174
|
+
void Establish() => _scenario.Given.ForEventSourceId(_id).NoReadModel<Author>();
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
These are alternatives in **separate specification classes**, not two
|
|
178
|
+
`Establish()` methods in one class. Both builders return
|
|
179
|
+
`ReactorSourceGivenBuilder<TReactor>` for chaining; neither setup is async.
|
|
180
|
+
Use the triggering event-source id by default, or the key returned by the
|
|
181
|
+
reactor's `ICanResolveReadModelKey.Resolve` when implemented. An absence
|
|
182
|
+
declaration is for that exact type and key and does not delete a seeded model.
|
|
183
|
+
|
|
184
|
+
If a recognized read-model parameter resolves to `null` without declared absence,
|
|
185
|
+
`Events(...)` throws `ReadModelNotSeededForReactorScenario` from
|
|
186
|
+
`Cratis.Chronicle.Testing.Reactors`, even if the parameter is nullable. The
|
|
187
|
+
message names the full type and missing key, explains `ReadModel(...)` and
|
|
188
|
+
`NoReadModel<T>()`, and lists seeded keys for that type when any exist. Fix the
|
|
189
|
+
fixture or its key; do not add `NoReadModel<T>()` just to silence missing setup.
|
|
190
|
+
The guard applies to `EventStoreForTesting`; it does not alter production
|
|
191
|
+
resolution or an explicitly supplied event store of another type.
|
|
192
|
+
|
|
193
|
+
**Handler failures:** in Chronicle 19.46.0+, a synchronous handler throw surfaces
|
|
194
|
+
its own exception, not `TargetInvocationException`, with its handler stack
|
|
195
|
+
preserved. Use `await Catch.Exception(() => _scenario.Given.ForEventSource(_id).Events(...))`
|
|
196
|
+
and assert the handler's exception type.
|
|
197
|
+
|
|
198
|
+
**Occurrence time:** in Chronicle 19.46.0+, `EventContext.From(...)` uses
|
|
199
|
+
`DateTimeOffset.UtcNow` when `occurred` is omitted (previously local `Now`).
|
|
200
|
+
`ReactorScenario` builds its contexts through this helper, as do
|
|
201
|
+
`EventContext.Empty` and `EmptyWithEventSourceId(...)`. Explicit occurrence times
|
|
202
|
+
keep their supplied offset; the positional constructor has no such default.
|
|
203
|
+
|
|
163
204
|
## Out-of-process Chronicle integration specs (advanced)
|
|
164
205
|
|
|
165
206
|
Reserve these for the host/transport boundary the scenario helpers can't reach. They test a complete slice — HTTP request → command → append → constraint → projection — against a real Chronicle store, and live under `when_<behavior>/` directly inside the slice folder (no `for_` folder; the "unit" is the whole slice).
|
|
@@ -30,6 +30,10 @@ This skill decides **when to add a scenario** and **what to cover**. The C# mech
|
|
|
30
30
|
|
|
31
31
|
> Re-verified at the versions above by **symbol and signature**: every type, attribute and member this skill names exists at that tag, and the public surface it describes is unchanged since the previous verification (Chronicle 16.45.x / Arc 22.10.4 — the Chronicle 16→18 client diff is converters, options and doc comments; no type was removed or renamed). Behavior claims were verified at the earlier tag unless a section says otherwise.
|
|
32
32
|
|
|
33
|
+
Reactor read-model setup and the `EventContext.From` UTC default below are
|
|
34
|
+
verified separately at **Chronicle 19.46.0**, the minimum version for that
|
|
35
|
+
guidance; see `cratis-chronicle-reactor/references/specifications.md` for sources.
|
|
36
|
+
|
|
33
37
|
`Cratis.Testing` is the meta-package that brings `Cratis.Arc.Testing` and
|
|
34
38
|
`Cratis.Arc.Chronicle.Testing` together. Reverify against the owning product
|
|
35
39
|
repository before claiming support for another version.
|
|
@@ -112,6 +116,15 @@ keep a scenario for that wiring and for appended-event behavior.
|
|
|
112
116
|
| Automation or Translation — a reactor | `ReactorScenario<TReactor>` | `cratis-specifications-csharp` |
|
|
113
117
|
| A host, transport, or real-infrastructure boundary | Out-of-process integration | `cratis-specifications-csharp` |
|
|
114
118
|
|
|
119
|
+
For a class-based reactor scenario on **Chronicle 19.46.0+**, explicitly seed
|
|
120
|
+
handler read-model parameters with `Given.ForEventSourceId(id).ReadModel(instance)`
|
|
121
|
+
or declare intentional absence with `.NoReadModel<T>()`. An unseeded model throws
|
|
122
|
+
`ReadModelNotSeededForReactorScenario` even if nullable; `Given` events do not
|
|
123
|
+
build read models, including passive ones. Use the reactor's resolved key, not
|
|
124
|
+
necessarily the trigger's source id. This is **not** the command-scenario event
|
|
125
|
+
seeding described in Step 5. Read `cratis-chronicle-reactor` for the setup examples,
|
|
126
|
+
exception diagnostics, synchronous handler throws and UTC context defaults.
|
|
127
|
+
|
|
115
128
|
## Step 3 — Cover every outcome of a State Change slice
|
|
116
129
|
|
|
117
130
|
One specification class for **each** of:
|
|
@@ -45,6 +45,11 @@ This skill is verified against these exact sources:
|
|
|
45
45
|
| `Cratis.Chronicle` | `18.3.0` | `Cratis.Chronicle.Reactors`, `Cratis.Chronicle.Reactors.SideEffects`, `Cratis.Chronicle.EventSequences`, `Cratis.Chronicle.Events` |
|
|
46
46
|
| `Cratis.Chronicle.CodeAnalysis` | `18.3.0` | `CHR0004`, `CHR0005`, `CHR0008`, `CHR0013`, `CHR0022`, `CHR0031`, `CHR0032` |
|
|
47
47
|
|
|
48
|
+
Scenario read-model seeding/absence, synchronous handler exception propagation,
|
|
49
|
+
and the `EventContext.From` UTC default are verified separately at **Chronicle
|
|
50
|
+
19.46.0**; they require **19.46.0+**. See the source paths in
|
|
51
|
+
[references/specifications.md](references/specifications.md).
|
|
52
|
+
|
|
48
53
|
Reverify product sources before claiming support for another version.
|
|
49
54
|
|
|
50
55
|
> **There is no `ReactorSideEffect` type.** It does not exist in Chronicle, in
|
|
@@ -363,7 +368,17 @@ when they serve that concern.
|
|
|
363
368
|
|
|
364
369
|
Start with `Specification` plus a direct call for pure reactor decisions; add `ReactorScenario<TReactor>` only where invocation, dependency wiring or side effects contribute proof. Specify the contract, not only the happy path: field lineage, contract-authorized filtering, repeated delivery, and unrelated events.
|
|
365
370
|
|
|
366
|
-
|
|
371
|
+
In Chronicle **19.46.0+**, a handler's unseeded read-model parameter throws
|
|
372
|
+
`ReadModelNotSeededForReactorScenario`, even if nullable. Seed it through
|
|
373
|
+
`Given.ForEventSourceId(id).ReadModel(instance)`, or declare intentional absence
|
|
374
|
+
with `.NoReadModel<T>()`. Use the resolved read-model key; `Given` events do not
|
|
375
|
+
build read models, including passive ones. A synchronous handler throw surfaces
|
|
376
|
+
the handler's exception rather than a reflection wrapper, and the scenario's
|
|
377
|
+
`EventContext.Occurred` defaults to UTC.
|
|
378
|
+
|
|
379
|
+
Read [references/specifications.md](references/specifications.md) before writing
|
|
380
|
+
or reviewing reactor specifications; it shows both setup alternatives and the
|
|
381
|
+
scope of these scenario-only guards.
|
|
367
382
|
|
|
368
383
|
## Verify
|
|
369
384
|
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
# Reactor specifications
|
|
2
|
+
|
|
3
|
+
## Contents
|
|
4
|
+
|
|
5
|
+
- Specifications
|
|
6
|
+
- Read-model setup — Chronicle 19.46.0+
|
|
7
|
+
- Exceptions and occurrence time — Chronicle 19.46.0+
|
|
8
|
+
- Contract coverage
|
|
9
|
+
|
|
1
10
|
## Specifications
|
|
2
11
|
|
|
3
12
|
Start with `Specification` plus a direct call for pure reactor decisions,
|
|
@@ -9,6 +18,82 @@ non-event side effects; for handlers that return events, assert the resulting
|
|
|
9
18
|
appends through the scenario's event store. Cover the replay path separately
|
|
10
19
|
when the reactor has `[OnceOnly]` or `[Replay]` handlers.
|
|
11
20
|
|
|
21
|
+
## Read-model setup — Chronicle 19.46.0+
|
|
22
|
+
|
|
23
|
+
Class-based `ReactorScenario<TReactor>` does not build read models from `Given`
|
|
24
|
+
events, **including passive models**. Seed a pre-built instance in `Establish()`:
|
|
25
|
+
|
|
26
|
+
```csharp
|
|
27
|
+
void Establish() => _scenario.Given.ForEventSourceId(_id).ReadModel(_order);
|
|
28
|
+
async Task Because() => await _scenario.Given.ForEventSource(_id).Events(_orderPlaced);
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
For a **separate absence specification**, use this setup instead, so an `Order?`
|
|
32
|
+
handler parameter deliberately receives `null`:
|
|
33
|
+
|
|
34
|
+
```csharp
|
|
35
|
+
void Establish() => _scenario.Given.ForEventSourceId(_id).NoReadModel<Order>();
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The builders live in `Cratis.Chronicle.Testing.Reactors`. Exact signatures on
|
|
39
|
+
`ReactorSourceGivenBuilder<TReactor>` (`where TReactor : class, IReactor`):
|
|
40
|
+
|
|
41
|
+
```csharp
|
|
42
|
+
public ReactorSourceGivenBuilder<TReactor> ReadModel<TReadModel>(TReadModel readModel)
|
|
43
|
+
where TReadModel : class;
|
|
44
|
+
public ReactorSourceGivenBuilder<TReactor> NoReadModel<TReadModel>()
|
|
45
|
+
where TReadModel : class;
|
|
46
|
+
public Task Events(params object[] events);
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Both `Given.ForEventSource(EventSourceId)` and
|
|
50
|
+
`Given.ForEventSourceId(EventSourceId)` return this builder. Seed or declare
|
|
51
|
+
absence using the triggering source id by default, or the key returned by
|
|
52
|
+
`ICanResolveReadModelKey.Resolve` when implemented. Declarations are shared
|
|
53
|
+
across the scenario's builders, match the **exact type and key**, and do not
|
|
54
|
+
remove a seeded instance. Do not declare absence to hide forgotten setup.
|
|
55
|
+
|
|
56
|
+
If a recognized read-model parameter resolves to `null` without declared
|
|
57
|
+
absence, `Events(...)` throws `ReadModelNotSeededForReactorScenario` from
|
|
58
|
+
`Cratis.Chronicle.Testing.Reactors`, **even for nullable parameters**. Its message
|
|
59
|
+
names the full read-model type and key, tells you to seed via `ReadModel(...)`
|
|
60
|
+
or declare `NoReadModel<T>()`, and lists other seeded keys for that type when
|
|
61
|
+
any exist. Check the key first if the fixture already seeds that type. This
|
|
62
|
+
guard applies only when the scenario uses `EventStoreForTesting` (including an
|
|
63
|
+
explicit instance); an explicitly supplied store of another type and production
|
|
64
|
+
read-model resolution are unchanged.
|
|
65
|
+
|
|
66
|
+
## Exceptions and occurrence time — Chronicle 19.46.0+
|
|
67
|
+
|
|
68
|
+
`Events(...)` rethrows a synchronously throwing handler's **own exception**,
|
|
69
|
+
unwrapping the `TargetInvocationException` previously surfaced by reflection,
|
|
70
|
+
and preserves the handler stack. Capture with
|
|
71
|
+
`await Catch.Exception(() => _scenario.Given.ForEventSource(_id).Events(_orderPlaced))`
|
|
72
|
+
and assert the handler's exception type, not a reflection wrapper.
|
|
73
|
+
|
|
74
|
+
`EventContext.From(...)` defaults omitted `occurred` to `DateTimeOffset.UtcNow`
|
|
75
|
+
(previously `DateTimeOffset.Now`). `ReactorScenario` uses this helper, as do
|
|
76
|
+
`EventContext.Empty` and `EmptyWithEventSourceId(...)`. Explicit timestamps keep
|
|
77
|
+
their supplied offset; the positional constructor does not default `Occurred`.
|
|
78
|
+
For deterministic time-dependent plain-call specs, supply a fixed occurrence
|
|
79
|
+
time rather than asserting an exact current instant.
|
|
80
|
+
|
|
81
|
+
Verified against **Chronicle tag `v19.46.0`**:
|
|
82
|
+
|
|
83
|
+
- `Source/Clients/Testing/Reactors/ReactorSourceGivenBuilder.cs` and
|
|
84
|
+
`ReactorScenarioGivenBuilder.cs`: seeding, absence and builder signatures.
|
|
85
|
+
- `Source/Clients/Testing/Reactors/ScenarioReactorMethodArgumentsResolver.cs` and
|
|
86
|
+
`ReadModelNotSeededForReactorScenario.cs`: guard scope, resolved key and message.
|
|
87
|
+
- `Source/Clients/Testing/Reactors/ReactorScenario.cs`: synchronous exception
|
|
88
|
+
unwrapping and context creation.
|
|
89
|
+
- `Source/Clients/DotNET/Events/EventContext.cs`: UTC default and explicit time.
|
|
90
|
+
- `Source/Clients/Testing.Specs/Reactors/for_ReactorScenario/when_resolving_read_model_dependencies/`:
|
|
91
|
+
seeded, absent, nullable, passive, custom-key and explicit-store examples.
|
|
92
|
+
- `Source/Clients/Testing.Specs/Reactors/for_ReactorScenario/when_a_reactor_handler_throws_synchronously.cs`
|
|
93
|
+
and `when_a_reactor_reads_the_occurrence_time.cs`: handler exception and UTC specs.
|
|
94
|
+
|
|
95
|
+
## Contract coverage
|
|
96
|
+
|
|
12
97
|
Specify the contract, not only the happy path:
|
|
13
98
|
|
|
14
99
|
- **Field lineage.** For each returned command or event, a specification asserts
|
|
@@ -31,6 +31,17 @@ Check the actual executable before relying on this table: `screenplay --version`
|
|
|
31
31
|
`cratis --version`, MCP `initialize` → `serverInfo.version`. An older executable
|
|
32
32
|
on PATH can shadow a newer install; inspect `which -a cratis`.
|
|
33
33
|
|
|
34
|
+
**Minimum for no-model MCP startup: Cratis CLI 3.46.1.** At `v3.46.1`,
|
|
35
|
+
`cratis screenplay mcp --project-root <project>` resolves a root without creating
|
|
36
|
+
it; startup, discovery and proposals leave no model folder, and the first
|
|
37
|
+
`apply` write creates it. `cratis ai install` creates no model folder unless a
|
|
38
|
+
root is configured. Older CLIs create the resolved root on server startup (`Screenplay/` when no `.play` files, `Source/` or `src/` exist). Released
|
|
39
|
+
source evidence: CLI
|
|
40
|
+
`Source/Cli.Specs/for_ScreenplayMcpInvocation/when_serving_a_project_without_a_model/and_the_client_writes_a_document.cs`
|
|
41
|
+
and `Source/Cli.Specs/for_AiMcpRegistration/when_installing/with_a_composed_profile.cs`
|
|
42
|
+
at tag `v3.46.1` (CLI#322 / CLI#311). This minimum does not re-label the compiler
|
|
43
|
+
or renderer probes below as results from the newer bundle.
|
|
44
|
+
|
|
34
45
|
## Two compilers, one rule
|
|
35
46
|
|
|
36
47
|
Standalone Screenplay 4.131.0 and the CLI's 4.130.0 bundle are different releases.
|
|
@@ -22,6 +22,11 @@ This skill is verified against these exact public releases:
|
|
|
22
22
|
| `Cratis.Arc.Testing` | `22.16.0` | `CommandScenario<TCommand>` and `CommandResult` assertions |
|
|
23
23
|
| `Cratis.Chronicle.Testing` | `18.3.0` | `EventScenario`, `ReadModelScenario<T>`, `ReactorScenario<T>` |
|
|
24
24
|
|
|
25
|
+
Reactor read-model seeding/absence, synchronous handler exceptions and the
|
|
26
|
+
`EventContext.From` UTC default are verified separately at **Chronicle 19.46.0**
|
|
27
|
+
and require **19.46.0+**; see the ReactorScenario section in
|
|
28
|
+
[application-scenarios.md](references/application-scenarios.md).
|
|
29
|
+
|
|
25
30
|
Generated-value scenario pinning is verified separately against
|
|
26
31
|
`Cratis.Arc.Testing` **22.68.0** (introduced in **22.67.0**).
|
|
27
32
|
|
|
@@ -83,6 +88,14 @@ never produces a concurrency violation. `ShouldHaveConcurrencyViolations()`
|
|
|
83
88
|
cannot pass for such an append and its negation passes vacuously — specify concurrency against the
|
|
84
89
|
real kernel with an out-of-process integration specification instead.
|
|
85
90
|
|
|
91
|
+
For a class-based `ReactorScenario<TReactor>` on **Chronicle 19.46.0+**, seed each
|
|
92
|
+
handler read-model parameter with `Given.ForEventSourceId(id).ReadModel(instance)`
|
|
93
|
+
or declare intentional absence with `.NoReadModel<T>()`; `Given` events do not
|
|
94
|
+
build it, even if passive. Missing setup throws
|
|
95
|
+
`ReadModelNotSeededForReactorScenario` even for a nullable parameter. Use the
|
|
96
|
+
resolved read-model key. See the ReactorScenario reference for diagnostics,
|
|
97
|
+
exception capture and UTC occurrence-time defaults.
|
|
98
|
+
|
|
86
99
|
Specification projects are named `<Source>.Specs` and run on xUnit.
|
|
87
100
|
|
|
88
101
|
### Pin server-generated Handle values
|
package/package/corpus/skills/cratis-specifications-csharp/references/application-scenarios.md
CHANGED
|
@@ -262,6 +262,47 @@ async Task Because() =>
|
|
|
262
262
|
Construct with an `IServiceProvider` of substituted collaborators and assert on
|
|
263
263
|
those substitutes after `Given` fires the events.
|
|
264
264
|
|
|
265
|
+
### Read-model parameters — Chronicle 19.46.0+
|
|
266
|
+
|
|
267
|
+
`Given` events invoke the reactor; they **do not build read models**, including
|
|
268
|
+
passive ones. In the present-state specification, seed a pre-built instance:
|
|
269
|
+
|
|
270
|
+
```csharp
|
|
271
|
+
void Establish() => _scenario.Given.ForEventSourceId(_id).ReadModel(_author);
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
In a **separate absence specification**, deliberately let an `Author?` handler
|
|
275
|
+
parameter receive `null`:
|
|
276
|
+
|
|
277
|
+
```csharp
|
|
278
|
+
void Establish() => _scenario.Given.ForEventSourceId(_id).NoReadModel<Author>();
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Both return `ReactorSourceGivenBuilder<TReactor>` for chaining, not a `Task`.
|
|
282
|
+
Use the triggering source id by default or `ICanResolveReadModelKey.Resolve`'s
|
|
283
|
+
key when implemented. Absence matches the exact type and key and does not remove
|
|
284
|
+
a seeded instance. Missing setup throws `ReadModelNotSeededForReactorScenario`
|
|
285
|
+
(`Cratis.Chronicle.Testing.Reactors`), **even for nullable parameters**. Its
|
|
286
|
+
message names the type/key, explains both setups, and lists other seeded keys
|
|
287
|
+
for that type when any exist. Fix the fixture/key rather than hiding missing
|
|
288
|
+
setup with `NoReadModel<T>()`. This guard applies to `EventStoreForTesting`, not
|
|
289
|
+
production resolution or an explicitly supplied store of another type.
|
|
290
|
+
|
|
291
|
+
Also in **Chronicle 19.46.0+**, a synchronous handler throw surfaces its own
|
|
292
|
+
exception, not reflection's `TargetInvocationException`, preserving the handler
|
|
293
|
+
stack. Capture with `await Catch.Exception(() => _scenario.Given.ForEventSource(_id).Events(...))`
|
|
294
|
+
and assert the handler's exception type.
|
|
295
|
+
|
|
296
|
+
`EventContext.From(...)` defaults omitted `occurred` to `DateTimeOffset.UtcNow`,
|
|
297
|
+
previously local `Now`. Scenario contexts, `EventContext.Empty` and
|
|
298
|
+
`EmptyWithEventSourceId(...)` use this helper; explicit times retain their offset,
|
|
299
|
+
and the positional constructor has no such default.
|
|
300
|
+
|
|
301
|
+
Sources at **Chronicle tag `v19.46.0`**:
|
|
302
|
+
`Source/Clients/Testing/Reactors/{ReactorScenario,ReactorSourceGivenBuilder,ScenarioReactorMethodArgumentsResolver,ReadModelNotSeededForReactorScenario}.cs`
|
|
303
|
+
and `Source/Clients/DotNET/Events/EventContext.cs`. For exact signatures and
|
|
304
|
+
source-backed examples, read `cratis-chronicle-reactor/references/specifications.md`.
|
|
305
|
+
|
|
265
306
|
## Application conventions
|
|
266
307
|
|
|
267
308
|
- **Use a per-specification fixture value for anything under a shared uniqueness
|
|
@@ -114,6 +114,7 @@
|
|
|
114
114
|
"cratis/arc/core",
|
|
115
115
|
"cratis/chronicle/core",
|
|
116
116
|
"cratis/fundamentals",
|
|
117
|
+
"cratis/screenplay",
|
|
117
118
|
"cratis/specifications/dotnet"
|
|
118
119
|
],
|
|
119
120
|
"languages": [
|
|
@@ -125,6 +126,7 @@
|
|
|
125
126
|
"composes": [
|
|
126
127
|
"cratis/arc/core",
|
|
127
128
|
"cratis/fundamentals",
|
|
129
|
+
"cratis/screenplay",
|
|
128
130
|
"cratis/specifications/dotnet"
|
|
129
131
|
],
|
|
130
132
|
"languages": [
|
|
@@ -137,6 +139,7 @@
|
|
|
137
139
|
"cratis/chronicle/client-dotnet",
|
|
138
140
|
"cratis/chronicle/core",
|
|
139
141
|
"cratis/fundamentals",
|
|
142
|
+
"cratis/screenplay",
|
|
140
143
|
"cratis/specifications/dotnet"
|
|
141
144
|
],
|
|
142
145
|
"languages": [
|
|
@@ -152,6 +155,7 @@
|
|
|
152
155
|
"cratis/components",
|
|
153
156
|
"cratis/fundamentals",
|
|
154
157
|
"cratis/language/csharp",
|
|
158
|
+
"cratis/screenplay",
|
|
155
159
|
"cratis/specifications/dotnet",
|
|
156
160
|
"cratis/specifications/typescript"
|
|
157
161
|
],
|
|
@@ -165,7 +169,8 @@
|
|
|
165
169
|
"id": "cratis/application/elixir",
|
|
166
170
|
"composes": [
|
|
167
171
|
"cratis/chronicle/client-elixir",
|
|
168
|
-
"cratis/language/elixir"
|
|
172
|
+
"cratis/language/elixir",
|
|
173
|
+
"cratis/screenplay"
|
|
169
174
|
],
|
|
170
175
|
"languages": [
|
|
171
176
|
"elixir"
|
|
@@ -176,7 +181,8 @@
|
|
|
176
181
|
"composes": [
|
|
177
182
|
"cratis/arc/client-kotlin",
|
|
178
183
|
"cratis/chronicle/client-java",
|
|
179
|
-
"cratis/language/java"
|
|
184
|
+
"cratis/language/java",
|
|
185
|
+
"cratis/screenplay"
|
|
180
186
|
],
|
|
181
187
|
"languages": [
|
|
182
188
|
"java"
|
|
@@ -187,7 +193,8 @@
|
|
|
187
193
|
"composes": [
|
|
188
194
|
"cratis/arc/client-kotlin",
|
|
189
195
|
"cratis/chronicle/client-kotlin",
|
|
190
|
-
"cratis/language/kotlin"
|
|
196
|
+
"cratis/language/kotlin",
|
|
197
|
+
"cratis/screenplay"
|
|
191
198
|
],
|
|
192
199
|
"languages": [
|
|
193
200
|
"kotlin"
|
|
@@ -214,6 +221,7 @@
|
|
|
214
221
|
"composes": [
|
|
215
222
|
"cratis/chronicle/client-typescript",
|
|
216
223
|
"cratis/language/typescript",
|
|
224
|
+
"cratis/screenplay",
|
|
217
225
|
"cratis/specifications/typescript"
|
|
218
226
|
],
|
|
219
227
|
"languages": [
|