@vgai/cli 0.5.20 → 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 +347 -256
  2. package/package.json +7 -7
package/dist/index.js CHANGED
@@ -20163,6 +20163,13 @@ function readRegisteredSessions() {
20163
20163
  return [];
20164
20164
  }
20165
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
+ }
20166
20173
  async function fetchJson(url3, timeoutMs) {
20167
20174
  try {
20168
20175
  const res = await fetch(url3, { signal: AbortSignal.timeout(timeoutMs) });
@@ -20281,9 +20288,15 @@ var init_transport = __esm({
20281
20288
  const base = editorUrl.replace(/\/+$/, "");
20282
20289
  const body = await fetchJson(`${base}/__editor/project`, timeoutMs);
20283
20290
  if (body === void 0) return void 0;
20284
- const project = body.project?.path ?? null;
20291
+ const served = servedProjectPath(body);
20285
20292
  const port = url3.port ? Number(url3.port) : url3.protocol === "https:" ? 443 : 80;
20286
- 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
+ };
20287
20300
  }
20288
20301
  async listSessions(timeoutMs) {
20289
20302
  const registered = readRegisteredSessions();
@@ -20295,11 +20308,12 @@ var init_transport = __esm({
20295
20308
  perProbeTimeout
20296
20309
  );
20297
20310
  if (body === void 0) return void 0;
20298
- const project = body.project?.path ?? null;
20311
+ const served = servedProjectPath(body);
20299
20312
  const info = {
20300
20313
  port: s.port,
20301
- project,
20302
- pid: s.pid
20314
+ project: served.path,
20315
+ pid: s.pid,
20316
+ manifestError: served.manifestError
20303
20317
  };
20304
20318
  return info;
20305
20319
  })
@@ -42688,6 +42702,10 @@ function readRegisteredSessions2() {
42688
42702
  return [];
42689
42703
  }
42690
42704
  }
42705
+ function servedProjectPath2(body) {
42706
+ const b = body;
42707
+ return b.project?.path ?? b.serving?.path ?? null;
42708
+ }
42691
42709
  async function fetchJson2(url3, timeoutMs) {
42692
42710
  try {
42693
42711
  const res = await fetch(url3, { signal: AbortSignal.timeout(timeoutMs) });
@@ -42838,7 +42856,7 @@ var init_transport2 = __esm({
42838
42856
  const base = editorUrl.replace(/\/+$/, "");
42839
42857
  const body = await fetchJson2(`${base}/__editor/project`, timeoutMs);
42840
42858
  if (body === void 0) return void 0;
42841
- const project = body.project?.path ?? null;
42859
+ const project = servedProjectPath2(body);
42842
42860
  const port = url3.port ? Number(url3.port) : url3.protocol === "https:" ? 443 : 80;
42843
42861
  return { port, project, pid: null, url: base };
42844
42862
  }
@@ -42852,7 +42870,7 @@ var init_transport2 = __esm({
42852
42870
  perProbeTimeout
42853
42871
  );
42854
42872
  if (body === void 0) return void 0;
42855
- const project = body.project?.path ?? null;
42873
+ const project = servedProjectPath2(body);
42856
42874
  const info = {
42857
42875
  port: s.port,
42858
42876
  project,
@@ -45669,19 +45687,30 @@ function readProjectSession(projectRoot) {
45669
45687
  return null;
45670
45688
  }
45671
45689
  }
45672
- 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) {
45673
45698
  try {
45674
- 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`, {
45675
45700
  signal: AbortSignal.timeout(EDITOR_SESSION_DISCOVERY_TIMEOUT_MS)
45676
45701
  });
45677
- if (!response.ok) return false;
45678
- const body = await response.json();
45679
- const servedProject = body.project?.path;
45680
- return servedProject !== void 0 && canonicalPath(servedProject) === canonicalPath(projectRoot);
45702
+ if (!response.ok) return void 0;
45703
+ return servedProject(await response.json());
45681
45704
  } catch {
45682
- return false;
45705
+ return void 0;
45683
45706
  }
45684
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
+ }
45685
45714
  async function resolveSession(projectDir = process.cwd(), deps = {}) {
45686
45715
  const findRoot = deps.findProjectRootFrom ?? findProjectRootFrom;
45687
45716
  const transport = deps.transport ?? new HttpEditorTransport();
@@ -45692,8 +45721,18 @@ async function resolveSession(projectDir = process.cwd(), deps = {}) {
45692
45721
  );
45693
45722
  }
45694
45723
  const localHint = (deps.readProjectSession ?? readProjectSession)(projectRoot);
45695
- if (localHint && await (deps.verifyProjectSession ?? verifyProjectSession)(localHint, projectRoot)) {
45696
- 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
+ }
45697
45736
  }
45698
45737
  let sessions2;
45699
45738
  try {
@@ -45713,6 +45752,7 @@ async function resolveSession(projectDir = process.cwd(), deps = {}) {
45713
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.)` : "")
45714
45753
  );
45715
45754
  }
45755
+ if (match.manifestError != null) throw manifestRefusal(projectRoot, match.manifestError);
45716
45756
  return { port: match.port, projectRoot };
45717
45757
  }
45718
45758
  var init_session = __esm({
@@ -307087,6 +307127,9 @@ var require_lib = __commonJS({
307087
307127
 
307088
307128
  // src/index.ts
307089
307129
  init_src();
307130
+ init_src3();
307131
+ init_src2();
307132
+ init_inspection_node();
307090
307133
  import { execFileSync as execFileSync7, spawn as spawn4 } from "node:child_process";
307091
307134
  import {
307092
307135
  closeSync as closeSync3,
@@ -307106,234 +307149,6 @@ import { setTimeout as delay } from "node:timers/promises";
307106
307149
  import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL2 } from "node:url";
307107
307150
  import { inspect } from "node:util";
307108
307151
 
307109
- // ../editor/src/ingest/coverage-accounting.ts
307110
- var CoverageReconcileError = class extends Error {
307111
- constructor(message) {
307112
- super(`coverage-accounting: ${message}`);
307113
- this.name = "CoverageReconcileError";
307114
- }
307115
- };
307116
- function coverageSeamFamily(seam) {
307117
- const dot = seam.indexOf(".");
307118
- return dot === -1 ? seam : seam.slice(0, dot);
307119
- }
307120
- function subjectFromDetail(detail) {
307121
- if (!detail) return null;
307122
- const match = /^\[([^\]]+)\] /.exec(detail);
307123
- return match?.[1] ?? null;
307124
- }
307125
- function rowSubject(row) {
307126
- if (row.subject !== void 0 && row.subject !== null && row.subject !== "") {
307127
- return row.subject;
307128
- }
307129
- return subjectFromDetail(row.detail);
307130
- }
307131
- function foldSeam(existing, status) {
307132
- const fold = existing ?? { gap: false, applicable: false };
307133
- if (status === "na") return fold;
307134
- return { gap: fold.gap || status === "gap", applicable: true };
307135
- }
307136
- function familyAccounting(family, rows) {
307137
- const folds = /* @__PURE__ */ new Map();
307138
- for (const row of rows) {
307139
- folds.set(row.seam, foldSeam(folds.get(row.seam), row.status));
307140
- }
307141
- let capabilities = 0;
307142
- let capabilityGaps = 0;
307143
- for (const fold of folds.values()) {
307144
- if (!fold.applicable) continue;
307145
- capabilities += 1;
307146
- if (fold.gap) capabilityGaps += 1;
307147
- }
307148
- return { family, capabilities, capabilityGaps, rootInstances: rows.length };
307149
- }
307150
- function deriveCoverageAccounting(rows) {
307151
- const subjects = /* @__PURE__ */ new Set();
307152
- const familyRows = /* @__PURE__ */ new Map();
307153
- for (const row of rows) {
307154
- const subject = rowSubject(row);
307155
- if (subject !== null) subjects.add(subject);
307156
- const family = coverageSeamFamily(row.seam);
307157
- const list = familyRows.get(family);
307158
- if (list) list.push(row);
307159
- else familyRows.set(family, [row]);
307160
- }
307161
- const families = [...familyRows].map(
307162
- ([family, familyList]) => familyAccounting(family, familyList)
307163
- );
307164
- const folds = /* @__PURE__ */ new Map();
307165
- for (const row of rows) {
307166
- folds.set(row.seam, foldSeam(folds.get(row.seam), row.status));
307167
- }
307168
- let capabilities = 0;
307169
- let capabilityGaps = 0;
307170
- for (const fold of folds.values()) {
307171
- if (!fold.applicable) continue;
307172
- capabilities += 1;
307173
- if (fold.gap) capabilityGaps += 1;
307174
- }
307175
- return {
307176
- capabilities,
307177
- capabilityGaps,
307178
- rootsGraded: subjects.size,
307179
- rootInstances: rows.length,
307180
- families
307181
- };
307182
- }
307183
- function formatCoverageMissingLead(accounting) {
307184
- return `${accounting.capabilityGaps} of ${accounting.capabilities} capabilities are MISSING`;
307185
- }
307186
- function formatCoverageDerivation(accounting) {
307187
- const roots = accounting.rootsGraded > 0 ? `${accounting.rootsGraded} roots graded; ` : "";
307188
- return `${roots}${accounting.rootInstances} root-instances`;
307189
- }
307190
- function formatCoverageFamilyBreakdown(family, opts = {}) {
307191
- const roots = opts.rootsGraded ?? 0;
307192
- const showInstances = family.rootInstances !== family.capabilities || roots > 1;
307193
- const head = `${family.family} ${family.capabilityGaps} of ${family.capabilities} capabilities`;
307194
- if (!showInstances) return head;
307195
- const rootsBit = roots > 1 ? `${roots} roots graded; ` : "";
307196
- return `${head} (${rootsBit}${family.rootInstances} root-instances)`;
307197
- }
307198
- function parseCoverageHeadline(headline) {
307199
- const lead = /(\d+) of (\d+) capabilities are MISSING/.exec(headline);
307200
- if (!lead) {
307201
- throw new CoverageReconcileError(
307202
- 'headline does not state the capabilities unit (missing "N of M capabilities are MISSING")'
307203
- );
307204
- }
307205
- const roots = /(\d+) roots graded/.exec(headline);
307206
- const instances2 = /(\d+) root-instances/.exec(headline);
307207
- if (!instances2) {
307208
- throw new CoverageReconcileError("headline does not state root-instances");
307209
- }
307210
- return {
307211
- capabilityGaps: Number(lead[1]),
307212
- capabilities: Number(lead[2]),
307213
- rootsGraded: roots ? Number(roots[1]) : 0,
307214
- rootInstances: Number(instances2[1])
307215
- };
307216
- }
307217
- function assertCoverageReconciles(rows, headline) {
307218
- const derived = deriveCoverageAccounting(rows);
307219
- const parsed = parseCoverageHeadline(headline);
307220
- if (parsed.capabilityGaps !== derived.capabilityGaps) {
307221
- throw new CoverageReconcileError(
307222
- `headline capability-gaps ${parsed.capabilityGaps} !== row-derived ${derived.capabilityGaps}`
307223
- );
307224
- }
307225
- if (parsed.capabilities !== derived.capabilities) {
307226
- throw new CoverageReconcileError(
307227
- `headline capabilities ${parsed.capabilities} !== row-derived ${derived.capabilities}`
307228
- );
307229
- }
307230
- if (parsed.rootInstances !== derived.rootInstances) {
307231
- throw new CoverageReconcileError(
307232
- `headline root-instances ${parsed.rootInstances} !== row-derived ${derived.rootInstances}`
307233
- );
307234
- }
307235
- if (parsed.rootsGraded !== derived.rootsGraded) {
307236
- throw new CoverageReconcileError(
307237
- `headline roots-graded ${parsed.rootsGraded} !== row-derived ${derived.rootsGraded}`
307238
- );
307239
- }
307240
- const familyInstanceSum = derived.families.reduce((sum, family) => sum + family.rootInstances, 0);
307241
- if (familyInstanceSum !== derived.rootInstances) {
307242
- throw new CoverageReconcileError(
307243
- `family root-instances ${familyInstanceSum} !== ${derived.rootInstances}`
307244
- );
307245
- }
307246
- const familyCapabilitySum = derived.families.reduce(
307247
- (sum, family) => sum + family.capabilities,
307248
- 0
307249
- );
307250
- if (familyCapabilitySum !== derived.capabilities) {
307251
- throw new CoverageReconcileError(
307252
- `family capabilities ${familyCapabilitySum} !== ${derived.capabilities}`
307253
- );
307254
- }
307255
- }
307256
-
307257
- // src/coverage-warning.ts
307258
- function coverageRowsOf(facet, subject) {
307259
- const rows = facet?.rows;
307260
- if (!Array.isArray(rows)) return [];
307261
- const read = [];
307262
- for (const row of rows) {
307263
- if (typeof row !== "object" || row === null) continue;
307264
- const candidate = row;
307265
- if (typeof candidate.seam !== "string" || typeof candidate.status !== "string") continue;
307266
- read.push({
307267
- seam: candidate.seam,
307268
- status: candidate.status,
307269
- ...typeof candidate.missing === "string" ? { missing: candidate.missing } : {},
307270
- ...typeof candidate.fix === "string" ? { fix: candidate.fix } : {},
307271
- subject
307272
- });
307273
- }
307274
- return read;
307275
- }
307276
- function coverageWarning(state) {
307277
- const ingest = state.ingest;
307278
- const ingestWorldId = typeof ingest?.worldId === "string" ? ingest.worldId : null;
307279
- const collected = [
307280
- // The ingest mount's own report first: it is the richest one, so where it
307281
- // and `rootCoverage` describe the same root, its row is the one kept.
307282
- ...coverageRowsOf(ingest?.coverage, ingestWorldId),
307283
- ...(Array.isArray(state.rootCoverage) ? state.rootCoverage : []).flatMap(
307284
- (entry) => coverageRowsOf(
307285
- entry,
307286
- typeof entry?.worldId === "string" ? entry.worldId ?? null : null
307287
- )
307288
- ),
307289
- ...coverageRowsOf(state.systemCoverage, null),
307290
- ...coverageRowsOf(state.projectCoverage, null),
307291
- ...coverageRowsOf(state.authoringCoverage, null)
307292
- ];
307293
- const seen = /* @__PURE__ */ new Set();
307294
- const rows = collected.filter((row) => {
307295
- const key = `${row.subject ?? ""}\0${row.seam}`;
307296
- if (seen.has(key)) return false;
307297
- seen.add(key);
307298
- return true;
307299
- });
307300
- const gaps = rows.filter((row) => row.status === "gap");
307301
- if (gaps.length === 0) return null;
307302
- const accounting = deriveCoverageAccounting(rows);
307303
- const lead = formatCoverageMissingLead(accounting);
307304
- assertCoverageReconciles(rows, `${lead} (${formatCoverageDerivation(accounting)})`);
307305
- const breakdown = accounting.families.map(
307306
- (family) => formatCoverageFamilyBreakdown(family, {
307307
- rootsGraded: family.family === "editor" ? accounting.rootsGraded : 0
307308
- })
307309
- ).join(", ");
307310
- const subject = ingestWorldId ?? state.projectName ?? "(this session)";
307311
- const lines = gaps.flatMap((gap) => [
307312
- ` \u2717 ${gap.subject ? `${gap.subject}: ` : ""}${gap.seam}`,
307313
- ...gap.missing ? [` missing: ${gap.missing}`] : [],
307314
- ...gap.fix ? [` fix: ${gap.fix}`] : []
307315
- ]);
307316
- return [
307317
- "================================================================",
307318
- ` COVERAGE \u2014 ${lead.toUpperCase()} IN "${subject}" (${formatCoverageDerivation(accounting)})`,
307319
- ` by family: ${breakdown}`,
307320
- "================================================================",
307321
- " This game is mounted and running. It is what the editor CANNOT do",
307322
- " with it that is listed here \u2014 each line names the mechanism that",
307323
- " would close the gap:",
307324
- "",
307325
- ...lines,
307326
- "",
307327
- " The seams that ARE present are in the coverage facets in the JSON below.",
307328
- "================================================================"
307329
- ].join("\n");
307330
- }
307331
-
307332
- // src/index.ts
307333
- init_src3();
307334
- init_src2();
307335
- init_inspection_node();
307336
-
307337
307152
  // ../create-vgai-project/src/scaffold.ts
307338
307153
  import { spawnSync as spawnSync3 } from "node:child_process";
307339
307154
  import {
@@ -313317,6 +313132,229 @@ function emitJsonCompact(payload) {
313317
313132
  );
313318
313133
  }
313319
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
+
313320
313358
  // src/create-auto-edit-gate.ts
313321
313359
  function shouldAutoLaunchEditor(input) {
313322
313360
  if (input.noEditFlag) return false;
@@ -313504,7 +313542,15 @@ async function fetchEditorSession(port, timeoutMs = 5e3) {
313504
313542
  const session = body.session;
313505
313543
  return {
313506
313544
  port,
313507
- 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,
313508
313554
  pid: typeof session?.["pid"] === "number" ? session["pid"] : null,
313509
313555
  sessionId: typeof session?.["sessionId"] === "string" ? session["sessionId"] : null,
313510
313556
  repositoryId: typeof session?.["repositoryId"] === "string" ? session["repositoryId"] : null,
@@ -313704,6 +313750,7 @@ async function verifiedSessions(alsoCheckPort) {
313704
313750
  branch: probed.branch,
313705
313751
  headCommit: probed.headCommit,
313706
313752
  baseCommit: probed.baseCommit,
313753
+ manifestError: probed.manifestError,
313707
313754
  registered: true,
313708
313755
  ...probed.ephemeral !== void 0 ? { ephemeral: probed.ephemeral } : {}
313709
313756
  });
@@ -313723,6 +313770,7 @@ async function verifiedSessions(alsoCheckPort) {
313723
313770
  branch: probed.branch,
313724
313771
  headCommit: probed.headCommit,
313725
313772
  baseCommit: probed.baseCommit,
313773
+ manifestError: probed.manifestError,
313726
313774
  registered: false,
313727
313775
  ...probed.ephemeral !== void 0 ? { ephemeral: probed.ephemeral } : {}
313728
313776
  });
@@ -323342,14 +323390,22 @@ function SessionsMonitor({
323342
323390
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { bold: true, color: "magenta", children: "vgai sessions \u2014 live" }),
323343
323391
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { flexDirection: "column", marginTop: 1, children: [
323344
323392
  sessions2.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { dimColor: true, children: "No live editor sessions." }),
323345
- sessions2.map((s) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Text, { children: [
323346
- " ",
323347
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "green", children: "\u25CF" }),
323348
- " http://127.0.0.1:",
323349
- s.port,
323350
- "/ \u2192 ",
323351
- s.project ?? "(no project)",
323352
- 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
+ ] })
323353
323409
  ] }, s.port))
323354
323410
  ] }),
