@cratis/pi 7.28.1 → 7.29.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -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 select = (id: string, explicit: boolean): void => {
73
- if (selected.has(id)) return;
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
- strings(profile.composes).forEach(child => select(child, false));
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 { project: physicalProject, root: root === undefined ? undefined : physicalRoot(physicalProject, root) };
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
- const activeConnection = connect(selection.project);
64
- connection = activeConnection;
67
+ let activeConnection: StdioConnection;
68
+ let tools: DiscoveredTool[];
65
69
  try {
66
- const tools = await discover(activeConnection);
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
- Application profiles do not automatically compose `cratis/screenplay`.
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
  ```
@@ -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.
@@ -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": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cratis/pi",
3
- "version": "7.28.1",
3
+ "version": "7.29.1",
4
4
  "description": "Configuration-aware Cratis AI integration for Pi",
5
5
  "type": "module",
6
6
  "files": [