@cratis/pi 7.103.6 → 7.104.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.
Files changed (34) hide show
  1. package/package/corpus/agents/screenplay-modeler.md +1 -0
  2. package/package/corpus/harnesses/copilot/instructions/profiles.instructions.md +6 -2
  3. package/package/corpus/harnesses/pi/agents/screenplay-modeler.md +1 -0
  4. package/package/corpus/harnesses/pi/extensions/cratis-mcp/ServerBridge.ts +7 -5
  5. package/package/corpus/harnesses/pi/extensions/cratis-mcp/process.ts +15 -3
  6. package/package/corpus/rules/profiles.md +6 -2
  7. package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/SKILL.md +30 -4
  8. package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/references/dotnet-nuget.md +136 -11
  9. package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/scripts/cratis-local-packages.sh +1143 -49
  10. package/package/corpus/skills/cratis-screenplay-captures-and-reactions/SKILL.md +2 -2
  11. package/package/corpus/skills/cratis-screenplay-command-surface/SKILL.md +2 -2
  12. package/package/corpus/skills/cratis-screenplay-event-modeling/SKILL.md +1 -1
  13. package/package/corpus/skills/cratis-screenplay-event-modeling/references/file-layout.md +15 -6
  14. package/package/corpus/skills/cratis-screenplay-event-modeling/verification.json +4 -1
  15. package/package/corpus/skills/cratis-screenplay-model-authoring/SKILL.md +7 -3
  16. package/package/corpus/skills/cratis-screenplay-model-authoring/references/mcp-loop.md +15 -4
  17. package/package/corpus/skills/cratis-screenplay-model-authoring/references/mcp-tools.md +49 -3
  18. package/package/corpus/skills/cratis-screenplay-model-authoring/references/repairs.md +27 -3
  19. package/package/corpus/skills/cratis-screenplay-model-authoring/verification.json +14 -0
  20. package/package/corpus/skills/cratis-screenplay-model-review/SKILL.md +6 -0
  21. package/package/corpus/skills/cratis-screenplay-model-review/references/checklist.md +1 -1
  22. package/package/corpus/skills/cratis-screenplay-model-review/verification.json +2 -0
  23. package/package/corpus/skills/cratis-screenplay-modeling-lifecycle/SKILL.md +4 -0
  24. package/package/corpus/skills/cratis-screenplay-projections/SKILL.md +4 -5
  25. package/package/corpus/skills/cratis-screenplay-read-surface/SKILL.md +4 -5
  26. package/package/corpus/skills/cratis-screenplay-specifications/SKILL.md +2 -2
  27. package/package/corpus/skills/cratis-screenplay-toolchain/SKILL.md +6 -0
  28. package/package/corpus/skills/cratis-screenplay-toolchain/references/cheat-sheet.md +13 -0
  29. package/package/corpus/skills/cratis-screenplay-toolchain/references/diagnostics.md +10 -2
  30. package/package/corpus/skills/cratis-screenplay-toolchain/verification.json +3 -0
  31. package/package/corpus/skills/cratis-screenplay-ui-composition/SKILL.md +8 -2
  32. package/package/corpus/skills/cratis-screenplay-ui-composition/references/ui-profile-and-theme.md +28 -0
  33. package/package/corpus/skills/cratis-screenplay-ui-composition/verification.json +4 -0
  34. package/package.json +1 -1
