@neocompose/cli 0.26.5 → 0.27.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/CHANGELOG.md CHANGED
@@ -1,5 +1,43 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.27.1] - 2026-08-12
4
+
5
+ ### Fixed
6
+
7
+ - Treat member-level `required` as non-semantic for Function and NSFunction
8
+ records during CLI reads and comparisons. Historical records stored with
9
+ `required: true` no longer produce permanent phantom changes in `neo pull`,
10
+ `neo status`, or `neo push --dry-run`; return nullability remains represented
11
+ by `returnTypeInfo.required`.
12
+ - Preserve independently mergeable edits on both sides of a `neo pull`
13
+ conflict marker, including nested fields and stable-ID collection entries.
14
+ Resolving with `neo resolve --mine` or `--theirs` now discards only genuinely
15
+ contested values instead of clean changes made on the other side.
16
+
17
+ ## [0.27.0] - 2026-08-10
18
+
19
+ ### Added
20
+
21
+ - `neo logout [--api <url>]`: targeted local credential deletion for exactly
22
+ one API origin — the namespaced OS-keychain entry and the credentials-file
23
+ row — plus best-effort server session revocation. Other origins, other
24
+ namespaces, and the rest of the config home are never touched.
25
+ - `NEO_COMPOSE_CONFIG_HOME`: an absolute directory that replaces
26
+ `$XDG_CONFIG_HOME/neo-compose` for Neo credential state only, so isolated
27
+ environments (P53 agent rigs) keep their own credential store without
28
+ changing the ambient XDG configuration of unrelated tools.
29
+ - `NEO_COMPOSE_CREDENTIAL_NAMESPACE`: scopes the OS-keychain account
30
+ (`<namespace>::<api origin>`) so isolated environments sharing one OS user
31
+ cannot read or delete each other's tokens.
32
+
33
+ ### Fixed
34
+
35
+ - The credential-store docstring described a two-level precedence with the
36
+ OS keychain "layering in later"; the keychain layer has existed for some
37
+ time. The docstring now matches the real precedence (env var, then
38
+ keychain, then credentials file) and documents that the metadata row is
39
+ written even when the keychain holds the token.
40
+
3
41
  ## [0.26.5] - 2026-08-10
4
42
 
5
43
  ### Changed
package/README.md CHANGED
@@ -419,6 +419,7 @@ authorization boundary.
419
419
  | `--version` | Print the installed CLI package version. |
420
420
  | `login` | Authenticate the selected profile. |
421
421
  | `whoami` | Inspect the selected profile. |
422
+ | `logout [--api <url>]` | Delete the stored credential for one API origin and revoke its session. |
422
423
  | `init --project <id> [--version <id>]` | Create a format-4 working copy and perform its first reset pull. |
423
424
  | `doctor` | Validate format/compiler/editor/source/file contracts; no .NET discovery. |
424
425
  | `pull [--force\|--reset] [--regenerate-source-names]` | Merge remote changes or regenerate canonical source, names, and binaries. |
package/dist/neo.mjs CHANGED
@@ -44,7 +44,12 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
44
44
  // src/token-store.ts
45
45
  var token_store_exports = {};
