@neocompose/cli 0.31.8 → 0.31.9

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,18 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.31.9] - 2026-08-17
4
+
5
+ ### Added
6
+
7
+ - Surface `SpriteInfo.Empty` in project-source and NeoScript completion and
8
+ hover with documentation for its canonical empty-sprite behavior.
9
+
10
+ ### Fixed
11
+
12
+ - Accept the exact `SpriteInfo.Empty` sentinel as the non-null result of a
13
+ required Sprite function while continuing to reject malformed empty sprites
14
+ and empty Audio values.
15
+
3
16
  ## [0.31.8] - 2026-08-17
4
17
 
5
18
  ### Fixed
package/dist/neo.mjs CHANGED
@@ -1663,13 +1663,14 @@ var init_language_spec = __esm({
1663
1663
  id: "builtin:SpriteInfo",
1664
1664
  name: "SpriteInfo",
1665
1665
  kind: "builtin",
1666
+ documentation: "A sprite asset reference and slice index. Use SpriteInfo.Empty when a required sprite should render no image.",
1666
1667
  members: [
1667
1668
  {
1668
1669
  ...property(
1669
1670
  "SpriteInfo",
1670
1671
  "Empty",
1671
1672
  SPRITE_INFO_TYPE,
1672
- "An authored empty sprite at slice zero."
1673
+ "The canonical non-null empty sprite. Use it when a required SpriteInfo should render no image; it has no backing file and uses slice index 0."
1673
1674
  ),
1674
1675
  static: true
1675
1676
  },
@@ -2319,7 +2320,11 @@ function complete(snapshot, position) {
2319
2320
  word
2320
2321
  );
2321
2322
  } else {
2322
- const resolved = resolveChain(snapshot, receiverTokens, offset);
2323
+ const directStaticType = receiverTokens.length === 1 && receiverTokens[0]?.kind === "type" ? snapshot.project.typeByName.get(receiverTokens[0].text) : void 0;
2324
+ const resolved = directStaticType ? {
2325
+ type: { kind: "named", typeId: directStaticType.id },
2326
+ staticType: directStaticType
2327
+ } : resolveChain(snapshot, receiverTokens, offset);
2323
2328
  candidates = completionItemsForResolution(snapshot, resolved, word);
2324
2329
  }
2325
2330
  } else if (tail?.kind === "identifier" && tail.text === NEOSCRIPT_CONSTRUCTOR_KEYWORD) {
@@ -2343,27 +2348,14 @@ function complete(snapshot, position) {
2343
2348
  function hover(snapshot, position) {
2344
2349
  const offset = snapshot.source.offsetAt(position);
2345
2350
  const reference2 = referenceAt(snapshot.parsed.references, offset);
2346
- if (!reference2) return null;
2351
+ if (!reference2) return builtinStaticMemberHoverAt(snapshot, offset);
2347
2352
  const resolution = resolveReference(snapshot, reference2);
2348
- if (!resolution?.symbol && !resolution?.staticType) return null;
2353
+ if (!resolution?.symbol && !resolution?.staticType) {
2354
+ return builtinStaticMemberHoverAt(snapshot, offset);
2355
+ }
2349
2356
  const symbol = resolution.symbol;
2350
2357
  if (symbol) {
2351
- const signature = formatSymbolSignature(symbol, snapshot.project);
2352
- const origin = symbol.inheritedFrom ? `
2353
-
2354
- Inherited from \`${symbol.inheritedFrom.typeName}\`.` : "";
2355
- const identity2 = symbol.id.startsWith("local:") || symbol.id.startsWith("parameter:") ? "" : `
2356
-
2357
- Schema id: \`${symbol.id}\``;
2358
- const docs2 = symbol.documentation ? `
2359
-
2360
- ${symbol.documentation}` : "";
2361
- return {
2362
- range: reference2.range,
2363
- markdown: `**${symbol.name}** _${displaySymbolKind(symbol)}_
2364
-
2365
- \`${signature}\`${docs2}${origin}${identity2}`
2366
- };
2358
+ return symbolHover(symbol, reference2.range, snapshot.project);
2367
2359
  }
2368
2360
  const type = resolution.staticType;
2369
2361
  if (!type) return null;
@@ -2380,6 +2372,39 @@ ${type.documentation}` : "";
2380
2372
  Schema id: \`${type.id}\`${docs}`
2381
2373
  };
2382
2374
  }
2375
+ function symbolHover(symbol, range2, project) {
2376
+ const signature = formatSymbolSignature(symbol, project);
2377
+ const origin = symbol.inheritedFrom ? `
2378
+
2379
+ Inherited from \`${symbol.inheritedFrom.typeName}\`.` : "";
2380
+ const identity2 = symbol.id.startsWith("local:") || symbol.id.startsWith("parameter:") ? "" : `
2381
+
2382
+ Schema id: \`${symbol.id}\``;
2383
+ const docs = symbol.documentation ? `
2384
+
2385
+ ${symbol.documentation}` : "";
2386
+ return {
2387
+ range: range2,
2388
+ markdown: `**${symbol.name}** _${displaySymbolKind(symbol)}_
2389
+
2390
+ \`${signature}\`${docs}${origin}${identity2}`
2391
+ };
2392
+ }
2393
+ function builtinStaticMemberHoverAt(snapshot, offset) {
2394
+ const tokens = snapshot.lexed.tokens;
2395
+ const memberIndex = tokens.findIndex(
2396
+ (token) => token.kind === "identifier" && token.start <= offset && offset <= token.end
2397
+ );
2398
+ if (memberIndex < 2 || tokens[memberIndex - 1]?.text !== ".") return null;
2399
+ const owner = tokens[memberIndex - 2];
2400
+ const memberToken = tokens[memberIndex];
2401
+ if (owner?.kind !== "type" || !memberToken) return null;
2402
+ const type = snapshot.project.typeByName.get(owner.text);
2403
+ const member = type?.members.find(
2404
+ (candidate) => candidate.static === true && candidate.name === memberToken.text
2405
+ );
2406
+ return member ? symbolHover(member, memberToken.range, snapshot.project) : null;
2407
+ }
2383
2408
  function definition(snapshot, position) {
2384
2409
  const offset = snapshot.source.offsetAt(position);
2385
2410
  const reference2 = referenceAt(snapshot.parsed.references, offset);
@@ -27300,6 +27325,16 @@ function projectCompletions(analysis, document, position) {
27300
27325
  position
27301
27326
  );
27302
27327
  if (annotations) return { isIncomplete: false, items: annotations };
27328
+ const builtinMembers = projectBuiltinStaticMemberCompletions(
27329
+ document,
27330
+ position
27331
+ );
27332
+ if (builtinMembers) {
27333
+ return {
27334
+ isIncomplete: false,
27335
+ items: builtinMembers.map(projectBuiltinCompletionItem)
27336
+ };
27337
+ }
27303
27338
  const members = projectMemberCompletions(analysis, document, position);
27304
27339
  const variantScope = variantScopeInBody;
27305
27340
  if (members || variantScope) {
@@ -27800,20 +27835,8 @@ function sourceTypeAt(document, position) {
27800
27835
  return null;
27801
27836
  }
27802
27837
  function projectMemberCompletions(analysis, document, position) {
27803
- const tokens = projectTokens(document).filter(
27804
- (token) => token.kind !== "eof" && token.kind !== "comment" && // Auto-inserted/future punctuation at the cursor has not been authored
27805
- // yet for completion purposes. Including a semicolon that starts at the
27806
- // cursor makes `Device.|;` lose the dot it is completing.
27807
- positionCompare2(token.range.start, position) < 0
27808
- );
27809
- let cursor = tokens.length - 1;
27810
- const current = tokens[cursor];
27811
- if (current?.kind === "identifier" && current.range.start.line === position.line) {
27812
- cursor--;
27813
- }
27814
- if (tokens[cursor]?.text !== ".") return null;
27815
- const owner = tokens[cursor - 1];
27816
- if (!owner || owner.kind !== "identifier") return null;
27838
+ const owner = projectMemberCompletionOwnerToken(document, position);
27839
+ if (!owner) return null;
27817
27840
  const resolvedOwner = projectSymbolAt(analysis, document, owner.range.start);
27818
27841
  if (!resolvedOwner) return null;
27819
27842
  const ownerNames = projectReceiverOwnerNames(analysis, resolvedOwner.symbol);
@@ -27831,6 +27854,71 @@ function projectMemberCompletions(analysis, document, position) {
27831
27854
  }
27832
27855
  return members.length > 0 ? members : null;
27833
27856
  }
27857
+ function projectMemberCompletionOwnerToken(document, position) {
27858
+ const tokens = projectTokens(document).filter(
27859
+ (token) => token.kind !== "eof" && token.kind !== "comment" && // Auto-inserted/future punctuation at the cursor has not been authored
27860
+ // yet for completion purposes. Including a semicolon that starts at the
27861
+ // cursor makes `Device.|;` lose the dot it is completing.
27862
+ positionCompare2(token.range.start, position) < 0
27863
+ );
27864
+ let cursor = tokens.length - 1;
27865
+ const current = tokens[cursor];
27866
+ if (current?.kind === "identifier" && current.range.start.line === position.line) {
27867
+ cursor--;
27868
+ }
27869
+ if (tokens[cursor]?.text !== ".") return null;
27870
+ const owner = tokens[cursor - 1];
27871
+ if (!owner || owner.kind !== "identifier" && owner.kind !== "type") {
27872
+ return null;
27873
+ }
27874
+ return owner;
27875
+ }
27876
+ function projectBuiltinStaticMemberCompletions(document, position) {
27877
+ const owner = projectMemberCompletionOwnerToken(document, position);
27878
+ if (!owner) return null;
27879
+ const type = NEOSCRIPT_BUILTIN_TYPE_SYMBOLS.find(
27880
+ (candidate) => candidate.name === owner.text
27881
+ );
27882
+ if (!type) return null;
27883
+ const members = type.members.filter((member) => member.static === true);
27884
+ return members.length > 0 ? members : null;
27885
+ }
27886
+ function projectBuiltinCompletionItem(symbol) {
27887
+ return {
27888
+ label: symbol.name,
27889
+ kind: projectBuiltinCompletionKind(symbol),
27890
+ detail: projectBuiltinSymbolSignature(symbol),
27891
+ ...symbol.documentation ? { documentation: symbol.documentation } : {},
27892
+ insertText: symbol.name,
27893
+ symbolId: symbol.id
27894
+ };
27895
+ }
27896
+ function projectBuiltinCompletionKind(symbol) {
27897
+ switch (symbol.kind) {
27898
+ case "property":
27899
+ case "field":
27900
+ return "property";
27901
+ case "method":
27902
+ return "method";
27903
+ case "function":
27904
+ return "function";
27905
+ case "enumMember":
27906
+ return "enumMember";
27907
+ default:
27908
+ return "value";
27909
+ }
27910
+ }
27911
+ function projectBuiltinSymbolSignature(symbol) {
27912
+ if (symbol.parameters !== void 0) {
27913
+ const parameters = symbol.parameters.map((parameter4) => `${formatType(parameter4.type)} ${parameter4.name}`).join(", ");
27914
+ return `${formatType(symbol.returnType ?? symbol.type)} ${symbol.name}(${parameters})`;
27915
+ }
27916
+ if (symbol.kind === "property" || symbol.kind === "field") {
27917
+ const setter = symbol.writable ? " set;" : "";
27918
+ return `${formatType(symbol.type)} ${symbol.name} { get;${setter} }`;
27919
+ }
27920
+ return `${formatType(symbol.type)} ${symbol.name}`;
27921
+ }
27834
27922
  function projectConstructorArgumentCompletions(analysis, document, position) {
27835
27923
  const tokens = tokensBeforePosition(document.text, position);
27836
27924
  const openIndex = activeCallOpenIndex(tokens, position);
@@ -28202,6 +28290,31 @@ function projectHover(analysis, document, position) {
28202
28290
  }
28203
28291
  const body = projectBodySnapshotAt(analysis, document, position);
28204
28292
  if (body) return hover(body, position);
28293
+ const builtin = projectBuiltinSymbolAt(document, position);
28294
+ if (builtin) {
28295
+ if (builtin.symbol) {
28296
+ const documentation3 = builtin.symbol.documentation ? `
28297
+
28298
+ ${builtin.symbol.documentation}` : "";
28299
+ return {
28300
+ range: builtin.token.range,
28301
+ markdown: `**${builtin.symbol.name}** _${builtin.symbol.kind}_
28302
+
28303
+ \`${projectBuiltinSymbolSignature(builtin.symbol)}\`${documentation3}
28304
+
28305
+ Schema id: \`${builtin.symbol.id}\``
28306
+ };
28307
+ }
28308
+ const documentation2 = builtin.type.documentation ? `
28309
+
28310
+ ${builtin.type.documentation}` : "";
28311
+ return {
28312
+ range: builtin.token.range,
28313
+ markdown: `**${builtin.type.name}** _${builtin.type.kind}_${documentation2}
28314
+
28315
+ Schema id: \`${builtin.type.id}\``
28316
+ };
28317
+ }
28205
28318
  const resolved = projectSymbolAt(analysis, document, position);
28206
28319
  if (!resolved) return null;
28207
28320
  const owner = resolved.symbol.ownerName ? ` on \`${resolved.symbol.ownerName}\`` : "";
@@ -28218,6 +28331,30 @@ ID: \`${resolved.symbol.id}\`` : "\n\nPending ID";
28218
28331
  markdown: `**${resolved.symbol.kind}** \`${resolved.symbol.name}\`${detail}${owner}${documentation}${contract}${id2}`
28219
28332
  };
28220
28333
  }
28334
+ function projectBuiltinSymbolAt(document, position) {
28335
+ const token = projectTokenAt(document, position);
28336
+ if (!token || token.kind !== "identifier" && token.kind !== "type") {
28337
+ return null;
28338
+ }
28339
+ const tokens = projectTokens(document);
28340
+ const tokenIndex = tokens.findIndex(
28341
+ (candidate) => candidate.start === token.start && candidate.end === token.end
28342
+ );
28343
+ const owner = tokens[tokenIndex - 1]?.text === "." ? tokens[tokenIndex - 2] : void 0;
28344
+ if (owner?.kind === "identifier" || owner?.kind === "type") {
28345
+ const type2 = NEOSCRIPT_BUILTIN_TYPE_SYMBOLS.find(
28346
+ (candidate) => candidate.name === owner.text
28347
+ );
28348
+ const symbol = type2?.members.find(
28349
+ (candidate) => candidate.static === true && candidate.name === token.text
28350
+ );
28351
+ if (type2 && symbol) return { token, type: type2, symbol };
28352
+ }
28353
+ const type = NEOSCRIPT_BUILTIN_TYPE_SYMBOLS.find(
28354
+ (candidate) => candidate.name === token.text
28355
+ );
28356
+ return type ? { token, type } : null;
28357
+ }
28221
28358
  function projectConstructionContractMarkdown(analysis, className) {
28222
28359
  const index = projectConstructionIndex(analysis);
28223
28360
  const contract = classConstructionContract(index, className);
@@ -29373,6 +29510,7 @@ var init_project_source_language_features = __esm({
29373
29510
  init_language_spec();
29374
29511
  init_project_source_semantics();
29375
29512
  init_project_source_variants();
29513
+ init_project();
29376
29514
  init_project_source_semantics();
29377
29515
  init_project_source_settlement();
29378
29516
  init_project_source_tokens();
@@ -67158,6 +67296,7 @@ function isRuntimeFileValue(value) {
67158
67296
  return typeof fileId === "string" && fileId.length > 0;
67159
67297
  }
67160
67298
  function isRuntimeSpriteValue(value) {
67299
+ if (isEmptySpriteValue(value)) return true;
67161
67300
  if (!isRuntimeFileValue(value)) return false;
67162
67301
  const sliceIndex = value.sliceIndex;
67163
67302
  return typeof sliceIndex === "number" && Number.isInteger(sliceIndex) && sliceIndex >= 0;
@@ -111276,7 +111415,7 @@ var init_registry2 = __esm({
111276
111415
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
111277
111416
  formatVersion: 3,
111278
111417
  contractVersion: "3.13",
111279
- cliVersion: "0.31.8",
111418
+ cliVersion: "0.31.9",
111280
111419
  projectFileUploadBatchSize: 32,
111281
111420
  documentRecords: {
111282
111421
  member: {
@@ -117871,7 +118010,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
117871
118010
  async function main() {
117872
118011
  const args = parseArgs(process.argv.slice(2));
117873
118012
  if (args.command === "--version") {
117874
- console.log("0.31.8");
118013
+ console.log("0.31.9");
117875
118014
  return;
117876
118015
  }
117877
118016
  if (args.command === null || args.command === "help" || args.command === "--help" || args.command === "-h") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neocompose/cli",
3
- "version": "0.31.8",
3
+ "version": "0.31.9",
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.31.8 -->
12
+ <!-- reviewed-through-cli: 0.31.9 -->
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.31.8 -->
86
+ <!-- reviewed-through-cli: 0.31.9 -->
87
87
  ```
88
88
 
89
89
  The quoted version above is checked too, so this instruction cannot go stale
@@ -56,6 +56,10 @@ or narrowing guard before the next access.
56
56
 
57
57
  Treat lookup-return values with the same nullable narrowing rules.
58
58
 
59
+ Use `SpriteInfo.Empty` when a required sprite should intentionally render no
60
+ image. It is the canonical non-null empty sprite: it has no backing file and
61
+ uses slice index zero.
62
+
59
63
  Let runtime ownership—not body kind—decide whether a write target is Immutable,
60
64
  Save, Session, Setter, or otherwise writable. A constructor writes only its
61
65
  `this` instance. A writable structured-leaf field still inherits the receiver's