@@ -33,6 +33,7 @@ After reading these instructions, read each file listed under `process.agents.sc
33
33
 
34
34
  ## Method
35
35
 
36
+ - Reuse existing model/workspace roots. Code-only `Source/` or `src/` is not a model root; absent model/workspace state, use repository-root `Screenplay/`, never `.cratis/`. Read model-authoring's MCP root-selection/version guidance before switching roots.
36
37
  - Load `cratis-screenplay-modeling-lifecycle`, then exactly one phase skill per step: discovery, slice design, streams and consistency, automations and translations, scenario coverage. A legacy system enters through `cratis-screenplay-legacy-extraction` instead of discovery and modeling; later runs re-enter by recorded source identity, widen the candidate in layered passes, and promote locators, decisions and open questions into notes and `documentation` before acceptance (`references/re-entry-and-passes.md` there). Syntax, versions and verdict commands: `cratis-screenplay-toolchain`. Load the phase skill before authoring a construct family.
37
38
  - For a change to an existing model, start from an impact walkthrough (`cratis-screenplay-model-review` `references/walkthrough.md`) so the brief names every affected slice.
38
39
  - Write declarations in story order, one slice after another, never batched by kind. After each batch read `PLAY0516` and `PLAY0517` and repair, or record why the order stays (`cratis-screenplay-slice-design` `references/slicing.md`).
@@ -313,8 +313,12 @@ The profile also selects the corpus-owned Screenplay MCP declaration from
313
313
  `mcp-servers.json`. The Cratis CLI hosts the server as `cratis screenplay mcp`
314
314
  and registers a scoped entry for supported clients without replacing their other
315
315
  servers. Inspect install/status results for adapter support or configuration
316
- conflicts. The model is wherever the project keeps its `.play` files, else under `Source/` or
317
- `src/`, else in a `Screenplay/` folder at the repository root; never under `.cratis/`.
316
+ conflicts. Reuse the project's existing model/workspace root. A code-only `Source/`
317
+ or `src/` is not a model root; without model/workspace state use repository-root
318
+ `Screenplay/`, never `.cratis/`. Requires Cratis CLI 3.93.1 (`cratis --version`) for
319
+ CLI discovery and Screenplay 4.236.1 (`screenplay --version`) for standalone MCP
320
+ discovery; older versions can choose code-only folders. Fixed-root sibling admission
321
+ is narrowly scoped, not an arbitrary switch: see [root selection](../../../skills/cratis-screenplay-event-modeling/references/file-layout.md#locate-the-model).
318
322
  Project-owned configuration can name a root explicitly.
319
323
 
320
324
  Like `allowDependencyManifestWrites`, `modelFirst` (`frozen`, `reason`) and `process` (`decisions`, `workItems`, `acceptance`, `agents`) are project-owned keys in `.cratis/ai.json` that install and update preserve. Only `modelFirst.frozen` is read by code; the lifecycle skill's `references/project-process.md` defines the keys, defaults and routing.
@@ -28,6 +28,7 @@ After reading these instructions, read each file listed under `process.agents.sc
28
28
 
29
29
  ## Method
30
30
 
31
+ - Reuse existing model/workspace roots. Code-only `Source/` or `src/` is not a model root; absent model/workspace state, use repository-root `Screenplay/`, never `.cratis/`. Read model-authoring's MCP root-selection/version guidance before switching roots.
31
32
  - Load `cratis-screenplay-modeling-lifecycle`, then exactly one phase skill per step: discovery, slice design, streams and consistency, automations and translations, scenario coverage. A legacy system enters through `cratis-screenplay-legacy-extraction` instead of discovery and modeling; later runs re-enter by recorded source identity, widen the candidate in layered passes, and promote locators, decisions and open questions into notes and `documentation` before acceptance (`references/re-entry-and-passes.md` there). Syntax, versions and verdict commands: `cratis-screenplay-toolchain`. Load the phase skill before authoring a construct family.
32
33
  - For a change to an existing model, start from an impact walkthrough (`cratis-screenplay-model-review` `references/walkthrough.md`) so the brief names every affected slice.
33
34
  - Write declarations in story order, one slice after another, never batched by kind. After each batch read `PLAY0516` and `PLAY0517` and repair, or record why the order stays (`cratis-screenplay-slice-design` `references/slicing.md`).