46
46
  __export(token_store_exports, {
47
+ NEO_CONFIG_HOME_ENV_VAR: () => NEO_CONFIG_HOME_ENV_VAR,
48
+ NEO_CREDENTIAL_NAMESPACE_ENV_VAR: () => NEO_CREDENTIAL_NAMESPACE_ENV_VAR,
47
49
  NEO_TOKEN_ENV_VAR: () => NEO_TOKEN_ENV_VAR,
50
+ credentialsDir: () => credentialsDir,
51
+ deleteCredential: () => deleteCredential,
52
+ keychainAccount: () => keychainAccount,
48
53
  loadCredential: () => loadCredential,
49
54
  loadToken: () => loadToken,
50
55
  saveCredential: () => saveCredential
@@ -54,12 +59,22 @@ import {
54
59
  existsSync,
55
60
  mkdirSync,
56
61
  readFileSync,
62
+ rmSync,
57
63
  writeFileSync
58
64
  } from "node:fs";
59
65
  import { execFileSync } from "node:child_process";
60
66
  import { homedir } from "node:os";
61
- import { join } from "node:path";
67
+ import { isAbsolute, join } from "node:path";
62
68
  function credentialsDir() {
69
+ const configHome = process.env[NEO_CONFIG_HOME_ENV_VAR];
70
+ if (configHome !== void 0 && configHome !== "") {
71
+ if (!isAbsolute(configHome)) {
72
+ throw new Error(
73
+ `${NEO_CONFIG_HOME_ENV_VAR} must be an absolute directory path, got "${configHome}".`
74
+ );
75
+ }
76
+ return configHome;
77
+ }
63
78
  const xdg = process.env.XDG_CONFIG_HOME;
64
79
  const base = xdg !== void 0 && xdg !== "" ? xdg : join(homedir(), ".config");
65
80
  return join(base, "neo-compose");
@@ -80,6 +95,11 @@ function readCredentialsFile() {
80
95
  }
81
96
  return file;
82
97
  }
98
+ function keychainAccount(apiBaseUrl) {
99
+ const namespace = process.env[NEO_CREDENTIAL_NAMESPACE_ENV_VAR];
100
+ if (namespace === void 0 || namespace === "") return apiBaseUrl;
101
+ return `${namespace}::${apiBaseUrl}`;
102
+ }
83
103
  function keychainSet(account, secret) {
84
104
  if (process.platform !== "darwin") return false;
85
105
  try {
@@ -116,8 +136,24 @@ function keychainGet(account) {
116
136
  return null;
117
137
  }
118
138
  }
139
+ function keychainDelete(account) {
140
+ if (process.platform !== "darwin") return false;
141
+ try {
142
+ execFileSync(
143
+ "security",
144
+ ["delete-generic-password", "-s", KEYCHAIN_SERVICE, "-a", account],
145
+ { stdio: "ignore" }
146
+ );
147
+ return true;
148
+ } catch {
149
+ return false;
150
+ }
151
+ }
119
152
  function saveCredential(credential) {
120
- const inKeychain = keychainSet(credential.apiBaseUrl, credential.token);
153
+ const inKeychain = keychainSet(
154
+ keychainAccount(credential.apiBaseUrl),
155
+ credential.token
156
+ );
121
157
  mkdirSync(credentialsDir(), { recursive: true, mode: 448 });
122
158
  const file = readCredentialsFile();
123
159
  file.credentials[credential.apiBaseUrl] = inKeychain ? { ...credential, token: "" } : credential;
@@ -132,7 +168,7 @@ function saveCredential(credential) {
132
168
  function loadToken(apiBaseUrl) {
133
169
  const envToken = process.env[NEO_TOKEN_ENV_VAR];
134
170
  if (envToken !== void 0 && envToken !== "") return envToken;
135
- const fromKeychain = keychainGet(apiBaseUrl);
171
+ const fromKeychain = keychainGet(keychainAccount(apiBaseUrl));
136
172
  if (fromKeychain !== null) return fromKeychain;
137
173
  const file = readCredentialsFile();
138
174
  const credential = file.credentials[apiBaseUrl];
@@ -143,11 +179,38 @@ function loadCredential(apiBaseUrl) {
143
179
  const file = readCredentialsFile();
144
180
  return file.credentials[apiBaseUrl] ?? null;
145
181
  }
146
- var NEO_TOKEN_ENV_VAR, KEYCHAIN_SERVICE;
182
+ function deleteCredential(apiBaseUrl) {
183
+ const account = keychainAccount(apiBaseUrl);
184
+ const fromKeychain = keychainGet(account);
185
+ const file = readCredentialsFile();
186
+ const fileEntry = file.credentials[apiBaseUrl];
187
+ const token = fromKeychain ?? (fileEntry !== void 0 && fileEntry.token !== "" ? fileEntry.token : null);
188
+ const deletedKeychainEntry = keychainDelete(account);
189
+ let deletedFileEntry = false;
190
+ if (fileEntry !== void 0) {
191
+ delete file.credentials[apiBaseUrl];
192
+ deletedFileEntry = true;
193
+ const path = credentialsPath();
194
+ if (Object.keys(file.credentials).length === 0) {
195
+ rmSync(path, { force: true });
196
+ } else {
197
+ writeFileSync(path, `${JSON.stringify(file, null, 2)}
198
+ `, {
199
+ encoding: "utf8",
200
+ mode: 384
201
+ });
202
+ chmodSync(path, 384);
203
+ }
204
+ }
205
+ return { token, deletedKeychainEntry, deletedFileEntry };
206
+ }
207
+ var NEO_TOKEN_ENV_VAR, NEO_CONFIG_HOME_ENV_VAR, NEO_CREDENTIAL_NAMESPACE_ENV_VAR, KEYCHAIN_SERVICE;
147
208
  var init_token_store = __esm({
148
209
  "src/token-store.ts"() {
149
210
  "use strict";
150
211
  NEO_TOKEN_ENV_VAR = "NEO_COMPOSE_TOKEN";
212
+ NEO_CONFIG_HOME_ENV_VAR = "NEO_COMPOSE_CONFIG_HOME";
213
+ NEO_CREDENTIAL_NAMESPACE_ENV_VAR = "NEO_COMPOSE_CREDENTIAL_NAMESPACE";
151
214
  KEYCHAIN_SERVICE = "neo-compose-cli";
152
215
  }
153
216
  });
@@ -427,6 +490,44 @@ async function runLogin(options) {
427
490
  return;
428
491
  }
429
492
  }
493
+ async function runLogout(apiBaseUrl) {
494
+ const { deleteCredential: deleteCredential2 } = await Promise.resolve().then(() => (init_token_store(), token_store_exports));
495
+ const deleted = deleteCredential2(apiBaseUrl);
496
+ if (deleted.token === null && !deleted.deletedKeychainEntry && !deleted.deletedFileEntry) {
497
+ console.log(`No credentials stored for "${apiBaseUrl}".`);
498
+ return;
499
+ }
500
+ if (deleted.token !== null) {
501
+ try {
502
+ const response = await fetch(new URL("/api/auth/sign-out", apiBaseUrl), {
503
+ body: "{}",
504
+ headers: {
505
+ Authorization: `Bearer ${deleted.token}`,
506
+ "Content-Type": "application/json"
507
+ },
508
+ method: "POST"
509
+ });
510
+ if (response.ok) {
511
+ console.log(`Revoked the server session at ${apiBaseUrl}.`);
512
+ } else {
513
+ console.log(
514
+ `Server session revocation returned ${response.status}; the local credential was still deleted.`
515
+ );
516
+ }
517
+ } catch {
518
+ console.log(
519
+ "Server session revocation was unreachable; the local credential was still deleted."
520
+ );
521
+ }
522
+ }
523
+ const surfaces = [
524
+ deleted.deletedKeychainEntry ? "Keychain entry" : null,
525
+ deleted.deletedFileEntry ? "credentials file entry" : null
526
+ ].filter((surface) => surface !== null);
527
+ console.log(
528
+ `Logged out of ${apiBaseUrl}${surfaces.length > 0 ? ` (removed ${surfaces.join(" and ")})` : ""}.`
529
+ );
530
+ }
430
531
  async function runWhoami(apiBaseUrl) {
431
532
  const { loadToken: loadToken2 } = await Promise.resolve().then(() => (init_token_store(), token_store_exports));
432
533
  const token = loadToken2(apiBaseUrl);
@@ -12789,13 +12890,13 @@ var init_strict_resolver = __esm({
12789
12890
  scope,
12790
12891
  pos
12791
12892
  );
12792
- const selected2 = intrinsic(
12893
+ const selected = intrinsic(
12793
12894
  "select" /* Select */,
12794
12895
  { collectionPointer: receiver.pointer, function: fn },
12795
12896
  { kind: "list", elementType: resultType, readOnly: true }
12796
12897
  );
12797
12898
  return {
12798
- ...selected2,
12899
+ ...selected,
12799
12900
  entryWritability: inferredReturn.writability,
12800
12901
  writeRoot: inferredReturn.writeRoot
12801
12902
  };
@@ -25750,9 +25851,9 @@ function declaredConstructorNamedArguments(analysis, typeName, used) {
25750
25851
  )
25751
25852
  )
25752
25853
  );
25753
- const selected2 = reachable.length > 0 ? reachable : contract.constructors;
25854
+ const selected = reachable.length > 0 ? reachable : contract.constructors;
25754
25855
  const fields = /* @__PURE__ */ new Map();
25755
- for (const constructor2 of selected2) {
25856
+ for (const constructor2 of selected) {
25756
25857
  for (const parameter4 of constructor2.parameters) {
25757
25858
  if (fields.has(parameter4.name)) continue;
25758
25859
  fields.set(parameter4.name, parameterArgumentField(parameter4));
@@ -29398,14 +29499,14 @@ function sourceLocation(value, environment, field) {
29398
29499
  }
29399
29500
  function spanAndSelectionLocations(spanValue, selectionValue, environment, field) {
29400
29501
  const navigation = spanLocation(spanValue, environment, field);
29401
- const selection = spanLocation(
29502
+ const selection2 = spanLocation(
29402
29503
  selectionValue,
29403
29504
  environment,
29404
29505
  `${field}.selection`
29405
29506
  );
29406
29507
  return {
29407
29508
  ...navigation,
29408
- ...selection.location ? { selectionLocation: selection.location } : {}
29509
+ ...selection2.location ? { selectionLocation: selection2.location } : {}
29409
29510
  };
29410
29511
  }
29411
29512
  function scriptDefinitionLocations(member, environment, field) {
@@ -31121,6 +31222,9 @@ function normalizeDocumentFields(recordKind, value) {
31121
31222
  if (current === void 0) normalized[field] = null;
31122
31223
  }
31123
31224
  if (recordKind === "member") {
31225
+ if (CALLABLE_MEMBER_KINDS.has(normalized.kind)) {
31226
+ normalized.required = false;
31227
+ }
31124
31228
  normalizeTypeInfoBindings(normalized.returnTypeInfo);
31125
31229
  if (Array.isArray(normalized.argumentTypes)) {
31126
31230
  for (const argument2 of normalized.argumentTypes) {
@@ -31232,13 +31336,14 @@ function deepNormalizeValue(value) {
31232
31336
  function isRecord(value) {
31233
31337
  return typeof value === "object" && value !== null && !Array.isArray(value);
31234
31338
  }
31235
- var SchemaDocumentContractError;
31339
+ var CALLABLE_MEMBER_KINDS, SchemaDocumentContractError;
31236
31340
  var init_contracts = __esm({
31237
31341
  "src/project-manifest/contracts.ts"() {
31238
31342
  "use strict";
31239
31343
  init_document_contracts();
31240
31344
  init_machine_strings();
31241
31345
  init_document_contracts();
31346
+ CALLABLE_MEMBER_KINDS = /* @__PURE__ */ new Set([13, 23]);
31242
31347
  SchemaDocumentContractError = class extends Error {
31243
31348
  constructor(recordKind, field) {
31244
31349
  super(
@@ -31264,6 +31369,8 @@ function threeWayMergeRecord(base, server, local, policy = {}) {
31264
31369
  const comparisonServer = policy.comparison?.server ?? serverRecord;
31265
31370
  const comparisonLocal = policy.comparison?.local ?? localRecord;
31266
31371
  const merged = {};
31372
+ const localVariant = {};
31373
+ const serverVariant = {};
31267
31374
  const conflictFields = [];
31268
31375
  const keys = /* @__PURE__ */ new Set([
31269
31376
  ...Object.keys(serverRecord),
@@ -31274,7 +31381,11 @@ function threeWayMergeRecord(base, server, local, policy = {}) {
31274
31381
  const rawServerValue = serverRecord[key];
31275
31382
  const rawLocalValue = localRecord[key];
31276
31383
  if (policy.serverWinsFields?.has(key) === true) {
31277
- if (rawServerValue !== void 0) merged[key] = rawServerValue;
31384
+ if (rawServerValue !== void 0) {
31385
+ merged[key] = rawServerValue;
31386
+ localVariant[key] = rawServerValue;
31387
+ serverVariant[key] = rawServerValue;
31388
+ }
31278
31389
  continue;
31279
31390
  }
31280
31391
  const baseValue = comparisonBase[key];
@@ -31295,25 +31406,31 @@ function threeWayMergeRecord(base, server, local, policy = {}) {
31295
31406
  } else if (result.present) {
31296
31407
  merged[key] = result.value;
31297
31408
  }
31409
+ if (result.local.present) localVariant[key] = result.local.value;
31410
+ if (result.server.present) serverVariant[key] = result.server.value;
31298
31411
  }
31299
- return { merged, conflictFields };
31412
+ return { merged, localVariant, serverVariant, conflictFields };
31300
31413
  }
31301
31414
  function mergeValue(args) {
31302
31415
  if (canonicallyEqual(args.server, args.base)) {
31303
- return selected(args.rawLocal);
31416
+ return sharedSelection(args.rawLocal);
31304
31417
  }
31305
31418
  if (canonicallyEqual(args.local, args.base)) {
31306
- return selected(args.rawServer);
31419
+ return sharedSelection(args.rawServer);
31307
31420
  }
31308
31421
  if (canonicallyEqual(args.local, args.server)) {
31309
- return selected(args.rawLocal);
31422
+ return sharedSelection(args.rawLocal);
31423
+ }
31424
+ if (!args.recursive) {
31425
+ return conflict(args.path, args.rawLocal, args.rawServer);
31310
31426
  }
31311
- if (!args.recursive) return conflict(args.path);
31312
31427
  if (isObjectRecord2(args.rawBase) && isObjectRecord2(args.rawServer) && isObjectRecord2(args.rawLocal)) {
31313
31428
  const comparisonBase = isObjectRecord2(args.base) ? args.base : args.rawBase;
31314
31429
  const comparisonServer = isObjectRecord2(args.server) ? args.server : args.rawServer;
31315
31430
  const comparisonLocal = isObjectRecord2(args.local) ? args.local : args.rawLocal;
31316
31431
  const value = {};
31432
+ const localVariant = {};
31433
+ const serverVariant = {};
31317
31434
  const conflicts = [];
31318
31435
  const keys = /* @__PURE__ */ new Set([
31319
31436
  ...Object.keys(args.rawBase),
@@ -31333,8 +31450,16 @@ function mergeValue(args) {
31333
31450
  });
31334
31451
  conflicts.push(...nested.conflicts);
31335
31452
  if (nested.present) value[key] = nested.value;
31453
+ if (nested.local.present) localVariant[key] = nested.local.value;
31454
+ if (nested.server.present) serverVariant[key] = nested.server.value;
31336
31455
  }
31337
- return { present: true, value, conflicts };
31456
+ return {
31457
+ present: true,
31458
+ value,
31459
+ local: { present: true, value: localVariant },
31460
+ server: { present: true, value: serverVariant },
31461
+ conflicts
31462
+ };
31338
31463
  }
31339
31464
  if (Array.isArray(args.rawBase) && Array.isArray(args.rawServer) && Array.isArray(args.rawLocal)) {
31340
31465
  return mergeIdentityArray(
@@ -31344,17 +31469,24 @@ function mergeValue(args) {
31344
31469
  args.path
31345
31470
  );
31346
31471
  }
31347
- return conflict(args.path);
31472
+ return conflict(args.path, args.rawLocal, args.rawServer);
31348
31473
  }
31349
31474
  function mergeIdentityArray(base, server, local, path) {
31350
31475
  const baseItems = indexIdentityArray(base);
31351
31476
  const serverItems = indexIdentityArray(server);
31352
31477
  const localItems = indexIdentityArray(local);
31353
31478
  if (baseItems === null || serverItems === null || localItems === null) {
31354
- return conflict(path);
31479
+ return conflict(path, local, server);
31355
31480
  }
31356
- const retained = /* @__PURE__ */ new Map();
31481
+ const mergedValues = /* @__PURE__ */ new Map();
31482
+ const localValues = /* @__PURE__ */ new Map();
31483
+ const serverValues = /* @__PURE__ */ new Map();
31357
31484
  const conflicts = [];
31485
+ const retainShared = (id2, value2) => {
31486
+ mergedValues.set(id2, value2);
31487
+ localValues.set(id2, value2);
31488
+ serverValues.set(id2, value2);
31489
+ };
31358
31490
  const ids = /* @__PURE__ */ new Set([
31359
31491
  ...baseItems.order,
31360
31492
  ...serverItems.order,
@@ -31370,46 +31502,79 @@ function mergeIdentityArray(base, server, local, path) {
31370
31502
  if (baseHas && !serverHas && !localHas) continue;
31371
31503
  if (baseHas && !serverHas) {
31372
31504
  if (canonicallyEqual(localValue, baseValue)) continue;
31373
- conflicts.push(`${path}[${JSON.stringify(id2)}]`);
31505
+ conflicts.push(path + "[" + JSON.stringify(id2) + "]");
31506
+ localValues.set(id2, localValue);
31374
31507
  continue;
31375
31508
  }
31376
31509
  if (baseHas && !localHas) {
31377
31510
  if (canonicallyEqual(serverValue, baseValue)) continue;
31378
- conflicts.push(`${path}[${JSON.stringify(id2)}]`);
31511
+ conflicts.push(path + "[" + JSON.stringify(id2) + "]");
31512
+ serverValues.set(id2, serverValue);
31379
31513
  continue;
31380
31514
  }
31381
31515
  if (!serverHas && localHas) {
31382
- retained.set(id2, localValue);
31516
+ retainShared(id2, localValue);
31383
31517
  continue;
31384
31518
  }
31385
31519
  if (!localHas && serverHas) {
31386
- retained.set(id2, serverValue);
31520
+ retainShared(id2, serverValue);
31387
31521
  continue;
31388
31522
  }
31389
- const merged = mergeValue({
31523
+ const item = mergeValue({
31390
31524
  rawBase: baseValue,
31391
31525
  rawServer: serverValue,
31392
31526
  rawLocal: localValue,
31393
31527
  base: baseValue,
31394
31528
  server: serverValue,
31395
31529
  local: localValue,
31396
- path: `${path}[${JSON.stringify(id2)}]`,
31530
+ path: path + "[" + JSON.stringify(id2) + "]",
31397
31531
  recursive: true
31398
31532
  });
31399
- conflicts.push(...merged.conflicts);
31400
- if (merged.present) retained.set(id2, merged.value);
31533
+ conflicts.push(...item.conflicts);
31534
+ if (item.present) mergedValues.set(id2, item.value);
31535
+ if (item.local.present) localValues.set(id2, item.local.value);
31536
+ if (item.server.present) serverValues.set(id2, item.server.value);
31401
31537
  }
31402
- if (conflicts.length > 0) return { present: true, value: [], conflicts };
31403
- const order = mergeIdentityOrder(
31538
+ const retainedIds = /* @__PURE__ */ new Set([...localValues.keys(), ...serverValues.keys()]);
31539
+ const sharedOrder = mergeIdentityOrder(
31540
+ baseItems.order,
31541
+ serverItems.order,
31542
+ localItems.order,
31543
+ retainedIds
31544
+ );
31545
+ if (sharedOrder === null) conflicts.push(path + ".$order");
31546
+ const localOrder = sharedOrder?.filter((id2) => localValues.has(id2)) ?? completeVariantOrder(
31547
+ localItems.order,
31548
+ serverItems.order,
31404
31549
  baseItems.order,
31550
+ new Set(localValues.keys())
31551
+ );
31552
+ const serverOrder = sharedOrder?.filter((id2) => serverValues.has(id2)) ?? completeVariantOrder(
31405
31553
  serverItems.order,
31406
31554
  localItems.order,
31407
- new Set(retained.keys())
31555
+ baseItems.order,
31556
+ new Set(serverValues.keys())
31408
31557
  );
31409
- if (order === null) return conflict(`${path}.$order`);
31558
+ const localVariant = localOrder.map((id2) => localValues.get(id2));
31559
+ const serverVariant = serverOrder.map((id2) => serverValues.get(id2));
31560
+ if (conflicts.length > 0) {
31561
+ return {
31562
+ present: true,
31563
+ value: [],
31564
+ local: { present: true, value: localVariant },
31565
+ server: { present: true, value: serverVariant },
31566
+ conflicts
31567
+ };
31568
+ }
31569
+ if (sharedOrder === null) {
31570
+ throw new Error("A conflict-free identity merge must have a shared order.");
31571
+ }
31572
+ const value = sharedOrder.map((id2) => mergedValues.get(id2));
31410
31573
  return {
31411
31574
  present: true,
31412
- value: order.map((id2) => retained.get(id2)),
31575
+ value,
31576
+ local: { present: true, value },
31577
+ server: { present: true, value },
31413
31578
  conflicts: []
31414
31579
  };
31415
31580
  }
@@ -31466,11 +31631,51 @@ function mergeIdentityOrder(base, server, local, retained) {
31466
31631
  }
31467
31632
  return result.length === nodes.length ? result : null;
31468
31633
  }
31469
- function selected(value) {
31470
- return value === void 0 ? { present: false, conflicts: [] } : { present: true, value, conflicts: [] };
31634
+ function completeVariantOrder(preferred, fallback, base, retained) {
31635
+ const result = preferred.filter((id2) => retained.has(id2));
31636
+ const included = new Set(result);
31637
+ for (const source of [fallback, base]) {
31638
+ for (let index = 0; index < source.length; index += 1) {
31639
+ const id2 = source[index];
31640
+ if (!retained.has(id2) || included.has(id2)) continue;
31641
+ let insertionIndex = -1;
31642
+ for (let next = index + 1; next < source.length; next += 1) {
31643
+ const nextIndex = result.indexOf(source[next]);
31644
+ if (nextIndex !== -1) {
31645
+ insertionIndex = nextIndex;
31646
+ break;
31647
+ }
31648
+ }
31649
+ if (insertionIndex === -1) {
31650
+ for (let previous = index - 1; previous >= 0; previous -= 1) {
31651
+ const previousIndex = result.indexOf(source[previous]);
31652
+ if (previousIndex !== -1) {
31653
+ insertionIndex = previousIndex + 1;
31654
+ break;
31655
+ }
31656
+ }
31657
+ }
31658
+ if (insertionIndex === -1) insertionIndex = result.length;
31659
+ result.splice(insertionIndex, 0, id2);
31660
+ included.add(id2);
31661
+ }
31662
+ }
31663
+ return result;
31471
31664
  }
31472
- function conflict(path) {
31473
- return { present: false, conflicts: [path] };
31665
+ function selection(value) {
31666
+ return value === void 0 ? { present: false } : { present: true, value };
31667
+ }
31668
+ function sharedSelection(value) {
31669
+ const selected = selection(value);
31670
+ return { ...selected, local: selected, server: selected, conflicts: [] };
31671
+ }
31672
+ function conflict(path, localValue, serverValue) {
31673
+ return {
31674
+ present: false,
31675
+ local: selection(localValue),
31676
+ server: selection(serverValue),
31677
+ conflicts: [path]
31678
+ };
31474
31679
  }
31475
31680
  var init_merge = __esm({
31476
31681
  "src/project-sync/merge.ts"() {
@@ -51601,13 +51806,13 @@ function sourceBaseParameterNames(context, baseName, suppliedNames) {
51601
51806
  return required2.parameters.map((parameter4) => parameter4.name);
51602
51807
  }
51603
51808
  const declared = context.declaredConstructors.byClassName.get(baseName) ?? [];
51604
- const selected2 = selectOverload(
51809
+ const selected = selectOverload(
51605
51810
  declared.map(
51606
51811
  (constructor2) => constructor2.parameters.map((parameter4) => parameter4.name)
51607
51812
  ),
51608
51813
  suppliedNames
51609
51814
  );
51610
- return selected2;
51815
+ return selected;
51611
51816
  }
51612
51817
  function recordBaseParameterNames(context, baseName, suppliedNames) {
51613
51818
  const baseClassId = context.classIdsByName.get(baseName);
@@ -64538,7 +64743,7 @@ function evaluateInitializerInContext(init, member, ctx, createdValues, argument
64538
64743
  };
64539
64744
  }
64540
64745
  function encodeLookupInitializerResult(member, value, created, ctx) {
64541
- const selection = encodeLookupInitializerSelection(
64746
+ const selection2 = encodeLookupInitializerSelection(
64542
64747
  member,
64543
64748
  value,
64544
64749
  created,
@@ -64550,7 +64755,7 @@ function encodeLookupInitializerResult(member, value, created, ctx) {
64550
64755
  ctx.__executionState?.constructorGroups.delete(row.id);
64551
64756
  }
64552
64757
  }
64553
- return { value: selection, classId: null };
64758
+ return { value: selection2, classId: null };
64554
64759
  }
64555
64760
  function encodeLookupInitializerSelection(member, value, created, ctx) {
64556
64761
  if (value === null) return null;
@@ -72482,7 +72687,7 @@ var init_animation_clips = __esm({
72482
72687
  const seenChildren = /* @__PURE__ */ new Set();
72483
72688
  for (const row of rows) {
72484
72689
  const rowClassId = this.requireNodeClassId(row, "animationChildOverride");
72485
- const selected2 = this.resolveSelector(
72690
+ const selected = this.resolveSelector(
72486
72691
  row,
72487
72692
  rowClassId,
72488
72693
  WORLD_ANIMATION_CHILD_OVERRIDE_SELECTOR_MEMBER_ID,
@@ -72490,8 +72695,8 @@ var init_animation_clips = __esm({
72490
72695
  args.selectorOwnerNode,
72491
72696
  `Animation clip "${args.clipName}" frame ${args.frameIndex} child override`
72492
72697
  );
72493
- if (selected2 === null) continue;
72494
- const { childId, childClassId, childNode: child } = selected2;
72698
+ if (selected === null) continue;
72699
+ const { childId, childClassId, childNode: child } = selected;
72495
72700
  if (!args.childIds.has(childId)) {
72496
72701
  throw new Error(
72497
72702
  `Animation clip "${args.clipName}" frame ${args.frameIndex} references child "${childId}" outside the owner's authored Children graph.`
@@ -72657,7 +72862,7 @@ var init_animation_clips = __esm({
72657
72862
  * segment's length is instance data this document does not have.
72658
72863
  */
72659
72864
  validateTrackBase(args) {
72660
- const selected2 = this.resolveSelector(
72865
+ const selected = this.resolveSelector(
72661
72866
  args.track,
72662
72867
  args.trackClassId,
72663
72868
  WORLD_ANIMATION_TRACK_SELECTOR_MEMBER_ID,
@@ -72665,9 +72870,9 @@ var init_animation_clips = __esm({
72665
72870
  args.selectorOwnerNode,
72666
72871
  args.label
72667
72872
  );
72668
- if (selected2 !== null && !args.childIds.has(selected2.childId)) {
72873
+ if (selected !== null && !args.childIds.has(selected.childId)) {
72669
72874
  throw new Error(
72670
- `${args.label} references child "${selected2.childId}" outside the owner's authored Children graph.`
72875
+ `${args.label} references child "${selected.childId}" outside the owner's authored Children graph.`
72671
72876
  );
72672
72877
  }
72673
72878
  const startFrame = this.requireIntegerField(
@@ -72686,7 +72891,7 @@ var init_animation_clips = __esm({
72686
72891
  }
72687
72892
  this.validateTrackDirection(args.track, args.trackClassId, args.label);
72688
72893
  this.validateTrackCropWindow(args.track, args.trackClassId, args.label);
72689
- return selected2;
72894
+ return selected;
72690
72895
  }
72691
72896
  resolveSelector(parent, classId, selectorMemberId, refreshMemberId, _selectorOwnerNode, label) {
72692
72897
  this.validateSelectorRefresh(parent, classId, refreshMemberId, label);
@@ -74157,7 +74362,7 @@ function explicitTargetIds(changes) {
74157
74362
  return result;
74158
74363
  }
74159
74364
  function selectedRecordIds(records2, explicitIds, impactedIds, impactedTypeNames, constructedClassIds, impactedSourceNames, ownerId = () => null) {
74160
- const selected2 = new Set(explicitIds);
74365
+ const selected = new Set(explicitIds);
74161
74366
  for (const record3 of records2) {
74162
74367
  const owner = ownerId(record3);
74163
74368
  if (owner !== null && impactedIds.has(owner) || recordDependsOnChangedContract(
@@ -74167,10 +74372,10 @@ function selectedRecordIds(records2, explicitIds, impactedIds, impactedTypeNames
74167
74372
  constructedClassIds,
74168
74373
  impactedSourceNames
74169
74374
  )) {
74170
- selected2.add(record3.id);
74375
+ selected.add(record3.id);
74171
74376
  }
74172
74377
  }
74173
- return selected2;
74378
+ return selected;
74174
74379
  }
74175
74380
  function recordDependsOnChangedContract(record3, impactedIds, impactedTypeNames, constructedClassIds, impactedSourceNames) {
74176
74381
  const recordId = isObjectRecord3(record3) ? record3.id : void 0;
@@ -85224,11 +85429,11 @@ var init_project_version_whole_graph_validation = __esm({
85224
85429
 
85225
85430
  // src/project-source/workspace-status-core.ts
85226
85431
  function listVirtualProjectSourceFilesV4(files) {
85227
- const selected2 = files.filter((file) => {
85432
+ const selected = files.filter((file) => {
85228
85433
  const kind = neoProjectSourceKind(file.path);
85229
85434
  return kind !== null && isNeoProjectProductionSourceKind(kind) && !neoProjectPathHasIgnoredDirectory(file.path);
85230
85435
  });
85231
- const sorted = [...selected2].sort(
85436
+ const sorted = [...selected].sort(
85232
85437
  (left, right) => compareWorkspacePaths(left.path, right.path)
85233
85438
  );
85234
85439
  for (let index = 1; index < sorted.length; index += 1) {
@@ -91292,9 +91497,6 @@ function resolveLookupCollectionValueIds(context, member, collection, ownerValue
91292
91497
  member.collectionMemberId,
91293
91498
  collection.name
91294
91499
  );
91295
- if (declaredRows.size > 0) {
91296
- return { valueIds: [...declaredRows], origin: "collectionMember" };
91297
- }
91298
91500
  const candidates = /* @__PURE__ */ new Set();
91299
91501
  const collectionState = context.state[`member:${member.collectionMemberId}`];
91300
91502
  const collectionMemberData = isObjectRecord2(collectionState?.data) ? collectionState.data : {};
@@ -91306,6 +91508,12 @@ function resolveLookupCollectionValueIds(context, member, collection, ownerValue
91306
91508
  ) ?? []) {
91307
91509
  candidates.add(placedValueId);
91308
91510
  }
91511
+ if (declaredRows.size > 0) {
91512
+ return {
91513
+ valueIds: [.../* @__PURE__ */ new Set([...declaredRows, ...candidates])],
91514
+ origin: "collectionMember"
91515
+ };
91516
+ }
91309
91517
  return { valueIds: [...candidates], origin: "nameScan" };
91310
91518
  }
91311
91519
  function collectionRowsOfDeclaredMember(context, collectionMemberId, schemaKey) {
@@ -92268,13 +92476,13 @@ function storedValueConstructor(context, schemaClass2, constructorArgs) {
92268
92476
  `Stored construction for ${schemaClass2.name} cannot select one constructor from ${candidates.length} ${count}-argument candidates.`
92269
92477
  );
92270
92478
  }
92271
- const selected2 = candidates[0];
92272
- if (selected2 === void 0) {
92479
+ const selected = candidates[0];
92480
+ if (selected === void 0) {
92273
92481
  throw new Error(
92274
92482
  `Stored construction for ${schemaClass2.name} resolved no constructor.`
92275
92483
  );
92276
92484
  }
92277
- return selected2;
92485
+ return selected;
92278
92486
  }
92279
92487
  function storedConstructorArgumentSource(context, type, value, environment, visited, cloneAggregateArguments = false) {
92280
92488
  if (value === null) {
@@ -93879,7 +94087,7 @@ import {
93879
94087
  readFileSync as readFileSync6,
93880
94088
  readdirSync,
93881
94089
  renameSync as renameSync4,
93882
- rmSync,
94090
+ rmSync as rmSync2,
93883
94091
  writeFileSync as writeFileSync6
93884
94092
  } from "node:fs";
93885
94093
  import { basename, dirname as dirname5, extname, join as join6, relative, sep } from "node:path";
@@ -94159,7 +94367,7 @@ function writeVerifiedBinaryDownloadV4(destination, bytes, expectedSha256) {
94159
94367
  writeFileSync6(temporary, bytes);
94160
94368
  renameSync4(temporary, destination);
94161
94369
  } finally {
94162
- rmSync(temporary, { force: true });
94370
+ rmSync2(temporary, { force: true });
94163
94371
  }
94164
94372
  }
94165
94373
  function writeBinaryConflictArtifactV4(root, fileId, fileName2, bytes, expectedSha256) {
@@ -95284,7 +95492,7 @@ import {
95284
95492
  mkdirSync as mkdirSync7,
95285
95493
  readFileSync as readFileSync8,
95286
95494
  readdirSync as readdirSync3,
95287
- rmSync as rmSync2,
95495
+ rmSync as rmSync3,
95288
95496
  writeFileSync as writeFileSync7,
95289
95497
  statSync
95290
95498
  } from "node:fs";
@@ -95296,14 +95504,14 @@ function resetWorkspaceToProjectSourcesV4(workspace, document, options = {}) {
95296
95504
  const previous = managedFilesBeforeReset(workspace.root);
95297
95505
  const preservedSpecs = preserveManagedSpecs(workspace.root);
95298
95506
  for (const directory of FORMAT_4_MANAGED_DIRECTORIES) {
95299
- rmSync2(join8(workspace.root, directory), { recursive: true, force: true });
95507
+ rmSync3(join8(workspace.root, directory), { recursive: true, force: true });
95300
95508
  }
95301
- rmSync2(join8(workspace.root, "Scripts"), { recursive: true, force: true });
95509
+ rmSync3(join8(workspace.root, "Scripts"), { recursive: true, force: true });
95302
95510
  for (const file of LEGACY_ROOT_FILES) {
95303
- rmSync2(join8(workspace.root, file), { force: true });
95511
+ rmSync3(join8(workspace.root, file), { force: true });
95304
95512
  }
95305
95513
  for (const privatePath of LEGACY_PRIVATE_PATHS) {
95306
- rmSync2(join8(workspace.root, privatePath), { recursive: true, force: true });
95514
+ rmSync3(join8(workspace.root, privatePath), { recursive: true, force: true });
95307
95515
  }
95308
95516
  for (const [path, bytes] of preservedSpecs) {
95309
95517
  const absolute = join8(workspace.root, path);
@@ -95559,7 +95767,7 @@ var init_http = __esm({
95559
95767
  });
95560
95768
 
95561
95769
  // src/project-source/project-file-pull.ts
95562
- import { existsSync as existsSync6, rmSync as rmSync3 } from "node:fs";
95770
+ import { existsSync as existsSync6, rmSync as rmSync4 } from "node:fs";
95563
95771
  import { join as join9 } from "node:path";
95564
95772
  async function pullProjectBinariesV4(args) {
95565
95773
  let client = args.client ?? null;
@@ -95688,7 +95896,7 @@ async function pullProjectBinariesV4(args) {
95688
95896
  conflicted += 1;
95689
95897
  continue;
95690
95898
  }
95691
- rmSync3(absolute, { force: true });
95899
+ rmSync4(absolute, { force: true });
95692
95900
  removePreviousConflict(args.workspace.root, previous.projectBinary);
95693
95901
  }
95694
95902
  return { states, downloaded, conflicted, conflicts };
@@ -95758,7 +95966,7 @@ function fileName(data) {
95758
95966
  }
95759
95967
  function removePreviousConflict(root, state) {
95760
95968
  if (state?.conflict?.artifactPath === void 0) return;
95761
- rmSync3(join9(root, state.conflict.artifactPath), { force: true });
95969
+ rmSync4(join9(root, state.conflict.artifactPath), { force: true });
95762
95970
  }
95763
95971
  var init_project_file_pull = __esm({
95764
95972
  "src/project-source/project-file-pull.ts"() {
@@ -95823,7 +96031,7 @@ __export(pull_exports, {
95823
96031
  import {
95824
96032
  mkdirSync as mkdirSync8,
95825
96033
  writeFileSync as writeFileSync8,
95826
- rmSync as rmSync4,
96034
+ rmSync as rmSync5,
95827
96035
  existsSync as existsSync7,
95828
96036
  readFileSync as readFileSync9
95829
96037
  } from "node:fs";
@@ -95972,7 +96180,7 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
95972
96180
  continue;
95973
96181
  }
95974
96182
  const localSide = locallyDeleted ? {} : local;
95975
- const { merged, conflictFields } = mergeProjectDocumentRecord(
96183
+ const { merged, localVariant, serverVariant, conflictFields } = mergeProjectDocumentRecord(
95976
96184
  serverRecord.recordKind,
95977
96185
  baseState.data,
95978
96186
  serverRecord.data,
@@ -95985,11 +96193,11 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
95985
96193
  plans.set(key, {
95986
96194
  // The working copy renders the LOCAL side; the marker's other half
95987
96195
  // comes from the server-variant emission.
95988
- emitData: locallyDeleted ? void 0 : localSide,
96196
+ emitData: locallyDeleted ? void 0 : localVariant,
95989
96197
  serverHash: serverRecord.contentHash,
95990
96198
  serverData: serverRecord.data,
95991
96199
  conflicted: true,
95992
- localData: locallyDeleted ? void 0 : localSide
96200
+ serverEmitData: locallyDeleted ? serverRecord.data : serverVariant
95993
96201
  });
95994
96202
  } else {
95995
96203
  mergedCount += 1;
@@ -96024,8 +96232,7 @@ ${blockingErrors.map((error) => ` ${error.message}`).join("\n")}`
96024
96232
  emitData: local,
96025
96233
  serverHash: baseState.contentHash,
96026
96234
  serverData: void 0,
96027
- conflicted: true,
96028
- localData: local
96235
+ conflicted: true
96029
96236
  });
96030
96237
  continue;
96031
96238
  }
@@ -96212,7 +96419,7 @@ async function finishFormat4Pull(args) {
96212
96419
  if (previousPath === void 0 || emittedPaths.has(previousPath)) continue;
96213
96420
  const absolute = join10(workspace.root, previousPath);
96214
96421
  if (existsSync7(absolute)) {
96215
- rmSync4(absolute);
96422
+ rmSync5(absolute);
96216
96423
  removed += 1;
96217
96424
  }
96218
96425
  }
@@ -96520,7 +96727,7 @@ function sourceComparableObjectRecord(recordKind, value, mainLocale) {
96520
96727
  function buildEmitRecordSet(document, plans, side) {
96521
96728
  const records2 = /* @__PURE__ */ new Map();
96522
96729
  for (const [key, plan] of plans) {
96523
- const data = side === "emit" ? plan.emitData : plan.serverData;
96730
+ const data = side === "emit" ? plan.emitData : plan.serverEmitData ?? plan.serverData;
96524
96731
  if (data === void 0) continue;
96525
96732
  const serverRecord = document.records.get(key);
96526
96733
  const [recordKind, recordId] = key.split(/:(.+)/, 2);
@@ -100378,7 +100585,7 @@ import { createHash as createHash10, randomUUID as randomUUID2 } from "node:cryp
100378
100585
  import {
100379
100586
  mkdirSync as mkdirSync10,
100380
100587
  writeFileSync as writeFileSync10,
100381
- rmSync as rmSync5,
100588
+ rmSync as rmSync6,
100382
100589
  existsSync as existsSync11,
100383
100590
  readFileSync as readFileSync15
100384
100591
  } from "node:fs";
@@ -101174,7 +101381,7 @@ async function runPush(workspace, options, preparationOverride) {
101174
101381
  });
101175
101382
  } finally {
101176
101383
  if (preparedBuildDir !== void 0) {
101177
- rmSync5(preparedBuildDir, { recursive: true, force: true });
101384
+ rmSync6(preparedBuildDir, { recursive: true, force: true });
101178
101385
  }
101179
101386
  }
101180
101387
  } else {
@@ -102387,7 +102594,7 @@ ${finalErrors.map(
102387
102594
  const previousPath = recordState.file;
102388
102595
  if (previousPath === void 0 || emittedPaths.has(previousPath)) continue;
102389
102596
  const absolute = join15(workspace.root, previousPath);
102390
- if (existsSync11(absolute)) rmSync5(absolute);
102597
+ if (existsSync11(absolute)) rmSync6(absolute);
102391
102598
  }
102392
102599
  for (const file of files) {
102393
102600
  const absolute = join15(workspace.root, file.path);
@@ -103197,7 +103404,7 @@ var init_registry2 = __esm({
103197
103404
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
103198
103405
  formatVersion: 3,
103199
103406
  contractVersion: "3.9",
103200
- cliVersion: "0.26.5",
103407
+ cliVersion: "0.27.1",
103201
103408
  projectFileUploadBatchSize: 32,
103202
103409
  documentRecords: {
103203
103410
  member: {
@@ -104498,14 +104705,14 @@ import {
104498
104705
  readFileSync as readFileSync16,
104499
104706
  realpathSync,
104500
104707
  renameSync as renameSync5,
104501
- rmSync as rmSync6,
104708
+ rmSync as rmSync7,
104502
104709
  statSync as statSync2,
104503
104710
  writeFileSync as writeFileSync11
104504
104711
  } from "node:fs";
104505
104712
  import {
104506
104713
  basename as basename3,
104507
104714
  dirname as dirname9,
104508
- isAbsolute,
104715
+ isAbsolute as isAbsolute2,
104509
104716
  join as join16,
104510
104717
  relative as relative5,
104511
104718
  resolve as resolve3,
@@ -104706,7 +104913,7 @@ function preparedHookCandidate(workspace) {
104706
104913
  "test-build"
104707
104914
  );
104708
104915
  const pathFromRoot = relative5(cacheRoot, directory);
104709
- if (isAbsolute(pathFromRoot)) {
104916
+ if (isAbsolute2(pathFromRoot)) {
104710
104917
  throw new NeoTestPreparedCandidateError(
104711
104918
  "NEO_PREPARED_BUILD_DIR must not resolve to an absolute path outside this workspace's .neo/test-build directory."
104712
104919
  );
@@ -104906,7 +105113,7 @@ function selectedSpecPaths(workspace, selectors) {
104906
105113
  );
104907
105114
  for (const selector of normalizedSelectors) {
104908
105115
  if (/[*?]/u.test(selector)) continue;
104909
- if (isAbsolute(selector)) {
105116
+ if (isAbsolute2(selector)) {
104910
105117
  throw new Error(
104911
105118
  `Spec selector ${JSON.stringify(selector)} must be workspace-relative.`
104912
105119
  );
@@ -104953,8 +105160,8 @@ function selectedSpecPaths(workspace, selectors) {
104953
105160
  const selectedPaths = all.filter((absolutePath) => {
104954
105161
  const path = relativePaths.get(absolutePath);
104955
105162
  const configured = configuredIncludes.length === 0 || configuredIncludes.some((pattern) => globMatches(pattern, path));
104956
- const selected2 = normalizedSelectors.length === 0 || normalizedSelectors.some((selector) => selectorMatches(selector, path));
104957
- return configured && selected2 && !(workspace.config.test?.exclude ?? []).some(
105163
+ const selected = normalizedSelectors.length === 0 || normalizedSelectors.some((selector) => selectorMatches(selector, path));
105164
+ return configured && selected && !(workspace.config.test?.exclude ?? []).some(
104958
105165
  (pattern) => globMatches(pattern.replaceAll("\\", "/"), path)
104959
105166
  );
104960
105167
  });
@@ -105155,22 +105362,22 @@ function registerSpec(spec, document) {
105155
105362
  );
105156
105363
  return { spec, root, tests };
105157
105364
  }
105158
- function mockResponse(environment, mock, selected2, call) {
105159
- if (selected2 === null) {
105365
+ function mockResponse(environment, mock, selected, call) {
105366
+ if (selected === null) {
105160
105367
  if (mock.callThrough) return { handled: false };
105161
105368
  throw new NeoTestAssertionError(
105162
105369
  `Mock for Function '${mock.memberId}' has no configured behavior. Add returns, throws, doesNothing, or implementation.`
105163
105370
  );
105164
105371
  }
105165
- if (selected2.kind === "throw") {
105166
- throw new NSGetterRuntimeError(selected2.message);
105372
+ if (selected.kind === "throw") {
105373
+ throw new NSGetterRuntimeError(selected.message);
105167
105374
  }
105168
- if (selected2.kind === "implementation") {
105375
+ if (selected.kind === "implementation") {
105169
105376
  return {
105170
105377
  handled: true,
105171
105378
  value: call.invokeDelegate(
105172
105379
  bindNeoScriptDelegateToContext(
105173
- selected2.delegate,
105380
+ selected.delegate,
105174
105381
  environment.context
105175
105382
  ),
105176
105383
  mock.isStatic ? call.args : [call.receiver, ...call.args]
@@ -105179,7 +105386,7 @@ function mockResponse(environment, mock, selected2, call) {
105179
105386
  }
105180
105387
  return {
105181
105388
  handled: true,
105182
- value: selected2.kind === "return" ? selected2.value : null
105389
+ value: selected.kind === "return" ? selected.value : null
105183
105390
  };
105184
105391
  }
105185
105392
  function partialObjectMatch(actual, expected) {
@@ -105196,16 +105403,16 @@ function matchingMock(environment, call) {
105196
105403
  (candidate) => candidate.receiverValueId !== void 0
105197
105404
  );
105198
105405
  const receiverValueId = needsReceiverLookup ? mockReceiverValueId(environment, call.receiver) : null;
105199
- let selected2;
105406
+ let selected;
105200
105407
  for (const candidate of candidates) {
105201
105408
  if (candidate.receiverValueId !== void 0 && candidate.receiverValueId !== receiverValueId) {
105202
105409
  continue;
105203
105410
  }
105204
- if (selected2 === void 0 || candidate.id > selected2.id) {
105205
- selected2 = candidate;
105411
+ if (selected === void 0 || candidate.id > selected.id) {
105412
+ selected = candidate;
105206
105413
  }
105207
105414
  }
105208
- return selected2;
105415
+ return selected;
105209
105416
  }
105210
105417
  function mockReceiverValueId(environment, receiver) {
105211
105418
  if (typeof receiver !== "object" || receiver === null) return null;
@@ -105550,15 +105757,15 @@ function failureFor(error, file, position, member = null) {
105550
105757
  ]
105551
105758
  };
105552
105759
  }
105553
- function suiteHasSelectedTests(suite, selected2) {
105554
- return suite.tests.some((test) => selected2.has(test.id)) || suite.suites.some((child) => suiteHasSelectedTests(child, selected2));
105760
+ function suiteHasSelectedTests(suite, selected) {
105761
+ return suite.tests.some((test) => selected.has(test.id)) || suite.suites.some((child) => suiteHasSelectedTests(child, selected));
105555
105762
  }
105556
- async function executeRegisteredSpec(registered, document, selected2, timeoutMs, interrupted) {
105763
+ async function executeRegisteredSpec(registered, document, selected, timeoutMs, interrupted) {
105557
105764
  const results = [];
105558
105765
  const fileFailures = [];
105559
105766
  let testDurationMs = 0;
105560
105767
  const executeSuite = async (suite, parentEnvironment, inheritedFailures) => {
105561
- if (!suiteHasSelectedTests(suite, selected2)) return;
105768
+ if (!suiteHasSelectedTests(suite, selected)) return;
105562
105769
  const fixture = cloneTestEnvironment(parentEnvironment);
105563
105770
  const suiteFailures = [...inheritedFailures];
105564
105771
  const suiteDeadline = Date.now() + timeoutMs;
@@ -105575,7 +105782,7 @@ async function executeRegisteredSpec(registered, document, selected2, timeoutMs,
105575
105782
  }
105576
105783
  }
105577
105784
  for (const test of suite.tests) {
105578
- if (!selected2.has(test.id)) continue;
105785
+ if (!selected.has(test.id)) continue;
105579
105786
  const started = performance.now();
105580
105787
  const deadline = Date.now() + timeoutMs;
105581
105788
  const environment = cloneTestEnvironment(fixture);
@@ -105691,16 +105898,16 @@ function maintainNeoTestBuildCache(root, maxBytes = TEST_BUILD_CACHE_LIMIT_BYTES
105691
105898
  const resolvedProtected = protectedDirectory === void 0 ? null : resolve3(protectedDirectory);
105692
105899
  const protectedInsideRoot = resolvedProtected !== null && (() => {
105693
105900
  const fromRoot = relative5(resolvedRoot, resolvedProtected);
105694
- return fromRoot === "" || !fromRoot.startsWith(`..${sep5}`) && fromRoot !== ".." && !isAbsolute(fromRoot);
105901
+ return fromRoot === "" || !fromRoot.startsWith(`..${sep5}`) && fromRoot !== ".." && !isAbsolute2(fromRoot);
105695
105902
  })();
105696
105903
  const isProtected = (path) => {
105697
105904
  if (!protectedInsideRoot || resolvedProtected === null) return false;
105698
105905
  const fromProtected = relative5(resolvedProtected, resolve3(path));
105699
- return fromProtected === "" || !fromProtected.startsWith(`..${sep5}`) && fromProtected !== ".." && !isAbsolute(fromProtected);
105906
+ return fromProtected === "" || !fromProtected.startsWith(`..${sep5}`) && fromProtected !== ".." && !isAbsolute2(fromProtected);
105700
105907
  };
105701
105908
  for (const file of testBuildFiles(root)) {
105702
105909
  if (!isProtected(file.path) && basename3(file.path).includes(".tmp-") && now - file.modifiedMs >= ABANDONED_TEMP_MAX_AGE_MS) {
105703
- rmSync6(file.path, { force: true });
105910
+ rmSync7(file.path, { force: true });
105704
105911
  }
105705
105912
  }
105706
105913
  const files = testBuildFiles(root);
@@ -105710,7 +105917,7 @@ function maintainNeoTestBuildCache(root, maxBytes = TEST_BUILD_CACHE_LIMIT_BYTES
105710
105917
  ).sort((left, right) => left.modifiedMs - right.modifiedMs);
105711
105918
  for (const file of removable) {
105712
105919
  if (total <= maxBytes) break;
105713
- rmSync6(file.path, { force: true });
105920
+ rmSync7(file.path, { force: true });
105714
105921
  total -= file.size;
105715
105922
  }
105716
105923
  }
@@ -106004,7 +106211,7 @@ async function runTest(workspace, options, dependencies = {}) {
106004
106211
  if (options.outputFile !== null) {
106005
106212
  try {
106006
106213
  atomicWrite(
106007
- isAbsolute(options.outputFile) ? options.outputFile : join16(workspace.root, options.outputFile),
106214
+ isAbsolute2(options.outputFile) ? options.outputFile : join16(workspace.root, options.outputFile),
106008
106215
  serialized
106009
106216
  );
106010
106217
  } catch (error) {
@@ -106217,7 +106424,7 @@ import {
106217
106424
  existsSync as existsSync13,
106218
106425
  readFileSync as readFileSync17
106219
106426
  } from "node:fs";
106220
- import { extname as extname2, isAbsolute as isAbsolute2, join as join17, relative as relative6, sep as sep6 } from "node:path";
106427
+ import { extname as extname2, isAbsolute as isAbsolute3, join as join17, relative as relative6, sep as sep6 } from "node:path";
106221
106428
  function inspectNeoDoctor(workspace) {
106222
106429
  const formatCompatible = workspace.config.formatVersion === CURRENT_FORMAT_VERSION;
106223
106430
  const compiler = inspectCompilerContract();
@@ -106397,7 +106604,7 @@ function inspectTrackedBinary(root, record3, errors) {
106397
106604
  const binary = record3.projectBinary;
106398
106605
  if (!binary) return;
106399
106606
  const path = binary.path.replaceAll("\\", "/");
106400
- if (isAbsolute2(path) || path.split("/").includes("..")) {
106607
+ if (isAbsolute3(path) || path.split("/").includes("..")) {
106401
106608
  errors.push(
106402
106609
  `Project file ${record3.recordId} has unsafe tracked path ${JSON.stringify(binary.path)}.`
106403
106610
  );
@@ -109474,7 +109681,7 @@ __export(resolve_exports, {
109474
109681
  runResolve: () => runResolve,
109475
109682
  workspaceFilePath: () => workspaceFilePath
109476
109683
  });
109477
- import { readFileSync as readFileSync19, rmSync as rmSync7, writeFileSync as writeFileSync14 } from "node:fs";
109684
+ import { readFileSync as readFileSync19, rmSync as rmSync8, writeFileSync as writeFileSync14 } from "node:fs";
109478
109685
  import { join as join21 } from "node:path";
109479
109686
  function runResolve(workspace, side) {
109480
109687
  let resolvedFiles = 0;
@@ -109500,12 +109707,12 @@ function runResolve(workspace, side) {
109500
109707
  );
109501
109708
  binary.sha256 = conflict2.remoteSha256;
109502
109709
  } else {
109503
- rmSync7(destination, { force: true });
109710
+ rmSync8(destination, { force: true });
109504
109711
  binary.sha256 = null;
109505
109712
  }
109506
109713
  }
109507
109714
  if (conflict2.artifactPath !== void 0) {
109508
- rmSync7(join21(workspace.root, conflict2.artifactPath), { force: true });
109715
+ rmSync8(join21(workspace.root, conflict2.artifactPath), { force: true });
109509
109716
  }
109510
109717
  delete binary.conflict;
109511
109718
  resolvedBinaries += 1;
@@ -109604,6 +109811,7 @@ ${h("Start")}
109604
109811
  login ${d("[--api <url>] [--profile editor|release] [--save-project <id>]")}
109605
109812
  init ${d("[--project <id>] [--version <id>] [--dir <path>] (interactive pickers)")}
109606
109813
  whoami ${d("[--api <url>]")}
109814
+ logout ${d("[--api <url>] delete the stored credential for one API origin")}
109607
109815
  help ${d("show this command overview")}
109608
109816
  --version ${d("print the installed CLI version")}
109609
109817
 
@@ -109762,7 +109970,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
109762
109970
  async function main() {
109763
109971
  const args = parseArgs(process.argv.slice(2));
109764
109972
  if (args.command === "--version") {
109765
- console.log("0.26.5");
109973
+ console.log("0.27.1");
109766
109974
  return;
109767
109975
  }
109768
109976
  if (args.command === null || args.command === "help" || args.command === "--help" || args.command === "-h") {
@@ -109787,6 +109995,9 @@ async function main() {
109787
109995
  case "whoami":
109788
109996
  await runWhoami(apiBaseUrl);
109789
109997
  return;
109998
+ case "logout":
109999
+ await runLogout(apiBaseUrl);
110000
+ return;
109790
110001
  case "init":
109791
110002
  {
109792
110003
  const { runInit: runInit2 } = await Promise.resolve().then(() => (init_init(), init_exports));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neocompose/cli",
3
- "version": "0.26.5",
3
+ "version": "0.27.1",
4
4
  "description": "Neo Compose native project-source CLI with bidirectional sync.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -9,7 +9,7 @@ description: >-
9
9
  `@neocompose/cli` or `node cli/bin/neo.mjs` in the neo-compose repository.
10
10
  ---
11
11
 
12
- <!-- reviewed-through-cli: 0.26.5 -->
12
+ <!-- reviewed-through-cli: 0.27.1 -->
13
13
 
14
14
  # Neo Compose CLI
15
15
 
@@ -83,7 +83,7 @@ wrappers.
83
83
  The marker near the top of `SKILL.md` must exactly match the package version:
84
84
 
85
85
  ```html
86
- <!-- reviewed-through-cli: 0.26.5 -->
86
+ <!-- reviewed-through-cli: 0.27.1 -->
87
87
  ```
88
88
 
89
89
  The quoted version above is checked too, so this instruction cannot go stale
@@ -145,5 +145,16 @@ and a protected file only as fallback. Use `NEO_COMPOSE_TOKEN` or
145
145
  `--token-stdin` in CI. The editor profile cannot publish releases; server
146
146
  scopes remain the security boundary.
147
147
 
148
+ `neo logout [--api <url>]` deletes the stored credential for exactly one API
149
+ origin (keychain entry and credentials-file row) and revokes the server
150
+ session best-effort. It never clears other origins or the whole store.
151
+
152
+ Isolated environments (P53 agent rigs) set `NEO_COMPOSE_CONFIG_HOME` (an
153
+ absolute directory replacing `$XDG_CONFIG_HOME/neo-compose` for Neo state
154
+ only) and `NEO_COMPOSE_CREDENTIAL_NAMESPACE` (scopes the OS-keychain account)
155
+ so concurrent environments sharing one OS user cannot read, overwrite, or
156
+ delete each other's tokens. Login, logout, and authenticated commands all
157
+ honor both variables.
158
+
148
159
  Pass explicit project/version IDs and flags in automation. Prefer `--json` for
149
160
  machine-readable output and do not depend on interactive pickers or confirms.