@vgai/cli 0.5.19 → 0.5.21

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 (2) hide show
  1. package/dist/index.js +348 -256
  2. package/package.json +7 -7
package/dist/index.js CHANGED
@@ -830,6 +830,7 @@ var init_src = __esm({
830
830
  "use strict";
831
831
  init_client();
832
832
  init_editor_view();
833
+ init_types();
833
834
  }
834
835
  });
835
836
 
@@ -20162,6 +20163,13 @@ function readRegisteredSessions() {
20162
20163
  return [];
20163
20164
  }
20164
20165
  }
20166
+ function servedProjectPath(body) {
20167
+ const b = body;
20168
+ return {
20169
+ path: b.project?.path ?? b.serving?.path ?? null,
20170
+ manifestError: b.project ? null : b.serving?.error ?? null
20171
+ };
20172
+ }
20165
20173
  async function fetchJson(url3, timeoutMs) {
20166
20174
  try {
20167
20175
  const res = await fetch(url3, { signal: AbortSignal.timeout(timeoutMs) });
@@ -20280,9 +20288,15 @@ var init_transport = __esm({
20280
20288
  const base = editorUrl.replace(/\/+$/, "");
20281
20289
  const body = await fetchJson(`${base}/__editor/project`, timeoutMs);
20282
20290
  if (body === void 0) return void 0;
20283
- const project = body.project?.path ?? null;
20291
+ const served = servedProjectPath(body);
20284
20292
  const port = url3.port ? Number(url3.port) : url3.protocol === "https:" ? 443 : 80;
20285
- return { port, project, pid: null, url: base };
20293
+ return {
20294
+ port,
20295
+ project: served.path,
20296
+ pid: null,
20297
+ url: base,
20298
+ manifestError: served.manifestError
20299
+ };
20286
20300
  }
20287
20301
  async listSessions(timeoutMs) {
20288
20302
  const registered = readRegisteredSessions();
@@ -20294,11 +20308,12 @@ var init_transport = __esm({
20294
20308
  perProbeTimeout
20295
20309
  );
20296
20310
  if (body === void 0) return void 0;
20297
- const project = body.project?.path ?? null;
20311
+ const served = servedProjectPath(body);
20298
20312
  const info = {
20299
20313
  port: s.port,
20300
- project,
20301
- pid: s.pid
20314
+ project: served.path,
20315
+ pid: s.pid,
20316
+ manifestError: served.manifestError
20302
20317
  };
20303
20318
  return info;
20304
20319
  })
@@ -42687,6 +42702,10 @@ function readRegisteredSessions2() {
42687
42702
  return [];
42688
42703
  }
42689
42704
  }
42705
+ function servedProjectPath2(body) {
42706
+ const b = body;
42707
+ return b.project?.path ?? b.serving?.path ?? null;
42708
+ }
42690
42709
  async function fetchJson2(url3, timeoutMs) {
42691
42710
  try {
42692
42711
  const res = await fetch(url3, { signal: AbortSignal.timeout(timeoutMs) });
@@ -42837,7 +42856,7 @@ var init_transport2 = __esm({
42837
42856
  const base = editorUrl.replace(/\/+$/, "");
42838
42857
  const body = await fetchJson2(`${base}/__editor/project`, timeoutMs);
42839
42858
  if (body === void 0) return void 0;
42840
- const project = body.project?.path ?? null;
42859
+ const project = servedProjectPath2(body);
42841
42860
  const port = url3.port ? Number(url3.port) : url3.protocol === "https:" ? 443 : 80;
42842
42861
  return { port, project, pid: null, url: base };
42843
42862
  }
@@ -42851,7 +42870,7 @@ var init_transport2 = __esm({
42851
42870
  perProbeTimeout
42852
42871
  );
42853
42872
  if (body === void 0) return void 0;
42854
- const project = body.project?.path ?? null;
42873
+ const project = servedProjectPath2(body);
42855
42874
  const info = {
42856
42875
  port: s.port,
42857
42876
  project,
@@ -45668,19 +45687,30 @@ function readProjectSession(projectRoot) {
45668
45687
  return null;
45669
45688
  }
45670
45689
  }
45671
- async function verifyProjectSession(hint, projectRoot) {
45690
+ function servedProject(body) {
45691
+ const b = body;
45692
+ return {
45693
+ path: b.project?.path ?? b.serving?.path ?? null,
45694
+ manifestError: b.project ? null : b.serving?.error ?? null
45695
+ };
45696
+ }
45697
+ async function probeServedProject(port) {
45672
45698
  try {
45673
- const response = await fetch(`http://127.0.0.1:${hint.port}/__editor/project`, {
45699
+ const response = await fetch(`http://127.0.0.1:${port}/__editor/project`, {
45674
45700
  signal: AbortSignal.timeout(EDITOR_SESSION_DISCOVERY_TIMEOUT_MS)
45675
45701
  });
45676
- if (!response.ok) return false;
45677
- const body = await response.json();
45678
- const servedProject = body.project?.path;
45679
- return servedProject !== void 0 && canonicalPath(servedProject) === canonicalPath(projectRoot);
45702
+ if (!response.ok) return void 0;
45703
+ return servedProject(await response.json());
45680
45704
  } catch {
45681
- return false;
45705
+ return void 0;
45682
45706
  }
45683
45707
  }
45708
+ function manifestRefusal(projectRoot, manifestError) {
45709
+ return new Error(
45710
+ `@vgai/live: the editor session for ${projectRoot} is live, but its vgai.project.json does not load, so there is no editor or game to drive \u2014 the editor page is showing this same error. Fix the manifest and retry; the session recovers on save, no restart needed.
45711
+ ${manifestError}`
45712
+ );
45713
+ }
45684
45714
  async function resolveSession(projectDir = process.cwd(), deps = {}) {
45685
45715
  const findRoot = deps.findProjectRootFrom ?? findProjectRootFrom;
45686
45716
  const transport = deps.transport ?? new HttpEditorTransport();
@@ -45691,8 +45721,18 @@ async function resolveSession(projectDir = process.cwd(), deps = {}) {
45691
45721
  );
45692
45722
  }
45693
45723
  const localHint = (deps.readProjectSession ?? readProjectSession)(projectRoot);
45694
- if (localHint && await (deps.verifyProjectSession ?? verifyProjectSession)(localHint, projectRoot)) {
45695
- return { port: localHint.port, projectRoot };
45724
+ if (localHint) {
45725
+ if (deps.verifyProjectSession) {
45726
+ if (await deps.verifyProjectSession(localHint, projectRoot)) {
45727
+ return { port: localHint.port, projectRoot };
45728
+ }
45729
+ } else {
45730
+ const served = await probeServedProject(localHint.port);
45731
+ if (served?.path != null && canonicalPath(served.path) === canonicalPath(projectRoot)) {
45732
+ if (served.manifestError !== null) throw manifestRefusal(projectRoot, served.manifestError);
45733
+ return { port: localHint.port, projectRoot };
45734
+ }
45735
+ }
45696
45736
  }
45697
45737
  let sessions2;
45698
45738
  try {
@@ -45712,6 +45752,7 @@ async function resolveSession(projectDir = process.cwd(), deps = {}) {
45712
45752
  `@vgai/live: no live editor session found for ${projectRoot}. @vgai/live only attaches to an already-running session \u2014 it never starts one \u2014 so run \`vgai edit\` in that project first, then retry.` + (otherCount > 0 ? ` (${otherCount} other live session(s) found, but none open this project \u2014 @vgai/live never silently attaches to a different project.)` : "")
45713
45753
  );
45714
45754
  }
45755
+ if (match.manifestError != null) throw manifestRefusal(projectRoot, match.manifestError);
45715
45756
  return { port: match.port, projectRoot };
45716
45757
  }
45717
45758
  var init_session = __esm({
@@ -307086,6 +307127,9 @@ var require_lib = __commonJS({
307086
307127
 
307087
307128
  // src/index.ts
307088
307129
  init_src();
307130
+ init_src3();
307131
+ init_src2();
307132
+ init_inspection_node();
307089
307133
  import { execFileSync as execFileSync7, spawn as spawn4 } from "node:child_process";
307090
307134
  import {
307091
307135
  closeSync as closeSync3,
@@ -307105,234 +307149,6 @@ import { setTimeout as delay } from "node:timers/promises";
307105
307149
  import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL2 } from "node:url";
307106
307150
  import { inspect } from "node:util";
307107
307151
 
307108
- // ../editor/src/ingest/coverage-accounting.ts
307109
- var CoverageReconcileError = class extends Error {
307110
- constructor(message) {
307111
- super(`coverage-accounting: ${message}`);
307112
- this.name = "CoverageReconcileError";
307113
- }
307114
- };
307115
- function coverageSeamFamily(seam) {
307116
- const dot = seam.indexOf(".");
307117
- return dot === -1 ? seam : seam.slice(0, dot);
307118
- }
307119
- function subjectFromDetail(detail) {
307120
- if (!detail) return null;
307121
- const match = /^\[([^\]]+)\] /.exec(detail);
307122
- return match?.[1] ?? null;
307123
- }
307124
- function rowSubject(row) {
307125
- if (row.subject !== void 0 && row.subject !== null && row.subject !== "") {
307126
- return row.subject;
307127
- }
307128
- return subjectFromDetail(row.detail);
307129
- }
307130
- function foldSeam(existing, status) {
307131
- const fold = existing ?? { gap: false, applicable: false };
307132
- if (status === "na") return fold;
307133
- return { gap: fold.gap || status === "gap", applicable: true };
307134
- }
307135
- function familyAccounting(family, rows) {
307136
- const folds = /* @__PURE__ */ new Map();
307137
- for (const row of rows) {
307138
- folds.set(row.seam, foldSeam(folds.get(row.seam), row.status));
307139
- }
307140
- let capabilities = 0;
307141
- let capabilityGaps = 0;
307142
- for (const fold of folds.values()) {
307143
- if (!fold.applicable) continue;
307144
- capabilities += 1;
307145
- if (fold.gap) capabilityGaps += 1;
307146
- }
307147
- return { family, capabilities, capabilityGaps, rootInstances: rows.length };
307148
- }
307149
- function deriveCoverageAccounting(rows) {
307150
- const subjects = /* @__PURE__ */ new Set();
307151
- const familyRows = /* @__PURE__ */ new Map();
307152
- for (const row of rows) {
307153
- const subject = rowSubject(row);
307154
- if (subject !== null) subjects.add(subject);
307155
- const family = coverageSeamFamily(row.seam);
307156
- const list = familyRows.get(family);
307157
- if (list) list.push(row);
307158
- else familyRows.set(family, [row]);
307159
- }
307160
- const families = [...familyRows].map(
307161
- ([family, familyList]) => familyAccounting(family, familyList)
307162
- );
307163
- const folds = /* @__PURE__ */ new Map();
307164
- for (const row of rows) {
307165
- folds.set(row.seam, foldSeam(folds.get(row.seam), row.status));
307166
- }
307167
- let capabilities = 0;
307168
- let capabilityGaps = 0;
307169
- for (const fold of folds.values()) {
307170
- if (!fold.applicable) continue;
307171
- capabilities += 1;
307172
- if (fold.gap) capabilityGaps += 1;
307173
- }
307174
- return {
307175
- capabilities,
307176
- capabilityGaps,
307177
- rootsGraded: subjects.size,
307178
- rootInstances: rows.length,
307179
- families
307180
- };
307181
- }
307182
- function formatCoverageMissingLead(accounting) {
307183
- return `${accounting.capabilityGaps} of ${accounting.capabilities} capabilities are MISSING`;
307184
- }
307185
- function formatCoverageDerivation(accounting) {
307186
- const roots = accounting.rootsGraded > 0 ? `${accounting.rootsGraded} roots graded; ` : "";
307187
- return `${roots}${accounting.rootInstances} root-instances`;
307188
- }
307189
- function formatCoverageFamilyBreakdown(family, opts = {}) {
307190
- const roots = opts.rootsGraded ?? 0;
307191
- const showInstances = family.rootInstances !== family.capabilities || roots > 1;
307192
- const head = `${family.family} ${family.capabilityGaps} of ${family.capabilities} capabilities`;
307193
- if (!showInstances) return head;
307194
- const rootsBit = roots > 1 ? `${roots} roots graded; ` : "";
307195
- return `${head} (${rootsBit}${family.rootInstances} root-instances)`;
307196
- }
307197
- function parseCoverageHeadline(headline) {
307198
- const lead = /(\d+) of (\d+) capabilities are MISSING/.exec(headline);
307199
- if (!lead) {
307200
- throw new CoverageReconcileError(
307201
- 'headline does not state the capabilities unit (missing "N of M capabilities are MISSING")'
307202
- );
307203
- }
307204
- const roots = /(\d+) roots graded/.exec(headline);
307205
- const instances2 = /(\d+) root-instances/.exec(headline);
307206
- if (!instances2) {
307207
- throw new CoverageReconcileError("headline does not state root-instances");
307208
- }
307209
- return {
307210
- capabilityGaps: Number(lead[1]),
307211
- capabilities: Number(lead[2]),
307212
- rootsGraded: roots ? Number(roots[1]) : 0,
307213
- rootInstances: Number(instances2[1])
307214
- };
307215
- }
307216
- function assertCoverageReconciles(rows, headline) {
307217
- const derived = deriveCoverageAccounting(rows);
307218
- const parsed = parseCoverageHeadline(headline);
307219
- if (parsed.capabilityGaps !== derived.capabilityGaps) {
307220
- throw new CoverageReconcileError(
307221
- `headline capability-gaps ${parsed.capabilityGaps} !== row-derived ${derived.capabilityGaps}`
307222
- );
307223
- }
307224
- if (parsed.capabilities !== derived.capabilities) {
307225
- throw new CoverageReconcileError(
307226
- `headline capabilities ${parsed.capabilities} !== row-derived ${derived.capabilities}`
307227
- );
307228
- }
307229
- if (parsed.rootInstances !== derived.rootInstances) {
307230
- throw new CoverageReconcileError(
307231
- `headline root-instances ${parsed.rootInstances} !== row-derived ${derived.rootInstances}`
307232
- );
307233
- }
307234
- if (parsed.rootsGraded !== derived.rootsGraded) {
307235
- throw new CoverageReconcileError(
307236
- `headline roots-graded ${parsed.rootsGraded} !== row-derived ${derived.rootsGraded}`
307237
- );
307238
- }
307239
- const familyInstanceSum = derived.families.reduce((sum, family) => sum + family.rootInstances, 0);
307240
- if (familyInstanceSum !== derived.rootInstances) {
307241
- throw new CoverageReconcileError(
307242
- `family root-instances ${familyInstanceSum} !== ${derived.rootInstances}`
307243
- );
307244
- }
307245
- const familyCapabilitySum = derived.families.reduce(
307246
- (sum, family) => sum + family.capabilities,
307247
- 0
307248
- );
307249
- if (familyCapabilitySum !== derived.capabilities) {
307250
- throw new CoverageReconcileError(
307251
- `family capabilities ${familyCapabilitySum} !== ${derived.capabilities}`
307252
- );
307253
- }
307254
- }
307255
-
307256
- // src/coverage-warning.ts
307257
- function coverageRowsOf(facet, subject) {
307258
- const rows = facet?.rows;
307259
- if (!Array.isArray(rows)) return [];
307260
- const read = [];
307261
- for (const row of rows) {
307262
- if (typeof row !== "object" || row === null) continue;
307263
- const candidate = row;
307264
- if (typeof candidate.seam !== "string" || typeof candidate.status !== "string") continue;
307265
- read.push({
307266
- seam: candidate.seam,
307267
- status: candidate.status,
307268
- ...typeof candidate.missing === "string" ? { missing: candidate.missing } : {},
307269
- ...typeof candidate.fix === "string" ? { fix: candidate.fix } : {},
307270
- subject
307271
- });
307272
- }
307273
- return read;
307274
- }
307275
- function coverageWarning(state) {
307276
- const ingest = state.ingest;
307277
- const ingestWorldId = typeof ingest?.worldId === "string" ? ingest.worldId : null;
307278
- const collected = [
307279
- // The ingest mount's own report first: it is the richest one, so where it
307280
- // and `rootCoverage` describe the same root, its row is the one kept.
307281
- ...coverageRowsOf(ingest?.coverage, ingestWorldId),
307282
- ...(Array.isArray(state.rootCoverage) ? state.rootCoverage : []).flatMap(
307283
- (entry) => coverageRowsOf(
307284
- entry,
307285
- typeof entry?.worldId === "string" ? entry.worldId ?? null : null
307286
- )
307287
- ),
307288
- ...coverageRowsOf(state.systemCoverage, null),
307289
- ...coverageRowsOf(state.projectCoverage, null),
307290
- ...coverageRowsOf(state.authoringCoverage, null)
307291
- ];
307292
- const seen = /* @__PURE__ */ new Set();
307293
- const rows = collected.filter((row) => {
307294
- const key = `${row.subject ?? ""}\0${row.seam}`;
307295
- if (seen.has(key)) return false;
307296
- seen.add(key);
307297
- return true;
307298
- });
307299
- const gaps = rows.filter((row) => row.status === "gap");
307300
- if (gaps.length === 0) return null;
307301
- const accounting = deriveCoverageAccounting(rows);
307302
- const lead = formatCoverageMissingLead(accounting);
307303
- assertCoverageReconciles(rows, `${lead} (${formatCoverageDerivation(accounting)})`);
307304
- const breakdown = accounting.families.map(
307305
- (family) => formatCoverageFamilyBreakdown(family, {
307306
- rootsGraded: family.family === "editor" ? accounting.rootsGraded : 0
307307
- })
307308
- ).join(", ");
307309
- const subject = ingestWorldId ?? state.projectName ?? "(this session)";
307310
- const lines = gaps.flatMap((gap) => [
307311
- ` \u2717 ${gap.subject ? `${gap.subject}: ` : ""}${gap.seam}`,
307312
- ...gap.missing ? [` missing: ${gap.missing}`] : [],
307313
- ...gap.fix ? [` fix: ${gap.fix}`] : []
307314
- ]);
307315
- return [
307316
- "================================================================",
307317
- ` COVERAGE \u2014 ${lead.toUpperCase()} IN "${subject}" (${formatCoverageDerivation(accounting)})`,
307318
- ` by family: ${breakdown}`,
307319
- "================================================================",
307320
- " This game is mounted and running. It is what the editor CANNOT do",
307321
- " with it that is listed here \u2014 each line names the mechanism that",
307322
- " would close the gap:",
307323
- "",
307324
- ...lines,
307325
- "",
307326
- " The seams that ARE present are in the coverage facets in the JSON below.",
307327
- "================================================================"
307328
- ].join("\n");
307329
- }
307330
-
307331
- // src/index.ts
307332
- init_src3();
307333
- init_src2();
307334
- init_inspection_node();
307335
-
307336
307152
  // ../create-vgai-project/src/scaffold.ts
307337
307153
  import { spawnSync as spawnSync3 } from "node:child_process";
307338
307154
  import {
@@ -313316,6 +313132,229 @@ function emitJsonCompact(payload) {
313316
313132
  );
313317
313133
  }
313318
313134
 
313135
+ // ../editor/src/ingest/coverage-accounting.ts
313136
+ var CoverageReconcileError = class extends Error {
313137
+ constructor(message) {
313138
+ super(`coverage-accounting: ${message}`);
313139
+ this.name = "CoverageReconcileError";
313140
+ }
313141
+ };
313142
+ function coverageSeamFamily(seam) {
313143
+ const dot = seam.indexOf(".");
313144
+ return dot === -1 ? seam : seam.slice(0, dot);
313145
+ }
313146
+ function subjectFromDetail(detail) {
313147
+ if (!detail) return null;
313148
+ const match = /^\[([^\]]+)\] /.exec(detail);
313149
+ return match?.[1] ?? null;
313150
+ }
313151
+ function rowSubject(row) {
313152
+ if (row.subject !== void 0 && row.subject !== null && row.subject !== "") {
313153
+ return row.subject;
313154
+ }
313155
+ return subjectFromDetail(row.detail);
313156
+ }
313157
+ function foldSeam(existing, status) {
313158
+ const fold = existing ?? { gap: false, applicable: false };
313159
+ if (status === "na") return fold;
313160
+ return { gap: fold.gap || status === "gap", applicable: true };
313161
+ }
313162
+ function familyAccounting(family, rows) {
313163
+ const folds = /* @__PURE__ */ new Map();
313164
+ for (const row of rows) {
313165
+ folds.set(row.seam, foldSeam(folds.get(row.seam), row.status));
313166
+ }
313167
+ let capabilities = 0;
313168
+ let capabilityGaps = 0;
313169
+ for (const fold of folds.values()) {
313170
+ if (!fold.applicable) continue;
313171
+ capabilities += 1;
313172
+ if (fold.gap) capabilityGaps += 1;
313173
+ }
313174
+ return { family, capabilities, capabilityGaps, rootInstances: rows.length };
313175
+ }
313176
+ function deriveCoverageAccounting(rows) {
313177
+ const subjects = /* @__PURE__ */ new Set();
313178
+ const familyRows = /* @__PURE__ */ new Map();
313179
+ for (const row of rows) {
313180
+ const subject = rowSubject(row);
313181
+ if (subject !== null) subjects.add(subject);
313182
+ const family = coverageSeamFamily(row.seam);
313183
+ const list = familyRows.get(family);
313184
+ if (list) list.push(row);
313185
+ else familyRows.set(family, [row]);
313186
+ }
313187
+ const families = [...familyRows].map(
313188
+ ([family, familyList]) => familyAccounting(family, familyList)
313189
+ );
313190
+ const folds = /* @__PURE__ */ new Map();
313191
+ for (const row of rows) {
313192
+ folds.set(row.seam, foldSeam(folds.get(row.seam), row.status));
313193
+ }
313194
+ let capabilities = 0;
313195
+ let capabilityGaps = 0;
313196
+ for (const fold of folds.values()) {
313197
+ if (!fold.applicable) continue;
313198
+ capabilities += 1;
313199
+ if (fold.gap) capabilityGaps += 1;
313200
+ }
313201
+ return {
313202
+ capabilities,
313203
+ capabilityGaps,
313204
+ rootsGraded: subjects.size,
313205
+ rootInstances: rows.length,
313206
+ families
313207
+ };
313208
+ }
313209
+ function formatCoverageMissingLead(accounting) {
313210
+ return `${accounting.capabilityGaps} of ${accounting.capabilities} capabilities are MISSING`;
313211
+ }
313212
+ function formatCoverageDerivation(accounting) {
313213
+ const roots = accounting.rootsGraded > 0 ? `${accounting.rootsGraded} roots graded; ` : "";
313214
+ return `${roots}${accounting.rootInstances} root-instances`;
313215
+ }
313216
+ function formatCoverageFamilyBreakdown(family, opts = {}) {
313217
+ const roots = opts.rootsGraded ?? 0;
313218
+ const showInstances = family.rootInstances !== family.capabilities || roots > 1;
313219
+ const head = `${family.family} ${family.capabilityGaps} of ${family.capabilities} capabilities`;
313220
+ if (!showInstances) return head;
313221
+ const rootsBit = roots > 1 ? `${roots} roots graded; ` : "";
313222
+ return `${head} (${rootsBit}${family.rootInstances} root-instances)`;
313223
+ }
313224
+ function parseCoverageHeadline(headline) {
313225
+ const lead = /(\d+) of (\d+) capabilities are MISSING/.exec(headline);
313226
+ if (!lead) {
313227
+ throw new CoverageReconcileError(
313228
+ 'headline does not state the capabilities unit (missing "N of M capabilities are MISSING")'
313229
+ );
313230
+ }
313231
+ const roots = /(\d+) roots graded/.exec(headline);
313232
+ const instances2 = /(\d+) root-instances/.exec(headline);
313233
+ if (!instances2) {
313234
+ throw new CoverageReconcileError("headline does not state root-instances");
313235
+ }
313236
+ return {
313237
+ capabilityGaps: Number(lead[1]),
313238
+ capabilities: Number(lead[2]),
313239
+ rootsGraded: roots ? Number(roots[1]) : 0,
313240
+ rootInstances: Number(instances2[1])
313241
+ };
313242
+ }
313243
+ function assertCoverageReconciles(rows, headline) {
313244
+ const derived = deriveCoverageAccounting(rows);
313245
+ const parsed = parseCoverageHeadline(headline);
313246
+ if (parsed.capabilityGaps !== derived.capabilityGaps) {
313247
+ throw new CoverageReconcileError(
313248
+ `headline capability-gaps ${parsed.capabilityGaps} !== row-derived ${derived.capabilityGaps}`
313249
+ );
313250
+ }
313251
+ if (parsed.capabilities !== derived.capabilities) {
313252
+ throw new CoverageReconcileError(
313253
+ `headline capabilities ${parsed.capabilities} !== row-derived ${derived.capabilities}`
313254
+ );
313255
+ }
313256
+ if (parsed.rootInstances !== derived.rootInstances) {
313257
+ throw new CoverageReconcileError(
313258
+ `headline root-instances ${parsed.rootInstances} !== row-derived ${derived.rootInstances}`
313259
+ );
313260
+ }
313261
+ if (parsed.rootsGraded !== derived.rootsGraded) {
313262
+ throw new CoverageReconcileError(
313263
+ `headline roots-graded ${parsed.rootsGraded} !== row-derived ${derived.rootsGraded}`
313264
+ );
313265
+ }
313266
+ const familyInstanceSum = derived.families.reduce((sum, family) => sum + family.rootInstances, 0);
313267
+ if (familyInstanceSum !== derived.rootInstances) {
313268
+ throw new CoverageReconcileError(
313269
+ `family root-instances ${familyInstanceSum} !== ${derived.rootInstances}`
313270
+ );
313271
+ }
313272
+ const familyCapabilitySum = derived.families.reduce(
313273
+ (sum, family) => sum + family.capabilities,
313274
+ 0
313275
+ );
313276
+ if (familyCapabilitySum !== derived.capabilities) {
313277
+ throw new CoverageReconcileError(
313278
+ `family capabilities ${familyCapabilitySum} !== ${derived.capabilities}`
313279
+ );
313280
+ }
313281
+ }
313282
+
313283
+ // src/coverage-warning.ts
313284
+ function coverageRowsOf(facet, subject) {
313285
+ const rows = facet?.rows;
313286
+ if (!Array.isArray(rows)) return [];
313287
+ const read = [];
313288
+ for (const row of rows) {
313289
+ if (typeof row !== "object" || row === null) continue;
313290
+ const candidate = row;
313291
+ if (typeof candidate.seam !== "string" || typeof candidate.status !== "string") continue;
313292
+ read.push({
313293
+ seam: candidate.seam,
313294
+ status: candidate.status,
313295
+ ...typeof candidate.missing === "string" ? { missing: candidate.missing } : {},
313296
+ ...typeof candidate.fix === "string" ? { fix: candidate.fix } : {},
313297
+ subject
313298
+ });
313299
+ }
313300
+ return read;
313301
+ }
313302
+ function coverageWarning(state) {
313303
+ const ingest = state.ingest;
313304
+ const ingestWorldId = typeof ingest?.worldId === "string" ? ingest.worldId : null;
313305
+ const collected = [
313306
+ // The ingest mount's own report first: it is the richest one, so where it
313307
+ // and `rootCoverage` describe the same root, its row is the one kept.
313308
+ ...coverageRowsOf(ingest?.coverage, ingestWorldId),
313309
+ ...(Array.isArray(state.rootCoverage) ? state.rootCoverage : []).flatMap(
313310
+ (entry) => coverageRowsOf(
313311
+ entry,
313312
+ typeof entry?.worldId === "string" ? entry.worldId ?? null : null
313313
+ )
313314
+ ),
313315
+ ...coverageRowsOf(state.systemCoverage, null),
313316
+ ...coverageRowsOf(state.projectCoverage, null),
313317
+ ...coverageRowsOf(state.authoringCoverage, null)
313318
+ ];
313319
+ const seen = /* @__PURE__ */ new Set();
313320
+ const rows = collected.filter((row) => {
313321
+ const key = `${row.subject ?? ""}\0${row.seam}`;
313322
+ if (seen.has(key)) return false;
313323
+ seen.add(key);
313324
+ return true;
313325
+ });
313326
+ const gaps = rows.filter((row) => row.status === "gap");
313327
+ if (gaps.length === 0) return null;
313328
+ const accounting = deriveCoverageAccounting(rows);
313329
+ const lead = formatCoverageMissingLead(accounting);
313330
+ assertCoverageReconciles(rows, `${lead} (${formatCoverageDerivation(accounting)})`);
313331
+ const breakdown = accounting.families.map(
313332
+ (family) => formatCoverageFamilyBreakdown(family, {
313333
+ rootsGraded: family.family === "editor" ? accounting.rootsGraded : 0
313334
+ })
313335
+ ).join(", ");
313336
+ const subject = ingestWorldId ?? state.projectName ?? "(this session)";
313337
+ const lines = gaps.flatMap((gap) => [
313338
+ ` \u2717 ${gap.subject ? `${gap.subject}: ` : ""}${gap.seam}`,
313339
+ ...gap.missing ? [` missing: ${gap.missing}`] : [],
313340
+ ...gap.fix ? [` fix: ${gap.fix}`] : []
313341
+ ]);
313342
+ return [
313343
+ "================================================================",
313344
+ ` COVERAGE \u2014 ${lead.toUpperCase()} IN "${subject}" (${formatCoverageDerivation(accounting)})`,
313345
+ ` by family: ${breakdown}`,
313346
+ "================================================================",
313347
+ " This game is mounted and running. It is what the editor CANNOT do",
313348
+ " with it that is listed here \u2014 each line names the mechanism that",
313349
+ " would close the gap:",
313350
+ "",
313351
+ ...lines,
313352
+ "",
313353
+ " The seams that ARE present are in the coverage facets in the JSON below.",
313354
+ "================================================================"
313355
+ ].join("\n");
313356
+ }
313357
+
313319
313358
  // src/create-auto-edit-gate.ts
313320
313359
  function shouldAutoLaunchEditor(input) {
313321
313360
  if (input.noEditFlag) return false;
@@ -313503,7 +313542,15 @@ async function fetchEditorSession(port, timeoutMs = 5e3) {
313503
313542
  const session = body.session;
313504
313543
  return {
313505
313544
  port,
313506
- project: body.project?.path ?? null,
313545
+ // `serving` is the server's own statement of "I AM serving this project,
313546
+ // I just cannot describe it" — see the `!configResult.ok` branch of
313547
+ // `/__editor/project` (`editor-server.ts`), which added it precisely
313548
+ // because a bare `{ project: null }` is indistinguishable from "no
313549
+ // project open". Reading only `project.path` collapsed the two here too,
313550
+ // so a session whose manifest a save had just broken reported as
313551
+ // `(no project)` and every project-matched command lost it.
313552
+ project: body.project?.path ?? body.serving?.path ?? null,
313553
+ manifestError: body.project ? null : body.serving?.error ?? null,
313507
313554
  pid: typeof session?.["pid"] === "number" ? session["pid"] : null,
313508
313555
  sessionId: typeof session?.["sessionId"] === "string" ? session["sessionId"] : null,
313509
313556
  repositoryId: typeof session?.["repositoryId"] === "string" ? session["repositoryId"] : null,
@@ -313703,6 +313750,7 @@ async function verifiedSessions(alsoCheckPort) {
313703
313750
  branch: probed.branch,
313704
313751
  headCommit: probed.headCommit,
313705
313752
  baseCommit: probed.baseCommit,
313753
+ manifestError: probed.manifestError,
313706
313754
  registered: true,
313707
313755
  ...probed.ephemeral !== void 0 ? { ephemeral: probed.ephemeral } : {}
313708
313756
  });
@@ -313722,6 +313770,7 @@ async function verifiedSessions(alsoCheckPort) {
313722
313770
  branch: probed.branch,
313723
313771
  headCommit: probed.headCommit,
313724
313772
  baseCommit: probed.baseCommit,
313773
+ manifestError: probed.manifestError,
313725
313774
  registered: false,
313726
313775
  ...probed.ephemeral !== void 0 ? { ephemeral: probed.ephemeral } : {}
313727
313776
  });
@@ -323341,14 +323390,22 @@ function SessionsMonitor({
323341
323390
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { bold: true, color: "magenta", children: "vgai sessions \u2014 live" }),
323342
323391
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { flexDirection: "column", marginTop: 1, children: [
323343
323392
  sessions2.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { dimColor: true, children: "No live editor sessions." }),
323344
- sessions2.map((s) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Text, { children: [
323345
- " ",
323346
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "green", children: "\u25CF" }),
323347
- " http://127.0.0.1:",
323348
- s.port,
323349
- "/ \u2192 ",
323350
- s.project ?? "(no project)",
323351
- s.pid === null ? " (unregistered legacy server)" : ""
323393
+ sessions2.map((s) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { flexDirection: "column", children: [
323394
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Text, { children: [
323395
+ " ",
323396
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "green", children: "\u25CF" }),
323397
+ " http://127.0.0.1:",
323398
+ s.port,
323399
+ "/ \u2192",
323400
+ " ",
323401
+ s.project ?? "(no project)",
323402
+ s.pid === null ? " (unregistered legacy server)" : ""
323403
+ ] }),
323404
+ s.manifestError != null && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Text, { color: "red", children: [
323405
+ " ",
323406
+ "\u2716 vgai.project.json: ",
323407
+ s.manifestError.split("\n").join(" ")
323408
+ ] })
323352
323409
  ] }, s.port))
323353
323410
  ] }),
323354
323411
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Box_default, { marginTop: 1, children: error48 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Text, { color: "red", children: [
@@ -326945,8 +327002,8 @@ function catalogDistributionDir() {
326945
327002
  );
326946
327003
  }
326947
327004
  function cliVersion() {
326948
- if ("0.5.19") {
326949
- return "0.5.19";
327005
+ if ("0.5.21") {
327006
+ return "0.5.21";
326950
327007
  }
326951
327008
  try {
326952
327009
  const pkg = JSON.parse(readFileSync32(join39(__dirname4, "..", "package.json"), "utf8"));
@@ -326965,7 +327022,7 @@ function bakedTargetVersions() {
326965
327022
  if (false)
326966
327023
  return void 0;
326967
327024
  try {
326968
- return JSON.parse('{"@vgai/engine":"0.5.19","@vgai/editor":"0.5.19","@vgai/p2p-colyseus":"0.5.19","@vgai/live":"0.5.19","@vgai/sdk":"0.5.19","@vgai/editor-sdk":"0.5.19","@vgai/cli":"0.5.19"}');
327025
+ return JSON.parse('{"@vgai/engine":"0.5.21","@vgai/editor":"0.5.21","@vgai/p2p-colyseus":"0.5.21","@vgai/live":"0.5.21","@vgai/sdk":"0.5.21","@vgai/editor-sdk":"0.5.21","@vgai/cli":"0.5.21"}');
326969
327026
  } catch {
326970
327027
  return void 0;
326971
327028
  }
@@ -327634,6 +327691,29 @@ function staleAssetCacheWarning(state) {
327634
327691
  "================================================================"
327635
327692
  ].join("\n");
327636
327693
  }
327694
+ function projectValidationFailureBanner(state) {
327695
+ const validation = state.projectValidation;
327696
+ if (!validation || typeof validation !== "object") return null;
327697
+ const files = Object.entries(validation).filter(
327698
+ ([, entry]) => Array.isArray(entry?.errors) ? entry.errors.length > 0 : false
327699
+ );
327700
+ if (files.length === 0) return null;
327701
+ const lines = files.flatMap(([file2, entry]) => [
327702
+ ` \u2716 ${file2}`,
327703
+ ...entry.errors.map((err2) => ` ${err2}`)
327704
+ ]);
327705
+ return [
327706
+ "================================================================",
327707
+ ` ${files.length} PROJECT FILE(S) CURRENTLY FAIL VALIDATION`,
327708
+ "================================================================",
327709
+ ...lines,
327710
+ "",
327711
+ " The dev server is still serving this project \u2014 it just cannot read",
327712
+ " these files. Fix them and the session recovers on save, with no",
327713
+ " restart: this list is recomputed on every read.",
327714
+ "================================================================"
327715
+ ].join("\n");
327716
+ }
327637
327717
  function authoringWarningsWarning(state) {
327638
327718
  const warnings = state.projectWarnings;
327639
327719
  if (!warnings || typeof warnings !== "object") return null;
@@ -327968,6 +328048,10 @@ function restartRequiredWarning(state) {
327968
328048
  "================================================================"
327969
328049
  ].join("\n");
327970
328050
  }
328051
+ function manifestErrorLines(manifestError) {
328052
+ const [first = "", ...rest] = manifestError.split("\n");
328053
+ return [`\u2716 vgai.project.json: ${first}`, ...rest];
328054
+ }
327971
328055
  async function findLiveEditorSession(projectRoot) {
327972
328056
  const canon = canonicalPath2(projectRoot);
327973
328057
  const sessions2 = await verifiedSessions(DEFAULT_PORT2);
@@ -331142,6 +331226,9 @@ checkout synchronization are separate. Sharing does not update another clone.`);
331142
331226
  for (const s of sessions2) {
331143
331227
  const tag = s.pid === null ? " (unregistered legacy server)" : "";
331144
331228
  console.log(` http://127.0.0.1:${s.port}/ \u2192 ${s.project ?? "(no project)"}${tag}`);
331229
+ if (s.manifestError !== null) {
331230
+ for (const line of manifestErrorLines(s.manifestError)) console.log(` ${line}`);
331231
+ }
331145
331232
  }
331146
331233
  break;
331147
331234
  }
@@ -331793,6 +331880,10 @@ Choose exactly one target: project, --port/--url, --all, or --everywhere.`
331793
331880
  if (coverage) {
331794
331881
  console.warn(coverage);
331795
331882
  }
331883
+ const validationFailures = projectValidationFailureBanner(state);
331884
+ if (validationFailures) {
331885
+ console.warn(validationFailures);
331886
+ }
331796
331887
  const authoringWarnings = authoringWarningsWarning(state);
331797
331888
  if (authoringWarnings) {
331798
331889
  console.warn(authoringWarnings);
@@ -331929,6 +332020,7 @@ export {
331929
332020
  playFailureReport,
331930
332021
  playLogErrorMessages,
331931
332022
  probeLoopTicks,
332023
+ projectValidationFailureBanner,
331932
332024
  resolveCliEngineRoot,
331933
332025
  resolvePackagedEditorEntry,
331934
332026
  resolveUpgradeTargetVersions,
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@vgai/cli",
3
3
  "author": "Volter AI, Inc.",
4
4
  "license": "Apache-2.0",
5
- "version": "0.5.19",
5
+ "version": "0.5.21",
6
6
  "description": "Create, open, control, validate, and playtest VGAI game projects.",
7
7
  "keywords": [
8
8
  "game-engine",
@@ -39,12 +39,12 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "@oclif/core": "^4.11.14",
42
- "@vgai/editor": "0.5.19",
43
- "@vgai/editor-sdk": "0.5.19",
44
- "@vgai/engine": "0.5.19",
45
- "@vgai/live": "0.5.19",
46
- "@vgai/p2p-colyseus": "0.5.19",
47
- "@vgai/sdk": "0.5.19",
42
+ "@vgai/editor": "0.5.21",
43
+ "@vgai/editor-sdk": "0.5.21",
44
+ "@vgai/engine": "0.5.21",
45
+ "@vgai/live": "0.5.21",
46
+ "@vgai/p2p-colyseus": "0.5.21",
47
+ "@vgai/sdk": "0.5.21",
48
48
  "ink": "^7.1.0",
49
49
  "playwright": "^1.58.2",
50
50
  "react": "^19.2.4",