@@ -44,22 +44,24 @@ export function createServerBridge(pi: ExtensionAPI, corpus: string, connect: (p
44
44
  connections.clear();
45
45
  deactivate();
46
46
  };
47
- interface Started { server: CorpusServer; connection: StdioConnection; catalog?: ServerCatalog; messages: string[] }
47
+ interface Started { server: CorpusServer; connection?: StdioConnection; catalog?: ServerCatalog; messages: string[] }
48
48
  /** Connect and discover only; registration happens afterwards in selection order so name claims are deterministic. */
49
49
  const start = async (context: ExtensionContext, server: CorpusServer): Promise<Started> => {
50
- const connection = connect(context.cwd, server);
51
- connections.set(server.id, connection);
50
+ let connection: StdioConnection | undefined;
52
51
  try {
52
+ connection = connect(context.cwd, server);
53
+ connections.set(server.id, connection);
53
54
  const catalog = await withDeadline(discoverServer(connection, server.id), deadlineMilliseconds, server.id);
54
55
  if (connections.get(server.id) !== connection || connection.stopped) throw new Error(`MCP server '${server.id}' session ended during discovery.`);
55
56
  return { server, connection, catalog, messages: [...catalog.messages] };
56
57
  } catch (error) {
57
- connection.dispose();
58
- if (connections.get(server.id) === connection) connections.delete(server.id);
58
+ connection?.dispose();
59
+ if (connection && connections.get(server.id) === connection) connections.delete(server.id);
59
60
  return { server, connection, messages: [`MCP server '${server.id}' is unavailable: ${error instanceof Error ? error.message : String(error)}`] };
60
61
  }
61
62
  };
62
63
  const register = ({ server, connection, catalog, messages }: Started, claimed: Set<string>): void => {
64
+ if (!connection) return;
63
65
  const existing = new Set(pi.getAllTools().map(tool => tool.name));
64
66
  for (const tool of catalog?.tools ?? []) {
65
67
  if (claimed.has(tool.nativeName) || (existing.has(tool.nativeName) && !registered.has(tool.nativeName))) {
@@ -2,6 +2,8 @@
2
2
  // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
3
 
4
4
  import { execFile, spawn } from 'node:child_process';
5
+ import { existsSync } from 'node:fs';
6
+ import { dirname, join } from 'node:path';
5
7
  import type { CorpusServer } from './CorpusServer.ts';
6
8
  import { StdioConnection } from './StdioConnection.ts';
7
9
 
@@ -45,9 +47,19 @@ export function startConnection(project: string, launch: typeof spawn = spawn):
45
47
  }));
46
48
  }
47
49
 