323355
323411
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Box_default, { marginTop: 1, children: error48 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Text, { color: "red", children: [
@@ -326946,8 +327002,8 @@ function catalogDistributionDir() {
326946
327002
  );
326947
327003
  }
326948
327004
  function cliVersion() {
326949
- if ("0.5.20") {
326950
- return "0.5.20";
327005
+ if ("0.5.21") {
327006
+ return "0.5.21";
326951
327007
  }
326952
327008
  try {
326953
327009
  const pkg = JSON.parse(readFileSync32(join39(__dirname4, "..", "package.json"), "utf8"));
@@ -326966,7 +327022,7 @@ function bakedTargetVersions() {
326966
327022
  if (false)
326967
327023
  return void 0;
326968
327024
  try {
326969
- return JSON.parse('{"@vgai/engine":"0.5.20","@vgai/editor":"0.5.20","@vgai/p2p-colyseus":"0.5.20","@vgai/live":"0.5.20","@vgai/sdk":"0.5.20","@vgai/editor-sdk":"0.5.20","@vgai/cli":"0.5.20"}');
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"}');
326970
327026
  } catch {
326971
327027
  return void 0;
326972
327028
  }
@@ -327635,6 +327691,29 @@ function staleAssetCacheWarning(state) {
327635
327691
  "================================================================"
327636
327692
  ].join("\n");
327637
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
+ }
327638
327717
  function authoringWarningsWarning(state) {
327639
327718
  const warnings = state.projectWarnings;
327640
327719
  if (!warnings || typeof warnings !== "object") return null;
@@ -327969,6 +328048,10 @@ function restartRequiredWarning(state) {
327969
328048
  "================================================================"
327970
328049
  ].join("\n");
327971
328050
  }
328051
+ function manifestErrorLines(manifestError) {
328052
+ const [first = "", ...rest] = manifestError.split("\n");
328053
+ return [`\u2716 vgai.project.json: ${first}`, ...rest];
328054
+ }
327972
328055
  async function findLiveEditorSession(projectRoot) {
327973
328056
  const canon = canonicalPath2(projectRoot);
327974
328057
  const sessions2 = await verifiedSessions(DEFAULT_PORT2);
@@ -331143,6 +331226,9 @@ checkout synchronization are separate. Sharing does not update another clone.`);
331143
331226
  for (const s of sessions2) {
331144
331227
  const tag = s.pid === null ? " (unregistered legacy server)" : "";
331145
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
+ }
331146
331232
  }
331147
331233
  break;
331148
331234
  }
@@ -331794,6 +331880,10 @@ Choose exactly one target: project, --port/--url, --all, or --everywhere.`
331794
331880
  if (coverage) {
331795
331881
  console.warn(coverage);
331796
331882
  }
331883
+ const validationFailures = projectValidationFailureBanner(state);
331884
+ if (validationFailures) {
331885
+ console.warn(validationFailures);
331886
+ }
331797
331887
  const authoringWarnings = authoringWarningsWarning(state);
331798
331888
  if (authoringWarnings) {
331799
331889
  console.warn(authoringWarnings);
@@ -331930,6 +332020,7 @@ export {
331930
332020
  playFailureReport,
331931
332021
  playLogErrorMessages,
331932
332022
  probeLoopTicks,
332023
+ projectValidationFailureBanner,
331933
332024
  resolveCliEngineRoot,
331934
332025
  resolvePackagedEditorEntry,
331935
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.20",
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.20",
43
- "@vgai/editor-sdk": "0.5.20",
44
- "@vgai/engine": "0.5.20",
45
- "@vgai/live": "0.5.20",
46
- "@vgai/p2p-colyseus": "0.5.20",
47
- "@vgai/sdk": "0.5.20",
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",