48
- /** Launch a corpus server verbatim (no shell, no interpolation): the default environment plus its declared literal env only. */
49
- export function startServer(project: string, server: CorpusServer, launch: typeof spawn = spawn): StdioConnection {
50
- return new StdioConnection(launch(server.command, server.args, {
50
+ /** Launch a corpus server without a shell or interpolation; Windows npx uses npm's JavaScript entry point. */
51
+ export function startServer(project: string, server: CorpusServer, launch: typeof spawn = spawn, platform: NodeJS.Platform = process.platform, fileExists: (path: string) => boolean = existsSync): StdioConnection {
52
+ let command = server.command;
53
+ let args = server.args;
54
+ if (platform === 'win32' && /^npx(?:\.cmd)?$/i.test(command)) {
55
+ const entryPoint = join(dirname(process.execPath), 'node_modules', 'npm', 'bin', 'npx-cli.js');
56
+ if (!fileExists(entryPoint)) {
57
+ throw new Error(`MCP server '${server.id}' requires npm's npx-cli.js at '${entryPoint}'. Install Node.js with npm; Windows command shims cannot be launched without a shell.`);
58
+ }
59
+ command = process.execPath;
60
+ args = [entryPoint, ...args];
61
+ }
62
+ return new StdioConnection(launch(command, args, {
51
63
  cwd: project,
52
64
  env: { ...childEnvironment(), ...server.env },
53
65
  shell: false,
@@ -312,8 +312,12 @@ The profile also selects the corpus-owned Screenplay MCP declaration from
312
312
  `mcp-servers.json`. The Cratis CLI hosts the server as `cratis screenplay mcp`
313
313
  and registers a scoped entry for supported clients without replacing their other
314
314
  servers. Inspect install/status results for adapter support or configuration
315
- conflicts. The model is wherever the project keeps its `.play` files, else under `Source/` or
316
- `src/`, else in a `Screenplay/` folder at the repository root; never under `.cratis/`.
315
+ conflicts. Reuse the project's existing model/workspace root. A code-only `Source/`
316
+ or `src/` is not a model root; without model/workspace state use repository-root
317
+ `Screenplay/`, never `.cratis/`. Requires Cratis CLI 3.93.1 (`cratis --version`) for
318
+ CLI discovery and Screenplay 4.236.1 (`screenplay --version`) for standalone MCP
319
+ discovery; older versions can choose code-only folders. Fixed-root sibling admission
320
+ is narrowly scoped, not an arbitrary switch: see [root selection](../skills/cratis-screenplay-event-modeling/references/file-layout.md#locate-the-model).
317
321
  Project-owned configuration can name a root explicitly.
318
322
 
319
323
  Like `allowDependencyManifestWrites`, `modelFirst` (`frozen`, `reason`) and `process` (`decisions`, `workItems`, `acceptance`, `agents`) are project-owned keys in `.cratis/ai.json` that install and update preserve. Only `modelFirst.frozen` is read by code; the lifecycle skill's `references/project-process.md` defines the keys, defaults and routing.
@@ -16,7 +16,16 @@ Use real locally built packages to prove a producer fix at its consumer's packag
16
16
  boundary. A **producer** builds the changed library or image; a **consumer** is the
17
17
  downstream repository that reproduces the bug. Start before opening the producer
18
18
  PR whose purpose is that consumer bug, rather than publishing just to find out.
19
- Also use this loop to compare the consumer against lower-layer main.
19
+ Also use this loop for:
20
+
21
+ - Consumer-blocking fixes, dependency features, and breaking-change trials.
22
+ - Release candidates in their main consumer (Specifications in Fundamentals).
23
+ - Cross-repository bisects and build/test timing comparisons against lower-layer main.
24
+ - Chained upgrades (Specifications → Fundamentals → Arc/Chronicle) without releasing each hop.
25
+ - Release blockers: trying Specifications in Fundamentals can expose a generator bug before publishing.
26
+
27
+ Prefer a release/preview for trivial changes, external consumers unable to use
28
+ the local feed, or CI-only behavior this loop cannot reproduce.
20
29
 
21
30
  **Local verification replaces waiting, not shipping.** After local proof, release
22
31
  the producer through the normal authorized workflow and bump the consumer to the
@@ -78,6 +87,8 @@ Format: `<next-patch>-local.<12-digit-UTC-minute>.g<sha7>`, based on the highest
78
87
  downgrades. Confirm tags cover the consumer's released constraints. With no tag,
79
88
  the helper warns and falls back to a base that must be checked before proceeding.
80
89
  The `g` prefix keeps even an all-digit SHA nonnumeric in SemVer.
90
+ On `release/5.0`, use `stamp --base-version 5.0.0 "$producer"` (also with `--train`);
91
+ the strict SemVer core overrides tag discovery verbatim. Ensure it meets consumer minima.
81
92
  Use the identical stamp for packages and images. A changed build needs a distinct
82
93
  stamp; do not overwrite a cached version (the minute and commit determine it).
83
94
 
@@ -126,9 +137,18 @@ export CHRONICLE_KERNEL_VERSION="$stamp" # Studio integration specs, if used
126
137
  ```
127
138
 
128
139
  Pass: the helper reports overridden IDs and restore resolves them to local
129
- artifacts. Follow any printed `--configfile` instruction. Run Yarn install after
140
+ artifacts. To isolate NuGet downloads per worktree, set the optional
141
+ `NUGET_PACKAGES` export printed by `use-nuget` before restore and keep it set through
142
+ `clean`; see [versions and cache](references/dotnet-nuget.md#versions-and-cache).
143
+ Follow any printed `--configfile` instruction. Run Yarn install after
130
144
  `use-npm`; it adds file-tarball resolutions, not links to source folders.
131
145
 
146
+ For independent NuGet producer versions, explicitly opt in with
147
+ `verify-pack --composition FILE`, `use-nuget CONSUMER --composition FILE
148
+ [--pin-transitive]`, then `verify-assets CONSUMER --composition FILE`. See the
149
+ [manifest and native-validation workflow](references/dotnet-nuget.md#mixed-version-compositions-opt-in).
150
+ No producer is rebuilt; restore before changing a composition or an active legacy overlay.
151
+
132
152
  ### 5. Build and exercise the consumer
133
153
 
134
154
  From the consumer root, with the printed NuGet environment sourced:
@@ -192,7 +212,13 @@ feed packages, NuGet version folders, discoverable Yarn cache entries, local ima
192
212
  tags and registered sessions; unresolved npm backups block it. Missing Docker/Yarn
193
213
  probes are reported as skipped cleanup, not proof those stores are empty. Default
194
214
  fuse: 500 subjects; investigate an unexpectedly large list instead of raising it.
195
- Prefer `--stamp` to `--all` on shared machines.
215
+ Prefer `--stamp` to `--all`. Legacy cleanup refuses the entire request (exit `2`,
216
+ also in dry-run) for registered live overlay/manifest references; it reports kept
217
+ stamps and paths. Restore/remove references, then retry. This is not an ownership
218
+ registry; unregistered consumers need manual review. To retire a hand-rolled
219
+ `-local.N` feed, restore released pins/sources and switch to helper stamps; review
220
+ old artifacts separately because `clean` does not recognize them. See
221
+ [migration](references/dotnet-nuget.md#retire-a-hand-rolled-feed).
196
222
 
197
223
  ## Configuration and results
198
224
 
@@ -203,7 +229,7 @@ is sourced before defaults. Do not put credentials in it. Environment settings:
203
229
  | --- | --- |
204
230
  | `CRATIS_LOCAL_FEED` | `${XDG_CACHE_HOME:-$HOME/.cache}/cratis/local-feed`; outside tracked files |
205
231
  | `CRATIS_LOCAL_SESSION_DIR` | `<absolute-git-dir>/cratis-local`; per-worktree overlays/backups |
206
- | `NUGET_PACKAGES` | `$HOME/.nuget/packages`; shared cache, unique version folders |
232
+ | `NUGET_PACKAGES` | `$HOME/.nuget/packages`; opt-in per-worktree cache via `use-nuget` hint |
207
233
  | `CRATIS_LOCAL_CLEAN_MAX` | `500`; cleanup subject fuse |
208
234
  | `CRATIS_LOCAL_RUN` | Optional executable wrapper receiving `build <label> -- <command...>` |
209
235
 
@@ -3,6 +3,16 @@
3
3
 
4
4
  # .NET and NuGet
5
5
 
6
+ ## Contents
7
+
8
+ - CPM overlay
9
+ - Source mapping
10
+ - Versions and cache
11
+ - Retire a hand-rolled feed
12
+ - Mixed-version compositions (opt-in)
13
+ - Non-CPM escape
14
+ - Restoration and drift
15
+
6
16
  ## CPM overlay
7
17
 
8
18
  Execute the helper from the entry point. `pack-nuget PRODUCER STAMP TARGET...`
@@ -14,10 +24,11 @@ them from an accidental published fallback.
14
24
  `use-nuget CONSUMER STAMP [ID...]` requires a root `Directory.Packages.props`.
15
25
  Without IDs it selects every package with `<id>.<stamp>.nupkg` in the feed,
16
26
  including transitive dependencies absent from the consumer props. The generated
17
- overlay imports the consumer's real props, uses `PackageVersion Update` for IDs
18
- already pinned there and `PackageVersion Include` for the others, and enables
19
- `CentralPackageTransitivePinningEnabled` so the added IDs pin transitive packages.
20
- An `Update` alone cannot add an absent ID. It does not copy or edit the props in git.
27
+ overlay imports each project's nearest `Directory.Packages.props` and replaces
28
+ owned `PackageVersion` items only when CPM is enabled, honoring nested props files.
29
+ CPM opt-out projects get owned versions through late `PackageReference Update`
30
+ items in `CustomAfterMicrosoftCommonTargets`, chaining any existing targets import;
31
+ consumer files are never edited.
21
32
 
22
33
  ```bash
23
34
  bash "$local" use-nuget "$consumer" "$stamp" Cratis.Chronicle Cratis.Chronicle.CodeAnalysis
@@ -31,8 +42,8 @@ dotnet build path/to/consumer.slnx -c Release --no-restore -warnaserror
31
42
  Transitive pinning stays as the consumer sets it; `--pin-transitive` forces it on so stamped IDs the
32
43
  consumer does not pin still take effect (it can raise NU1109 floors).
33
44
 
34
- The printed properties are `DirectoryPackagesPropsPath` and
35
- `RestoreAdditionalProjectSources`. For an IDE, launch it from that shell, or pass
45
+ The printed properties include `DirectoryPackagesPropsPath`,
46
+ `RestoreAdditionalProjectSources` and `CustomAfterMicrosoftCommonTargets`. For an IDE, launch it from that shell, or pass
36
47
  the printed `-p:` properties explicitly to its build. Verify restore uses the
37
48
  stamp in `obj/project.assets.json`; shell environment changes do not reconfigure
38
49
  an already-running IDE. A config overlay printed below must also reach IDE restore.
@@ -59,11 +70,123 @@ A stale highest tag or dependency with a newer minimum needs investigation, not
59
70
  warning suppression. The suffix is `-local.<12-digit-UTC-minute>.g<sha7>`; the `g` prefix makes an
60
71
  all-digit SHA unambiguously nonnumeric in SemVer. Dirty edits are not encoded.
61
72
  Wait for a distinct UTC minute or commit before repacking changed contents.
73
+ On a major-version branch, use `bash "$local" stamp --base-version 5.0.0 "$producer"`
74
+ (also with `--train`). It accepts only a SemVer core without leading zeros,
75
+ prerelease or metadata and uses it verbatim; no patch increment is added.
76
+ An explicit base still must meet the consumer's minimum.
77
+
78
+ Prefer a private cache per consumer worktree so another checkout's cached package
79
+ cannot mask a same-version repack. The optional `use-nuget` hint uses a sibling of
80
+ the session directory, not a tracked path:
81
+
82
+ ```bash
83
+ export NUGET_PACKAGES="$(git -C "$consumer" rev-parse --absolute-git-dir)/cratis-local-nuget"
84
+ dotnet restore path/to/consumer.slnx
85
+ # After restoring overlays/removing live local references, keep this export for:
86
+ bash "$local" clean "$consumer" --stamp "$stamp"
87
+ bash "$local" clean "$consumer" --stamp "$stamp" --apply
88
+ unset NUGET_PACKAGES
89
+ ```
90
+
91
+ A private cache still reuses its own downloads: changed bits need a distinct stamp,
92
+ or reviewed cleanup of that stamp before repacking. It does not make same-version
93
+ repacking safe. `clean` honors the active `NUGET_PACKAGES`, touches only recognized
94
+ `-local`/`-train` version folders there, and leaves released versions alone. With
95
+ no export, it uses the shared `$HOME/.nuget/packages` cache; never clear that whole
96
+ cache. Restore local overlays and remove live manifest references before cleanup;
97
+ registered live references refuse the entire request even in dry-run.
98
+
99
+ The helper uses a folder feed, so NuGet's HTTP cache is not involved. When diagnosing
100
+ an HTTP feed instead, `dotnet restore --no-http-cache` bypasses its HTTP cache only;
101
+ it does not bypass packages already in `NUGET_PACKAGES`. Use a fresh worktree cache
102
+ or review stamp-scoped cleanup too, and preserve any required `--configfile` option.
103
+
104
+ ## Retire a hand-rolled feed
105
+
106
+ Stop packing new `-local.N` versions. Restore consumers' released pins and sources,
107
+ then use helper-generated stamps and overlays for the next trial. Confirm restore
108
+ resolves the helper stamp and run `check` before committing. Inventory the old
109
+ feed separately: `clean` recognizes only its local/train grammar, not `-local.N`.
110
+ Remove old artifacts only after checking all their consumers and reviewing exact
111
+ targets; never rename old packages into a helper stamp or clear a whole shared cache.
112
+
113
+ ## Mixed-version compositions (opt-in)
114
+
115
+ Use a composition when products have independent local versions. It never packs
116
+ or rebuilds producers. Write a tab-separated manifest under `.ai-work/`:
117
+
118
+ ```text
119
+ # Separate fields with literal tabs.
120
+ producer Chronicle 22.138.1-local.202610240544.gd1ad215 /absolute/worktree/Chronicle
121
+ producer Screenplay 0.2.2-local.202610240544.g91916a3 /absolute/worktree/Screenplay
122
+ producer Stage 0.1.18-local.202610240544.g524bdce /absolute/worktree/Stage
123
+ package Chronicle Cratis.Chronicle.InProcess
124
+ package Screenplay Cratis.Screenplay
125
+ package Stage Cratis.Stage
126
+ ```
127
+
128
+ Each producer needs a valid local/train stamp, an existing absolute Git worktree
129
+ root and at least one package. Paths declare provenance, not cryptographic artifact
130
+ authenticity. Package IDs have one owner, case-insensitively.
131
+ Declare the complete local dependency closure. Only blank lines and lines starting
132
+ with `#` are ignored. No npm rows or implicit merging with an active overlay.
133
+
134
+ ```bash
135
+ bash "$local" verify-pack --composition .ai-work/composition.tsv --json
136
+ bash "$local" use-nuget "$consumer" --composition .ai-work/composition.tsv --pin-transitive
137
+ # Source the printed env; use the printed --configfile for consumer restore.
138
+ # Run the consumer's own restore/build/specs, then:
139
+ bash "$local" verify-assets "$consumer" --composition .ai-work/composition.tsv --json
140
+ bash "$local" restore "$consumer"
141
+ ```
62
142
 
63
- The existing global cache is shared safely only because local versions have
64
- unique directories. Do not repack changed bits under the same version. Use
65
- `clean --stamp "$stamp"` to list that stamp's version folders, then `--apply`
66
- after review; do not delete the entire NuGet cache.
143
+ `verify-assets` reads every `obj/project.assets.json` under the consumer and fails
144
+ closed on any project that resolved an owned ID at another version, including stale
145
+ assets from an earlier restore. Restore every project under the consumer with the
146
+ printed config, or remove the `obj` folders of projects outside the build, before
147
+ running it.
148
+
149
+ Transitive pinning is optional. The overlay imports the consumer's CPM file and
150
+ removes/re-includes owned IDs at exact producer versions. Its generated NuGet
151
+ config gives local IDs exclusive exact source mappings, removes conflicting remote
152
+ exact mappings and preserves unrelated sources/mappings/settings. Remote wildcards
153
+ remain less specific. Always pass the printed config during restore. Consumer
154
+ files are never edited. Ambiguous or inherited-only source configuration fails
155
+ closed instead of guessing.
156
+
157
+ Validation requires Node, `unzip` and the selected .NET SDK. A scratch MSBuild task
158
+ loads its `NuGet.Versioning.dll`: `NuGetVersion.TryParse`, `VersionRange.TryParse`
159
+ and `VersionRange.Satisfies` supply native semantics, never a range approximation.
160
+ Its `NuGet.Frameworks.dll` supplies `NuGetFramework.Parse` and
161
+ `FrameworkReducer.GetNearest` for selecting the applicable dependency group.
162
+ Nuspec XML is parsed structurally, across dependency groups. For example,
163
+ `[22.136.4,23.0.0)` admits the selected Chronicle prerelease above. Missing/corrupt
164
+ packages, mismatched metadata, malformed/incompatible ranges and undeclared local
165
+ dependencies fail. Nothing is rebuilt to make a range pass.
166
+
167
+ Assets verification examines every `obj/project.assets.json` and target, checks
168
+ package versions against `libraries`, and validates dependency edges against their
169
+ resolved target. The nearest native framework group must have its owned dependencies
170
+ consumed and represented by target edges. Owned IDs must resolve at their producer's exact version. Unused
171
+ declared IDs need not appear, but at least one owned ID must be consumed. Malformed
172
+ or failed-restore assets, missing dependency nodes and undeclared local packages fail.
173
+ A range violation on an edge where neither end is an owned ID is NuGet's own NU1608
174
+ case and exists without the overlay too, so it is printed as a `WARNING:` on stderr
175
+ (and listed under `warnings` with `--json`) instead of refusing the composition.
176
+ Any violation on an edge to or from an owned ID still fails.
177
+
178
+ Activation stages the normalized manifest, props, config and env together. A shared
179
+ mutation lock and journal protect publication and registration. The next mutation
180
+ rolls back incomplete work or finishes committed work after interruption. Changed
181
+ files, unknown ownership and live locks are preserved and refused: inspect rather
182
+ than deleting a lock by age. Identical activation is a no-op; restore before changing
183
+ a composition. Incompatible legacy NuGet/npm sessions are refused with backups
184
+ untouched. Active compositions retain member artifacts against `clean`; restore
185
+ before cleaning any member. Repeated restore is safe and never edits consumer files.
186
+
187
+ Exit codes: `0` success; `1` findings; `2` invalid input, unavailable native validation
188
+ or unsafe state. Composition JSON is `{"packages":[{"id":...,"version":...,"producer":...}]}`,
189
+ with no global `stamp`. Legacy JSON and uniform-stamp commands remain unchanged.
67
190
 
68
191
  ## Non-CPM escape
69
192
 
@@ -75,12 +198,14 @@ graph. Never edit versions intended to ship merely to make local proof work.
75
198
 
76
199
  ## Restoration and drift
77
200
 
78
- `restore` removes session props, generated config and env files, then runs `check`.
201
+ `restore` removes session props, targets, generated config and env files, then runs `check`.
79
202
  It cannot unset variables in the calling shell:
80
203
 
81
204
  ```bash
82
205
  bash "$local" restore "$consumer"
83
206
  unset DirectoryPackagesPropsPath RestoreAdditionalProjectSources
207
+ export CustomAfterMicrosoftCommonTargets="${_CratisLocalPreviousAfterMicrosoftCommonTargets:-}"
208
+ unset _CratisLocalPreviousAfterMicrosoftCommonTargets
84
209
  bash "$local" check "$consumer"
85
210
  ```
86
211