@patterkit/cli 0.2.6 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +750 -350
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -192615,6 +192615,7 @@ function readDirSafe(dir2) {
192615
192615
  function walkFiles(dir2, ext) {
192616
192616
  const out = [];
192617
192617
  for (const e of readDirSafe(dir2)) {
192618
+ if (e.name.startsWith(".")) continue;
192618
192619
  const p = join(dir2, e.name);
192619
192620
  if (e.isDirectory()) out.push(...walkFiles(p, ext));
192620
192621
  else if (e.isFile() && e.name.endsWith(ext)) out.push(p);
@@ -192748,6 +192749,347 @@ import { dirname as dirname2 } from "path";
192748
192749
  // ../ops/src/validate.ts
192749
192750
  import { readFileSync as readFileSync2, statSync as statSync2 } from "fs";
192750
192751
 
192752
+ // ../ops/src/merge.ts
192753
+ var UnsupportedMergeError = class extends Error {
192754
+ };
192755
+ var eq = (a, b) => canonicalStringify(a) === canonicalStringify(b);
192756
+ var isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
192757
+ var CONFLICT_SIDECAR = ".patterconflict";
192758
+ function sidecarIssues(sidecarPaths) {
192759
+ return sidecarPaths.map((file) => ({
192760
+ file,
192761
+ message: "unresolved merge conflict - resolve it and delete the .patterconflict sidecar before committing"
192762
+ }));
192763
+ }
192764
+ function detectMergeType(file) {
192765
+ const s = typeof file.schema === "string" ? file.schema : "";
192766
+ if (s.startsWith("patter/flow")) return "flow";
192767
+ if (s.startsWith("patter/strings")) return "loc";
192768
+ if (s.startsWith("patter/authoring")) return "authoring";
192769
+ if (s.startsWith("patter/project")) return "project";
192770
+ throw new UnsupportedMergeError(`cannot detect a Patter merge type from schema '${s}'`);
192771
+ }
192772
+ function runMerge(base, ours, theirs, opts) {
192773
+ const type = opts?.type ?? detectMergeType(ours);
192774
+ switch (type) {
192775
+ case "loc":
192776
+ return mergeLoc(base, ours, theirs);
192777
+ case "authoring":
192778
+ return mergeAuthoring(base, ours, theirs);
192779
+ case "flow":
192780
+ return mergeFlow(base, ours, theirs);
192781
+ case "project":
192782
+ return mergeProject(base, ours, theirs);
192783
+ }
192784
+ }
192785
+ function deletedKind(base, ours, theirs) {
192786
+ const deleted = base !== void 0 && (ours === void 0 || theirs === void 0);
192787
+ return deleted ? "delete-vs-edit" : "both-changed";
192788
+ }
192789
+ function merge3(base, ours, theirs, id, path, conflicts) {
192790
+ if (eq(ours, theirs)) return ours;
192791
+ if (eq(base, ours)) return theirs;
192792
+ if (eq(base, theirs)) return ours;
192793
+ conflicts.push({ id, path, base, ours, theirs, kind: deletedKind(base, ours, theirs) });
192794
+ return ours;
192795
+ }
192796
+ function mergeMap(base, ours, theirs, prefix, conflicts, resolve6) {
192797
+ const out = {};
192798
+ for (const k of /* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(ours), ...Object.keys(theirs)])) {
192799
+ const b = base[k], o = ours[k], t = theirs[k];
192800
+ let v;
192801
+ if (eq(o, t)) v = o;
192802
+ else if (eq(b, o)) v = t;
192803
+ else if (eq(b, t)) v = o;
192804
+ else {
192805
+ const r = resolve6?.(k, b, o, t);
192806
+ if (r && r.resolved) v = r.value;
192807
+ else {
192808
+ conflicts.push({ id: k, path: `${prefix}.${k}`, base: b, ours: o, theirs: t, kind: r ? r.kind : deletedKind(b, o, t) });
192809
+ v = o;
192810
+ }
192811
+ }
192812
+ if (v !== void 0) out[k] = v;
192813
+ }
192814
+ return out;
192815
+ }
192816
+ var asMap = (v) => isObj(v) ? v : {};
192817
+ var asArr = (v) => Array.isArray(v) ? v : [];
192818
+ function setIf(obj2, key2, val) {
192819
+ if (Array.isArray(val) ? val.length > 0 : Object.keys(val).length > 0) obj2[key2] = val;
192820
+ }
192821
+ function mergeLoc(base, ours, theirs) {
192822
+ const conflicts = [];
192823
+ const merged = {};
192824
+ for (const f of ["schema", "scene", "locale", "default"]) {
192825
+ const v = merge3(base[f], ours[f], theirs[f], "", f, conflicts);
192826
+ if (v !== void 0) merged[f] = v;
192827
+ }
192828
+ merged.strings = mergeMap(asMap(base.strings), asMap(ours.strings), asMap(theirs.strings), "strings", conflicts);
192829
+ return { type: "loc", merged, conflicts, warnings: [] };
192830
+ }
192831
+ function mergeAuthoring(base, ours, theirs) {
192832
+ const conflicts = [];
192833
+ const merged = {};
192834
+ merged.schema = merge3(base.schema, ours.schema, theirs.schema, "", "schema", conflicts) ?? ours.schema;
192835
+ setIf(merged, "comments", mergeComments(asArr(base.comments), asArr(ours.comments), asArr(theirs.comments)));
192836
+ const edits = mergeEdits(asMap(base.edits), asMap(ours.edits), asMap(theirs.edits));
192837
+ setIf(merged, "edits", edits);
192838
+ const lww = lastWriterWins(asMap(ours.edits), asMap(theirs.edits));
192839
+ for (const f of ["writing", "recording", "audio"]) {
192840
+ setIf(merged, f, mergeMap(asMap(base[f]), asMap(ours[f]), asMap(theirs[f]), f, conflicts, lww));
192841
+ }
192842
+ setIf(merged, "documentation", mergeMap(asMap(base.documentation), asMap(ours.documentation), asMap(theirs.documentation), "documentation", conflicts));
192843
+ setIf(merged, "cut", mergeMap(asMap(base.cut), asMap(ours.cut), asMap(theirs.cut), "cut", conflicts, void 0));
192844
+ setIf(merged, "suggestions", mergeById(asArr(base.suggestions), asArr(ours.suggestions), asArr(theirs.suggestions), "suggestions", conflicts));
192845
+ setIf(merged, "rerecord", mergeMap(asMap(base.rerecord), asMap(ours.rerecord), asMap(theirs.rerecord), "rerecord", conflicts, void 0));
192846
+ for (const k of /* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(ours), ...Object.keys(theirs)])) {
192847
+ if (k in merged || AUTHORING_HANDLED.has(k)) continue;
192848
+ const v = merge3(base[k], ours[k], theirs[k], "", k, conflicts);
192849
+ if (v !== void 0) merged[k] = v;
192850
+ }
192851
+ return { type: "authoring", merged, conflicts, warnings: [] };
192852
+ }
192853
+ var AUTHORING_HANDLED = /* @__PURE__ */ new Set(["schema", "comments", "edits", "writing", "recording", "audio", "documentation", "cut", "suggestions", "rerecord"]);
192854
+ function mergeById(base, ours, theirs, path, conflicts) {
192855
+ const index2 = (arr) => {
192856
+ const m = {};
192857
+ for (const r of arr) if (isObj(r) && typeof r.id === "string") m[r.id] = r;
192858
+ return m;
192859
+ };
192860
+ const merged = mergeMap(index2(base), index2(ours), index2(theirs), path, conflicts);
192861
+ return Object.values(merged).sort((a, b) => String(asMap(a).ts ?? "").localeCompare(String(asMap(b).ts ?? "")));
192862
+ }
192863
+ function mergeComments(base, ours, theirs) {
192864
+ const byId = /* @__PURE__ */ new Map();
192865
+ for (const c2 of [...base, ...ours, ...theirs]) {
192866
+ if (isObj(c2) && typeof c2.id === "string" && !byId.has(c2.id)) byId.set(c2.id, c2);
192867
+ }
192868
+ return [...byId.values()].sort((a, b) => String(a.ts ?? "").localeCompare(String(b.ts ?? "")));
192869
+ }
192870
+ function lastWriterWins(oursEdits, theirsEdits) {
192871
+ return (id, _b, o, t) => {
192872
+ const om = asMap(oursEdits[id]).modifiedAt, tm = asMap(theirsEdits[id]).modifiedAt;
192873
+ if (typeof om === "string" && typeof tm === "string") return { resolved: true, value: om >= tm ? o : t };
192874
+ return { resolved: false, kind: "no-timestamp" };
192875
+ };
192876
+ }
192877
+ function mergeEdits(base, ours, theirs) {
192878
+ const out = {};
192879
+ for (const id of /* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(ours), ...Object.keys(theirs)])) {
192880
+ const o = ours[id], t = theirs[id];
192881
+ if (eq(o, t)) {
192882
+ if (o !== void 0) out[id] = o;
192883
+ continue;
192884
+ }
192885
+ if (o === void 0) {
192886
+ if (t !== void 0) out[id] = t;
192887
+ continue;
192888
+ }
192889
+ if (t === void 0) {
192890
+ out[id] = o;
192891
+ continue;
192892
+ }
192893
+ out[id] = mergeEditRecord(asMap(o), asMap(t));
192894
+ }
192895
+ return out;
192896
+ }
192897
+ function mergeEditRecord(o, t) {
192898
+ const om = typeof o.modifiedAt === "string" ? o.modifiedAt : "";
192899
+ const tm = typeof t.modifiedAt === "string" ? t.modifiedAt : "";
192900
+ const newer = om >= tm ? o : t;
192901
+ const ol = asMap(o.localisedAt), tl = asMap(t.localisedAt);
192902
+ const loc = {};
192903
+ for (const k of /* @__PURE__ */ new Set([...Object.keys(ol), ...Object.keys(tl)])) {
192904
+ const a = typeof ol[k] === "string" ? ol[k] : "";
192905
+ const b = typeof tl[k] === "string" ? tl[k] : "";
192906
+ loc[k] = a >= b ? ol[k] ?? tl[k] : tl[k] ?? ol[k];
192907
+ }
192908
+ const merged = { ...newer };
192909
+ if (Object.keys(loc).length > 0) merged.localisedAt = loc;
192910
+ return merged;
192911
+ }
192912
+ function mergeKeyedByName(base, ours, theirs, path, conflicts) {
192913
+ const byName = (arr) => {
192914
+ const m = {};
192915
+ for (const p of arr) if (isObj(p) && typeof p.name === "string") m[p.name] = p;
192916
+ return m;
192917
+ };
192918
+ const merged = mergeMap(byName(base), byName(ours), byName(theirs), path, conflicts);
192919
+ return Object.keys(merged).sort().map((name) => merged[name]);
192920
+ }
192921
+ function mergeProject(base, ours, theirs) {
192922
+ const conflicts = [];
192923
+ const merged = {};
192924
+ for (const k of /* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(ours), ...Object.keys(theirs)])) {
192925
+ if (k === "properties" || k === "cast") {
192926
+ const v = mergeKeyedByName(asArr(base[k]), asArr(ours[k]), asArr(theirs[k]), k, conflicts);
192927
+ if (v.length > 0) merged[k] = v;
192928
+ } else if (k === "gameDataFields") {
192929
+ const v = mergeGameDataFields(asMap(base[k]), asMap(ours[k]), asMap(theirs[k]), conflicts);
192930
+ if (Object.keys(v).length > 0) merged[k] = v;
192931
+ } else if (k === "locales") {
192932
+ merged.locales = mergeLocales(asMap(base.locales), asMap(ours.locales), asMap(theirs.locales), conflicts);
192933
+ } else {
192934
+ const v = merge3(base[k], ours[k], theirs[k], "", k, conflicts);
192935
+ if (v !== void 0) merged[k] = v;
192936
+ }
192937
+ }
192938
+ return { type: "project", merged, conflicts, warnings: [] };
192939
+ }
192940
+ function mergeGameDataFields(b, o, t, conflicts) {
192941
+ const out = {};
192942
+ for (const kind of /* @__PURE__ */ new Set([...Object.keys(b), ...Object.keys(o), ...Object.keys(t)])) {
192943
+ const v = mergeKeyedByName(asArr(b[kind]), asArr(o[kind]), asArr(t[kind]), `gameDataFields.${kind}`, conflicts);
192944
+ if (v.length > 0) out[kind] = v;
192945
+ }
192946
+ return out;
192947
+ }
192948
+ function mergeLocales(b, o, t, conflicts) {
192949
+ const out = {};
192950
+ const def = merge3(b.default, o.default, t.default, "", "locales.default", conflicts);
192951
+ if (def !== void 0) out.default = def;
192952
+ const all = [];
192953
+ for (const arr of [asArr(b.all), asArr(o.all), asArr(t.all)]) {
192954
+ for (const x of arr) if (typeof x === "string" && !all.includes(x)) all.push(x);
192955
+ }
192956
+ out.all = all;
192957
+ return out;
192958
+ }
192959
+ var CHILD_KEYS = ["blocks", "children", "beats"];
192960
+ function toTree(raw) {
192961
+ const childKey = CHILD_KEYS.find((k) => Array.isArray(raw[k]));
192962
+ const fields = {};
192963
+ for (const [k, v] of Object.entries(raw)) if (k !== childKey) fields[k] = v;
192964
+ const children = childKey ? raw[childKey].filter(isObj).map(toTree) : [];
192965
+ return { id: typeof raw.id === "string" ? raw.id : "", fields, childKey, children };
192966
+ }
192967
+ function fromTree(n) {
192968
+ const out = { ...n.fields };
192969
+ if (n.childKey) out[n.childKey] = n.children.map(fromTree);
192970
+ return out;
192971
+ }
192972
+ function mergeFlow(base, ours, theirs) {
192973
+ const conflicts = [];
192974
+ const warnings = [];
192975
+ const merged = {};
192976
+ merged.schema = merge3(base.schema, ours.schema, theirs.schema, "", "schema", conflicts) ?? ours.schema;
192977
+ const bScene = isObj(base.scene) ? toTree(base.scene) : void 0;
192978
+ const oScene = toTree(asMap(ours.scene));
192979
+ const tScene = isObj(theirs.scene) ? toTree(theirs.scene) : void 0;
192980
+ const mergedScene = mergeNode(bScene, oScene, tScene, "scene", conflicts, warnings);
192981
+ merged.scene = fromTree(mergedScene);
192982
+ checkDuplicateIds(mergedScene, conflicts);
192983
+ return { type: "flow", merged, conflicts, warnings };
192984
+ }
192985
+ function mergeNode(b, o, t, path, conflicts, warnings) {
192986
+ const fields = mergeFields(b?.fields ?? {}, o.fields, t?.fields ?? {}, o.id, path, conflicts);
192987
+ const childKey = o.childKey ?? t?.childKey ?? b?.childKey;
192988
+ const children = childKey ? mergeChildren(b?.children ?? [], o.children, t?.children ?? [], `${path}.${childKey}`, conflicts, warnings) : [];
192989
+ return { id: o.id, fields, childKey, children };
192990
+ }
192991
+ function mergeFields(b, o, t, id, path, conflicts) {
192992
+ const out = {};
192993
+ for (const k of /* @__PURE__ */ new Set([...Object.keys(b), ...Object.keys(o), ...Object.keys(t)])) {
192994
+ const v = merge3(b[k], o[k], t[k], id, `${path}.${k}`, conflicts);
192995
+ if (v !== void 0) out[k] = v;
192996
+ }
192997
+ return out;
192998
+ }
192999
+ function mergeChildren(B, O, T, path, conflicts, warnings) {
193000
+ const Bm = idMap(B), Om = idMap(O), Tm = idMap(T);
193001
+ const Bset = new Set(Bm.keys()), Oset = new Set(Om.keys()), Tset = new Set(Tm.keys());
193002
+ const merged = /* @__PURE__ */ new Map();
193003
+ const here = (id) => `${path}[${id}]`;
193004
+ for (const id of /* @__PURE__ */ new Set([...Bm.keys(), ...Om.keys(), ...Tm.keys()])) {
193005
+ const b = Bm.get(id), o = Om.get(id), t = Tm.get(id);
193006
+ const inB = Bset.has(id), inO = Oset.has(id), inT = Tset.has(id);
193007
+ if (inO && inT) {
193008
+ if (!inB && !eq(fromTree(o), fromTree(t))) {
193009
+ conflicts.push({ id, path: here(id), base: void 0, ours: fromTree(o), theirs: fromTree(t), kind: "added-both" });
193010
+ merged.set(id, o);
193011
+ } else {
193012
+ merged.set(id, mergeNode(b, o, t, here(id), conflicts, warnings));
193013
+ }
193014
+ } else if (inO && !inT) {
193015
+ if (inB && !eq(fromTree(o), fromTree(b))) {
193016
+ conflicts.push({ id, path: here(id), base: fromTree(b), ours: fromTree(o), theirs: void 0, kind: "delete-vs-edit" });
193017
+ merged.set(id, o);
193018
+ } else if (!inB) {
193019
+ merged.set(id, o);
193020
+ }
193021
+ } else if (!inO && inT) {
193022
+ if (inB && !eq(fromTree(t), fromTree(b))) {
193023
+ conflicts.push({ id, path: here(id), base: fromTree(b), ours: void 0, theirs: fromTree(t), kind: "delete-vs-edit" });
193024
+ } else if (!inB) {
193025
+ merged.set(id, t);
193026
+ }
193027
+ }
193028
+ }
193029
+ return orderChildren(merged, B, O, T, path, conflicts, warnings);
193030
+ }
193031
+ function orderChildren(merged, B, O, T, path, conflicts, warnings) {
193032
+ const Bids = B.map((n) => n.id), Oids = O.map((n) => n.id), Tids = T.map((n) => n.id);
193033
+ const Oidset = new Set(Oids);
193034
+ const common = new Set(Bids.filter((id) => merged.has(id)));
193035
+ const restrict = (ids) => ids.filter((id) => common.has(id));
193036
+ const relB = restrict(Bids), relO = restrict(Oids), relT = restrict(Tids);
193037
+ const fullO = relO.length === common.size, fullT = relT.length === common.size;
193038
+ let commonOrder;
193039
+ if (!fullO && !fullT) commonOrder = relB;
193040
+ else if (!fullO) commonOrder = relT;
193041
+ else if (!fullT) commonOrder = relO;
193042
+ else if (arrEq(relO, relB)) commonOrder = relT;
193043
+ else if (arrEq(relT, relB)) commonOrder = relO;
193044
+ else if (arrEq(relO, relT)) commonOrder = relO;
193045
+ else {
193046
+ conflicts.push({ id: "", path, base: relB, ours: relO, theirs: relT, kind: "moved" });
193047
+ commonOrder = relO;
193048
+ }
193049
+ const anchorIn = (ids, id) => {
193050
+ for (let i = ids.indexOf(id) - 1; i >= 0; i--) if (common.has(ids[i])) return ids[i];
193051
+ return null;
193052
+ };
193053
+ const byAnchor = /* @__PURE__ */ new Map();
193054
+ const group = (a) => {
193055
+ let g = byAnchor.get(a);
193056
+ if (!g) {
193057
+ g = { ours: [], theirs: [] };
193058
+ byAnchor.set(a, g);
193059
+ }
193060
+ return g;
193061
+ };
193062
+ for (const n of O) if (merged.has(n.id) && !common.has(n.id)) group(anchorIn(Oids, n.id)).ours.push(n.id);
193063
+ for (const n of T) if (merged.has(n.id) && !common.has(n.id) && !Oidset.has(n.id)) group(anchorIn(Tids, n.id)).theirs.push(n.id);
193064
+ const out = [];
193065
+ const emit = (anchor) => {
193066
+ const g = byAnchor.get(anchor);
193067
+ if (!g) return;
193068
+ if (g.ours.length > 0 && g.theirs.length > 0) warnings.push({ id: anchor ?? "", path, message: "both sides inserted here; kept ours-then-theirs - review the order" });
193069
+ out.push(...g.ours, ...g.theirs);
193070
+ };
193071
+ emit(null);
193072
+ for (const id of commonOrder) {
193073
+ out.push(id);
193074
+ emit(id);
193075
+ }
193076
+ return out.map((id) => merged.get(id));
193077
+ }
193078
+ function checkDuplicateIds(root2, conflicts) {
193079
+ const seen = /* @__PURE__ */ new Set(), dup = /* @__PURE__ */ new Set();
193080
+ const walk2 = (n) => {
193081
+ if (n.id) {
193082
+ if (seen.has(n.id)) dup.add(n.id);
193083
+ else seen.add(n.id);
193084
+ }
193085
+ n.children.forEach(walk2);
193086
+ };
193087
+ walk2(root2);
193088
+ for (const id of dup) conflicts.push({ id, path: "scene", base: void 0, ours: void 0, theirs: void 0, kind: "structural" });
193089
+ }
193090
+ var idMap = (nodes) => new Map(nodes.filter((n) => n.id).map((n) => [n.id, n]));
193091
+ var arrEq = (a, b) => a.length === b.length && a.every((x, i) => x === b[i]);
193092
+
192751
193093
  // ../../../expr/packages/expr/src/parser.ts
192752
193094
  var ParseError = class extends Error {
192753
193095
  constructor(message, pos2, source2) {
@@ -194286,6 +194628,201 @@ function exportBundle(input) {
194286
194628
  };
194287
194629
  }
194288
194630
 
194631
+ // ../ops/src/reachability.ts
194632
+ var normaliseRef = (ref) => ref.includes(".") ? ref : `@patter.${ref.slice(1)}`;
194633
+ var SEP = "\0";
194634
+ var keyOf = (ref, sceneId) => ref.startsWith("@scene.") ? `${sceneId}${SEP}${ref}` : ref;
194635
+ var shown = (key2) => {
194636
+ const bare = key2.includes(SEP) ? key2.slice(key2.indexOf(SEP) + 1) : key2;
194637
+ const at = bare.indexOf(":");
194638
+ return at < 0 ? bare : `${bare.slice(0, at)} +${bare.slice(at + 1)}`;
194639
+ };
194640
+ var svRef = (ast) => Array.isArray(ast) && ast[0] === "sv" ? `@${String(ast[1])}.${String(ast[2])}` : void 0;
194641
+ function latchOf(ast, sceneId) {
194642
+ const direct = svRef(ast);
194643
+ if (direct !== void 0) return keyOf(direct, sceneId);
194644
+ if (!Array.isArray(ast)) return void 0;
194645
+ if (ast[0] === "bin" && ast[1] === "==") {
194646
+ const [, , l2, r] = ast;
194647
+ for (const [a, b] of [[l2, r], [r, l2]]) {
194648
+ const ref = svRef(a);
194649
+ if (ref !== void 0 && Array.isArray(b) && b[0] === "b" && b[1] === true) return keyOf(ref, sceneId);
194650
+ }
194651
+ return void 0;
194652
+ }
194653
+ if (ast[0] === "call" && ast[1] === "check_flags") {
194654
+ const ref = svRef(ast[2]);
194655
+ const args = ast.slice(3);
194656
+ if (ref === void 0 || args.length !== 1) return void 0;
194657
+ const arg = args[0];
194658
+ if (Array.isArray(arg) && arg[0] === "fd" && arg[1] === "+") return `${keyOf(ref, sceneId)}:${String(arg[2])}`;
194659
+ }
194660
+ return void 0;
194661
+ }
194662
+ function disjuncts(ast) {
194663
+ if (Array.isArray(ast) && ast[0] === "bin" && ast[1] === "or") {
194664
+ const [, , l2, r] = ast;
194665
+ return [...disjuncts(l2), ...disjuncts(r)];
194666
+ }
194667
+ return [ast];
194668
+ }
194669
+ function terms(ast, sceneId, negated = false, into = []) {
194670
+ const latch = latchOf(ast, sceneId);
194671
+ if (latch !== void 0) {
194672
+ into.push({ key: latch, negated });
194673
+ return into;
194674
+ }
194675
+ if (!Array.isArray(ast)) return into;
194676
+ if (ast[0] === "u" && ast[1] === "not") return terms(ast[2], sceneId, !negated, into);
194677
+ if (ast[0] === "bin" && ast[1] === "and" && !negated) {
194678
+ terms(ast[2], sceneId, false, into);
194679
+ terms(ast[3], sceneId, false, into);
194680
+ }
194681
+ return into;
194682
+ }
194683
+ var requirementsOf = (expr, sceneId) => expr === void 0 ? [] : terms(expr.ast, sceneId);
194684
+ var asFlags = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
194685
+ function reachabilityIssues(loaded, compiled) {
194686
+ const bundle = compiled ?? exportBundle({ project: loaded.project, scenes: loaded.scenes, locales: loaded.locales });
194687
+ const hostTokens = new Set((loaded.project.scopeRegistry?.scopes ?? []).map((s) => s.token));
194688
+ const preset = /* @__PURE__ */ new Set();
194689
+ const notALatch = (decl, key2) => {
194690
+ if (decl.temporary) {
194691
+ preset.add(key2);
194692
+ for (const v of decl.values ?? []) preset.add(`${key2}:${v}`);
194693
+ return;
194694
+ }
194695
+ if (decl.type === "boolean" && decl.default === true) preset.add(key2);
194696
+ if (decl.type === "flags") for (const v of asFlags(decl.default)) preset.add(`${key2}:${v}`);
194697
+ };
194698
+ for (const decl of loaded.project.properties ?? []) notALatch(decl, `@patter.${decl.name}`);
194699
+ for (const scene of loaded.scenes) {
194700
+ for (const decl of scene.sceneProps ?? []) notALatch(decl, `${scene.id}${SEP}@scene.${decl.name}`);
194701
+ }
194702
+ const latched = /* @__PURE__ */ new Set();
194703
+ const broken = /* @__PURE__ */ new Set();
194704
+ const writers = /* @__PURE__ */ new Map();
194705
+ const noteWrite = (key2, requires) => {
194706
+ latched.add(key2);
194707
+ (writers.get(key2) ?? writers.set(key2, []).get(key2)).push({ requires });
194708
+ };
194709
+ const breakKey = (key2) => {
194710
+ broken.add(key2);
194711
+ for (const k of latched) if (k.startsWith(`${key2}:`)) broken.add(k);
194712
+ };
194713
+ const scanEffects = (effects, sceneId, need) => {
194714
+ for (const e of effects ?? []) {
194715
+ const ref = normaliseRef(e.target);
194716
+ const key2 = keyOf(ref, sceneId);
194717
+ const ast = e.value.ast;
194718
+ if (Array.isArray(ast) && ast[0] === "b" && ast[1] === true) {
194719
+ noteWrite(key2, need);
194720
+ continue;
194721
+ }
194722
+ if (Array.isArray(ast) && ast[0] === "call" && ast[1] === "set_flags" && svRef(ast[2]) === ref) {
194723
+ let clean3 = true;
194724
+ for (const arg of ast.slice(3)) {
194725
+ if (Array.isArray(arg) && arg[0] === "fd" && arg[1] === "+") noteWrite(`${key2}:${String(arg[2])}`, need);
194726
+ else clean3 = false;
194727
+ }
194728
+ if (clean3) continue;
194729
+ }
194730
+ breakKey(key2);
194731
+ }
194732
+ };
194733
+ const walk2 = (nodes, sceneId, gate) => {
194734
+ for (const node of nodes) {
194735
+ const here = [...gate, ...requirementsOf(node.condition, sceneId)];
194736
+ if (node.type === "group") walk2(node.children, sceneId, here);
194737
+ else {
194738
+ scanEffects(node.onEnter, sceneId, here);
194739
+ scanEffects(node.onExit, sceneId, here);
194740
+ }
194741
+ }
194742
+ };
194743
+ for (const scene of Object.values(bundle.scenes)) {
194744
+ scanEffects(scene.onEntry, scene.id, []);
194745
+ for (const block of scene.blocks) walk2(block.children, scene.id, []);
194746
+ }
194747
+ for (const key2 of [...latched]) {
194748
+ const at = key2.indexOf(":");
194749
+ if (at > 0 && broken.has(key2.slice(0, at))) broken.add(key2);
194750
+ }
194751
+ const hostDriven = (key2) => hostTokens.has(key2.slice(1, key2.indexOf(".")));
194752
+ const monotonic = (key2) => latched.has(key2) && !broken.has(key2) && !preset.has(key2) && !hostDriven(key2);
194753
+ const cache2 = /* @__PURE__ */ new Map();
194754
+ const inFlight = /* @__PURE__ */ new Set();
194755
+ const mustHold = (key2) => {
194756
+ const done = cache2.get(key2);
194757
+ if (done !== void 0) return { need: done, cut: false };
194758
+ if (inFlight.has(key2)) return { need: /* @__PURE__ */ new Set(), cut: true };
194759
+ const routes = writers.get(key2);
194760
+ if (routes === void 0 || routes.length === 0) return { need: /* @__PURE__ */ new Set(), cut: false };
194761
+ inFlight.add(key2);
194762
+ let shared;
194763
+ let cut = false;
194764
+ for (const route of routes) {
194765
+ const need = /* @__PURE__ */ new Set();
194766
+ for (const t of route.requires) {
194767
+ if (t.negated || !monotonic(t.key)) continue;
194768
+ need.add(t.key);
194769
+ const deeper = mustHold(t.key);
194770
+ cut ||= deeper.cut;
194771
+ for (const k of deeper.need) need.add(k);
194772
+ }
194773
+ shared = shared === void 0 ? need : new Set([...shared].filter((k) => need.has(k)));
194774
+ }
194775
+ inFlight.delete(key2);
194776
+ const result = shared ?? /* @__PURE__ */ new Set();
194777
+ if (!cut) cache2.set(key2, result);
194778
+ return { need: result, cut };
194779
+ };
194780
+ const refute = (gate) => {
194781
+ for (const no of gate.filter((t) => t.negated)) {
194782
+ if (!monotonic(no.key)) continue;
194783
+ if (gate.some((t) => !t.negated && t.key === no.key)) {
194784
+ return `it asks for ${shown(no.key)} to be both set and not set`;
194785
+ }
194786
+ for (const yes of gate.filter((t) => !t.negated && t.key !== no.key)) {
194787
+ if (!monotonic(yes.key)) continue;
194788
+ const chain = mustHold(yes.key);
194789
+ if (!chain.cut && chain.need.has(no.key)) {
194790
+ return `${shown(yes.key)} can only become true after ${shown(no.key)}, which nothing sets back, so this condition can never hold`;
194791
+ }
194792
+ }
194793
+ }
194794
+ return void 0;
194795
+ };
194796
+ const issues = [];
194797
+ const report = (nodes, sceneId, gate) => {
194798
+ for (const node of nodes) {
194799
+ const own = node.condition;
194800
+ const branches = own ? disjuncts(own.ast) : [void 0];
194801
+ let reason;
194802
+ const refuted = branches.every((b) => {
194803
+ const r = refute([...gate, ...b ? terms(b, sceneId) : []]);
194804
+ reason ??= r;
194805
+ return r !== void 0;
194806
+ });
194807
+ if (refuted && reason !== void 0 && own) {
194808
+ issues.push({
194809
+ nodeId: node.id,
194810
+ field: "condition",
194811
+ src: own.src,
194812
+ severity: "warning",
194813
+ message: `this can never run: ${reason}`
194814
+ });
194815
+ continue;
194816
+ }
194817
+ if (node.type === "group") report(node.children, sceneId, [...gate, ...requirementsOf(own, sceneId)]);
194818
+ }
194819
+ };
194820
+ for (const scene of Object.values(bundle.scenes)) {
194821
+ for (const block of scene.blocks) report(block.children, scene.id, []);
194822
+ }
194823
+ return issues;
194824
+ }
194825
+
194289
194826
  // ../ops/src/validate.ts
194290
194827
  function runValidate(loaded) {
194291
194828
  const { project, scenes, locales } = loaded;
@@ -194295,19 +194832,42 @@ function runValidate(loaded) {
194295
194832
  const interpolation = validateInterpolation({ project, scenes, locales }, { foreignScopes });
194296
194833
  const hygiene = checkHygiene([loaded.projectFile, ...Object.values(loaded.sceneFiles), ...loaded.localeFiles, ...loaded.authoringFiles]);
194297
194834
  const staleBundles = checkBundles(loaded);
194298
- const unresolvedMerges = walkFiles(loaded.root, ".patterconflict").map((file) => ({
194299
- file,
194300
- message: "unresolved merge conflict - resolve it and delete the .patterconflict sidecar before committing"
194301
- }));
194835
+ const unresolvedMerges = sidecarIssues(walkFiles(loaded.root, CONFLICT_SIDECAR));
194836
+ const orphans = orphanShards(loaded);
194837
+ const reachability = structural.length === 0 && conditions.length === 0 ? reachabilityIssues(loaded) : [];
194302
194838
  return {
194303
194839
  structural,
194304
194840
  conditions,
194305
194841
  interpolation,
194842
+ reachability,
194306
194843
  hygiene,
194307
194844
  staleBundles,
194308
194845
  unresolvedMerges,
194309
- ok: structural.length === 0 && conditions.length === 0 && interpolation.length === 0 && hygiene.length === 0 && staleBundles.length === 0 && unresolvedMerges.length === 0
194846
+ orphans,
194847
+ ok: structural.length === 0 && conditions.length === 0 && interpolation.length === 0 && hygiene.length === 0 && staleBundles.length === 0 && unresolvedMerges.length === 0 && orphans.length === 0
194848
+ };
194849
+ }
194850
+ function orphanShards(loaded) {
194851
+ const collected = /* @__PURE__ */ new Set([
194852
+ loaded.projectFile,
194853
+ ...Object.values(loaded.sceneFiles),
194854
+ ...loaded.localeFiles,
194855
+ ...loaded.authoringFiles
194856
+ ]);
194857
+ const kind = {
194858
+ ".patterflow": "scene",
194859
+ ".patterloc": "locale",
194860
+ ".patterx": "authoring",
194861
+ ".patterproj": "project"
194310
194862
  };
194863
+ const out = [];
194864
+ for (const [ext, what] of Object.entries(kind)) {
194865
+ for (const file of walkFiles(loaded.root, ext)) {
194866
+ if (collected.has(file)) continue;
194867
+ out.push({ file, message: `a ${what} shard outside the project's layout - nothing loads it, so its content is not in the project (move it under the configured folder, or delete it)` });
194868
+ }
194869
+ }
194870
+ return out;
194311
194871
  }
194312
194872
  function checkBundles(loaded) {
194313
194873
  const issues = [];
@@ -194388,7 +194948,16 @@ function compileScenes(loaded) {
194388
194948
  blocks: s.blocks.map((b) => ({ ...b, children: b.children.map(clean3) }))
194389
194949
  }));
194390
194950
  }
194951
+ function refuseUnresolvedMerge(loaded) {
194952
+ const issues = sidecarIssues(walkFiles(loaded.root, CONFLICT_SIDECAR));
194953
+ if (!issues.length) return;
194954
+ throw new Error(
194955
+ `${issues.length} unresolved merge conflict(s) - resolve them and delete the .patterconflict sidecar(s) first:
194956
+ ` + issues.map((i) => ` ${i.file}`).join("\n")
194957
+ );
194958
+ }
194391
194959
  function runExport(loaded) {
194960
+ refuseUnresolvedMerge(loaded);
194392
194961
  const { project, locales } = loaded;
194393
194962
  const full = exportBundle({ project, scenes: compileScenes(loaded), locales });
194394
194963
  const loc = project.export?.localisation;
@@ -194397,6 +194966,7 @@ function runExport(loaded) {
194397
194966
  return { ...full, strings, localisation: { mode: "ids", ...loc.sourceDebug ? { sourceDebug: true } : {} } };
194398
194967
  }
194399
194968
  function runExportFull(loaded) {
194969
+ refuseUnresolvedMerge(loaded);
194400
194970
  const { project, locales } = loaded;
194401
194971
  return exportBundle({ project, scenes: compileScenes(loaded), locales });
194402
194972
  }
@@ -196456,7 +197026,7 @@ function stringsByLocale(loaded) {
196456
197026
  }
196457
197027
  return byLocale;
196458
197028
  }
196459
- function mergeAuthoring(loaded) {
197029
+ function mergeAuthoring2(loaded) {
196460
197030
  const writing = /* @__PURE__ */ new Map();
196461
197031
  const recording = /* @__PURE__ */ new Map();
196462
197032
  const cut = /* @__PURE__ */ new Set();
@@ -196586,40 +197156,113 @@ function targetHostRef(target, hostTokens) {
196586
197156
  const m = /^@([A-Za-z_][\w]*)\.(.+)$/.exec(target);
196587
197157
  return m && hostTokens.has(m[1]) ? `@${m[1]}.${m[2]}` : void 0;
196588
197158
  }
197159
+ function flagKeys(node, hostTokens, fn) {
197160
+ if (node.kind !== "call" || node.name !== fn) return [];
197161
+ const subject = node.args[0];
197162
+ if (!subject || subject.kind !== "scopedvar" || !hostTokens.has(subject.scope)) return [];
197163
+ const ref = `@${subject.scope}.${subject.name}`;
197164
+ return node.args.slice(1).filter((a) => a.kind === "flagdelta").map((a) => `${ref}:${a.name}`);
197165
+ }
197166
+ function fineRefsIn(node, hostTokens, out) {
197167
+ for (const k of flagKeys(node, hostTokens, "check_flags")) out.add(k);
197168
+ switch (node.kind) {
197169
+ case "unary":
197170
+ fineRefsIn(node.operand, hostTokens, out);
197171
+ break;
197172
+ case "binary":
197173
+ fineRefsIn(node.left, hostTokens, out);
197174
+ fineRefsIn(node.right, hostTokens, out);
197175
+ break;
197176
+ case "call":
197177
+ for (const a of node.args) fineRefsIn(a, hostTokens, out);
197178
+ break;
197179
+ default:
197180
+ break;
197181
+ }
197182
+ }
196589
197183
  function analyzeHostScopes(bundle, hostTokens) {
196590
197184
  const written = /* @__PURE__ */ new Set();
196591
197185
  const gatesByBeat = /* @__PURE__ */ new Map();
197186
+ const fineGatesByBeat = /* @__PURE__ */ new Map();
196592
197187
  const proposals = /* @__PURE__ */ new Map();
196593
- if (hostTokens.size === 0) return { written, gatesByBeat, proposals };
197188
+ const writerSites = /* @__PURE__ */ new Map();
197189
+ const opaqueWrites = /* @__PURE__ */ new Set();
197190
+ const empty = { written, gatesByBeat, proposals, fineGatesByBeat, writerSites, opaqueWrites };
197191
+ if (hostTokens.size === 0) return empty;
196594
197192
  const refsIn = (expr) => {
196595
197193
  const refs = /* @__PURE__ */ new Set();
196596
197194
  if (expr) scanExpr(deserialiseAst(expr.ast), hostTokens, refs, proposals);
196597
197195
  return refs;
196598
197196
  };
196599
- const scanEffects = (effects) => {
197197
+ const fineIn = (expr) => {
197198
+ const refs = /* @__PURE__ */ new Set();
197199
+ if (expr) fineRefsIn(deserialiseAst(expr.ast), hostTokens, refs);
197200
+ return refs;
197201
+ };
197202
+ const addSite = (key2, witnesses) => {
197203
+ (writerSites.get(key2) ?? writerSites.set(key2, []).get(key2)).push(witnesses);
197204
+ };
197205
+ const scanEffects = (effects, witnesses) => {
196600
197206
  for (const e of effects ?? []) {
196601
- const t = targetHostRef(e.target, hostTokens);
196602
- if (t) written.add(t);
197207
+ const target = targetHostRef(e.target, hostTokens);
197208
+ if (target) {
197209
+ written.add(target);
197210
+ addSite(target, witnesses);
197211
+ const flags = e.value ? flagKeys(deserialiseAst(e.value.ast), hostTokens, "set_flags") : [];
197212
+ if (flags.length) for (const k of flags) addSite(k, witnesses);
197213
+ else opaqueWrites.add(target);
197214
+ }
196603
197215
  refsIn(e.value);
196604
197216
  }
196605
197217
  };
196606
- const walk2 = (nodes, gate) => {
197218
+ const walk2 = (nodes, gate, fine) => {
196607
197219
  for (const node of nodes) {
196608
197220
  const here = /* @__PURE__ */ new Set([...gate, ...refsIn(node.condition)]);
197221
+ const hereFine = /* @__PURE__ */ new Set([...fine, ...fineIn(node.condition)]);
196609
197222
  if (node.type === "group") {
196610
- walk2(node.children, here);
197223
+ walk2(node.children, here, hereFine);
196611
197224
  } else {
196612
- scanEffects(node.onEnter);
196613
- scanEffects(node.onExit);
196614
- for (const beat of node.beats ?? []) gatesByBeat.set(beat.id, here);
197225
+ const witnesses = (node.beats ?? []).map((b) => b.id);
197226
+ scanEffects(node.onEnter, witnesses);
197227
+ scanEffects(node.onExit, witnesses);
197228
+ for (const beat of node.beats ?? []) {
197229
+ gatesByBeat.set(beat.id, here);
197230
+ fineGatesByBeat.set(beat.id, hereFine);
197231
+ }
196615
197232
  }
196616
197233
  }
196617
197234
  };
196618
197235
  for (const scene of Object.values(bundle.scenes)) {
196619
- scanEffects(scene.onEntry);
196620
- for (const block of scene.blocks) walk2(block.children, /* @__PURE__ */ new Set());
197236
+ const sceneBeats = [];
197237
+ const collect = (nodes) => {
197238
+ for (const n of nodes) {
197239
+ if (n.type === "group") collect(n.children);
197240
+ else for (const b of n.beats ?? []) sceneBeats.push(b.id);
197241
+ }
197242
+ };
197243
+ for (const block of scene.blocks) collect(block.children);
197244
+ scanEffects(scene.onEntry, sceneBeats);
197245
+ for (const block of scene.blocks) walk2(block.children, /* @__PURE__ */ new Set(), /* @__PURE__ */ new Set());
196621
197246
  }
196622
- return { written, gatesByBeat, proposals };
197247
+ return empty;
197248
+ }
197249
+ function blockedGates(beatId, analysis, drivenRefs, reachedRuns) {
197250
+ const out = [];
197251
+ const gates = /* @__PURE__ */ new Set([...analysis.gatesByBeat.get(beatId) ?? [], ...analysis.fineGatesByBeat.get(beatId) ?? []]);
197252
+ for (const ref of [...gates].sort()) {
197253
+ const coarse = ref.includes(":") ? ref.slice(0, ref.indexOf(":")) : ref;
197254
+ if (drivenRefs.has(coarse)) continue;
197255
+ if (!analysis.written.has(coarse)) continue;
197256
+ if (ref !== coarse && analysis.opaqueWrites.has(coarse)) continue;
197257
+ if (ref === coarse && [...gates].some((g) => g !== ref && g.startsWith(`${ref}:`))) continue;
197258
+ const sites = analysis.writerSites.get(ref) ?? [];
197259
+ if (!sites.length) continue;
197260
+ if (sites.some((s) => !s.length || s.some((w) => !reachedRuns.has(w)))) continue;
197261
+ if (!sites.every((s) => s.every((w) => reachedRuns.get(w) === 0))) continue;
197262
+ const writers = [...new Set(sites.flat())].sort();
197263
+ out.push({ ref, writers });
197264
+ }
197265
+ return out;
196623
197266
  }
196624
197267
  function proposeCoverageDrivers(loaded) {
196625
197268
  const hostTokens = new Set((loaded.project.scopeRegistry?.scopes ?? []).map((s) => s.token));
@@ -196752,12 +197395,15 @@ function* sweep(loaded, options = {}, hooks = {}) {
196752
197395
  const m = meta.get(id);
196753
197396
  const reached = reachedRuns.get(id);
196754
197397
  let needsInput;
197398
+ let blockedBy;
196755
197399
  if (reached === 0) {
196756
197400
  const gates = [...analysis.gatesByBeat.get(id) ?? []].filter((r) => !analysis.written.has(r) && !drivenRefs.has(r));
196757
197401
  if (gates.length) {
196758
197402
  needsInput = gates;
196759
197403
  for (const g of gates) unwrittenInputs.add(g);
196760
197404
  }
197405
+ const blocked = blockedGates(id, analysis, drivenRefs, reachedRuns);
197406
+ if (blocked.length) blockedBy = blocked;
196761
197407
  }
196762
197408
  return {
196763
197409
  id,
@@ -196768,7 +197414,8 @@ function* sweep(loaded, options = {}, hooks = {}) {
196768
197414
  hits: hitCount.get(id),
196769
197415
  reachedRuns: reached,
196770
197416
  reachPct: executed ? reached / executed * 100 : 0,
196771
- ...needsInput ? { needsInput } : {}
197417
+ ...needsInput ? { needsInput } : {},
197418
+ ...blockedBy ? { blockedBy } : {}
196772
197419
  };
196773
197420
  });
196774
197421
  const neverHit = beats.filter((b) => b.reachedRuns === 0).length;
@@ -196821,9 +197468,16 @@ function renderCoverageText(report, sceneName = (id) => id) {
196821
197468
  out.push("");
196822
197469
  out.push(`${sceneName(scene)}${dead ? ` (${dead} never reached)` : ""}`);
196823
197470
  for (const b of beats) {
196824
- const mark = b.reachedRuns === 0 ? b.needsInput ? "? " : "\u203C " : " ";
197471
+ const mark = b.reachedRuns === 0 ? b.needsInput || b.blockedBy ? "? " : "\u203C " : " ";
196825
197472
  const label = b.character ? `${b.character}: ${clip(b.preview)}` : clip(b.preview || `(${b.kind})`);
196826
197473
  out.push(` ${mark}${b.reachPct.toFixed(0).padStart(3)}% ${String(b.hits).padStart(6)} ${label}`);
197474
+ for (const bg of b.blockedBy ?? []) {
197475
+ const names = bg.writers.map((w) => {
197476
+ const target = report.beats.find((x) => x.id === w);
197477
+ return target ? clip(target.preview || target.id, 28) : w;
197478
+ });
197479
+ out.push(` gated on ${bg.ref}, written only by: ${names.join(", ")} (never played either)`);
197480
+ }
196827
197481
  }
196828
197482
  }
196829
197483
  return out;
@@ -196875,7 +197529,7 @@ function runReport(loaded, recordingOverride) {
196875
197529
  const thresholdIdx = estimatingOn ? writingIndex.get(est?.thresholdStatus ?? "") ?? 0 : -1;
196876
197530
  const defaultLines = est?.defaultLines ?? 0;
196877
197531
  const tagMap = new Map((est?.tagEstimates ?? []).map((t) => [t.tag, t.lines]));
196878
- const { writing: writingOf, recording: manualRecordingOf, cut: cutSet, rerecord: rerecordSet, edits: editsOf } = mergeAuthoring(loaded);
197532
+ const { writing: writingOf, recording: manualRecordingOf, cut: cutSet, rerecord: rerecordSet, edits: editsOf } = mergeAuthoring2(loaded);
196879
197533
  const recordingBase = recordingOverride ?? manualRecordingOf;
196880
197534
  const recordingOf = (id) => effectiveRecording(id, recordingBase, rerecordSet, recordingLadder[0]);
196881
197535
  const byLocale = stringsByLocale(loaded);
@@ -197232,7 +197886,7 @@ function classesForChannel(classes, channel) {
197232
197886
  function resolveDocumentation(loaded, channel) {
197233
197887
  const classes = loaded.project.documentationClasses ?? DEFAULT_DOCUMENTATION_CLASSES;
197234
197888
  const allowed = classesForChannel(classes, channel);
197235
- const docsOf = mergeAuthoring(loaded).documentation;
197889
+ const docsOf = mergeAuthoring2(loaded).documentation;
197236
197890
  const own = (id) => (docsOf.get(id) ?? []).filter((l2) => l2.type !== void 0 && allowed.has(l2.type));
197237
197891
  const out = /* @__PURE__ */ new Map();
197238
197892
  const visit = (id, inherited) => {
@@ -197268,7 +197922,7 @@ function extractLoc(loaded, opts = {}) {
197268
197922
  const targetLocale = isTemplate ? void 0 : opts.locale;
197269
197923
  const source2 = tableFor(loaded, defaultLocale);
197270
197924
  const target = targetLocale ? tableFor(loaded, targetLocale) : {};
197271
- const edits = mergeAuthoring(loaded).edits;
197925
+ const edits = mergeAuthoring2(loaded).edits;
197272
197926
  const docs = resolveDocumentation(loaded, "loc");
197273
197927
  const commentsOf = (id) => (docs.get(id) ?? []).map((d) => d.text);
197274
197928
  const staleFor = (id, translation) => {
@@ -197608,7 +198262,7 @@ function runVoiceScript(loaded, opts = {}) {
197608
198262
  const stub = writingLadder[0];
197609
198263
  const recordThreshold = ladderDecls.findIndex((s) => s.readyToRecord);
197610
198264
  const writingIndex = new Map(writingLadder.map((name, i) => [name, i]));
197611
- const { writing: writingOf, recording: manualRecordingOf, cut: cutSet, rerecord: rerecordSet, documentation: docsOf } = mergeAuthoring(loaded);
198265
+ const { writing: writingOf, recording: manualRecordingOf, cut: cutSet, rerecord: rerecordSet, documentation: docsOf } = mergeAuthoring2(loaded);
197612
198266
  const recordingBase = opts.recordingOverride ?? manualRecordingOf;
197613
198267
  const recordingOf = (id) => effectiveRecording(id, recordingBase, rerecordSet, recordingLadder[0]);
197614
198268
  const source2 = sourceStrings(loaded);
@@ -197804,7 +198458,7 @@ function sequenceLabel(node) {
197804
198458
  function runScriptDoc(loaded) {
197805
198459
  const { project } = loaded;
197806
198460
  const source2 = sourceStrings(loaded);
197807
- const { cut } = mergeAuthoring(loaded);
198461
+ const { cut } = mergeAuthoring2(loaded);
197808
198462
  const sceneOf = /* @__PURE__ */ new Map();
197809
198463
  const blockTrail = /* @__PURE__ */ new Map();
197810
198464
  const blockName = /* @__PURE__ */ new Map();
@@ -238165,6 +238819,13 @@ async function runPack(startPath) {
238165
238819
  const projectFile = findProjectFile(startPath);
238166
238820
  const root2 = dirname4(projectFile);
238167
238821
  const project = parseSource(readFileSync4(projectFile, "utf8"));
238822
+ const unresolved = sidecarIssues(walkFiles(root2, CONFLICT_SIDECAR));
238823
+ if (unresolved.length) {
238824
+ throw new Error(
238825
+ `${unresolved.length} unresolved merge conflict(s) - resolve them before packing, or the recipient gets your side of a disagreement with no sign of it:
238826
+ ` + unresolved.map((i) => ` ${i.file}`).join("\n")
238827
+ );
238828
+ }
238168
238829
  const files = SHARD_EXTENSIONS.flatMap((ext) => walkFiles(root2, ext)).map((abs) => ({ abs, rel: relative(root2, abs).split(sep2).join("/") })).sort((a, b) => a.rel.localeCompare(b.rel));
238169
238830
  const manifest = {
238170
238831
  schema: "patter/document@0",
@@ -238181,325 +238842,6 @@ async function runPack(startPath) {
238181
238842
  var import_jszip2 = __toESM(require_lib3(), 1);
238182
238843
  import { join as join5, normalize, isAbsolute as isAbsolute2, resolve as resolve4, sep as sep3 } from "path";
238183
238844
  import { existsSync as existsSync3, readFileSync as readFileSync5 } from "fs";
238184
-
238185
- // ../ops/src/merge.ts
238186
- var UnsupportedMergeError = class extends Error {
238187
- };
238188
- var eq = (a, b) => canonicalStringify(a) === canonicalStringify(b);
238189
- var isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
238190
- function detectMergeType(file) {
238191
- const s = typeof file.schema === "string" ? file.schema : "";
238192
- if (s.startsWith("patter/flow")) return "flow";
238193
- if (s.startsWith("patter/strings")) return "loc";
238194
- if (s.startsWith("patter/authoring")) return "authoring";
238195
- if (s.startsWith("patter/project")) return "project";
238196
- throw new UnsupportedMergeError(`cannot detect a Patter merge type from schema '${s}'`);
238197
- }
238198
- function runMerge(base, ours, theirs, opts) {
238199
- const type = opts?.type ?? detectMergeType(ours);
238200
- switch (type) {
238201
- case "loc":
238202
- return mergeLoc(base, ours, theirs);
238203
- case "authoring":
238204
- return mergeAuthoring2(base, ours, theirs);
238205
- case "flow":
238206
- return mergeFlow(base, ours, theirs);
238207
- case "project":
238208
- return mergeProject(base, ours, theirs);
238209
- }
238210
- }
238211
- function deletedKind(base, ours, theirs) {
238212
- const deleted = base !== void 0 && (ours === void 0 || theirs === void 0);
238213
- return deleted ? "delete-vs-edit" : "both-changed";
238214
- }
238215
- function merge3(base, ours, theirs, id, path, conflicts) {
238216
- if (eq(ours, theirs)) return ours;
238217
- if (eq(base, ours)) return theirs;
238218
- if (eq(base, theirs)) return ours;
238219
- conflicts.push({ id, path, base, ours, theirs, kind: deletedKind(base, ours, theirs) });
238220
- return ours;
238221
- }
238222
- function mergeMap(base, ours, theirs, prefix, conflicts, resolve6) {
238223
- const out = {};
238224
- for (const k of /* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(ours), ...Object.keys(theirs)])) {
238225
- const b = base[k], o = ours[k], t = theirs[k];
238226
- let v;
238227
- if (eq(o, t)) v = o;
238228
- else if (eq(b, o)) v = t;
238229
- else if (eq(b, t)) v = o;
238230
- else {
238231
- const r = resolve6?.(k, b, o, t);
238232
- if (r && r.resolved) v = r.value;
238233
- else {
238234
- conflicts.push({ id: k, path: `${prefix}.${k}`, base: b, ours: o, theirs: t, kind: r ? r.kind : deletedKind(b, o, t) });
238235
- v = o;
238236
- }
238237
- }
238238
- if (v !== void 0) out[k] = v;
238239
- }
238240
- return out;
238241
- }
238242
- var asMap = (v) => isObj(v) ? v : {};
238243
- var asArr = (v) => Array.isArray(v) ? v : [];
238244
- function setIf(obj2, key2, val) {
238245
- if (Array.isArray(val) ? val.length > 0 : Object.keys(val).length > 0) obj2[key2] = val;
238246
- }
238247
- function mergeLoc(base, ours, theirs) {
238248
- const conflicts = [];
238249
- const merged = {};
238250
- for (const f of ["schema", "scene", "locale", "default"]) {
238251
- const v = merge3(base[f], ours[f], theirs[f], "", f, conflicts);
238252
- if (v !== void 0) merged[f] = v;
238253
- }
238254
- merged.strings = mergeMap(asMap(base.strings), asMap(ours.strings), asMap(theirs.strings), "strings", conflicts);
238255
- return { type: "loc", merged, conflicts, warnings: [] };
238256
- }
238257
- function mergeAuthoring2(base, ours, theirs) {
238258
- const conflicts = [];
238259
- const merged = {};
238260
- merged.schema = merge3(base.schema, ours.schema, theirs.schema, "", "schema", conflicts) ?? ours.schema;
238261
- setIf(merged, "comments", mergeComments(asArr(base.comments), asArr(ours.comments), asArr(theirs.comments)));
238262
- const edits = mergeEdits(asMap(base.edits), asMap(ours.edits), asMap(theirs.edits));
238263
- setIf(merged, "edits", edits);
238264
- const lww = lastWriterWins(asMap(ours.edits), asMap(theirs.edits));
238265
- for (const f of ["writing", "recording", "audio"]) {
238266
- setIf(merged, f, mergeMap(asMap(base[f]), asMap(ours[f]), asMap(theirs[f]), f, conflicts, lww));
238267
- }
238268
- setIf(merged, "documentation", mergeMap(asMap(base.documentation), asMap(ours.documentation), asMap(theirs.documentation), "documentation", conflicts));
238269
- setIf(merged, "cut", mergeMap(asMap(base.cut), asMap(ours.cut), asMap(theirs.cut), "cut", conflicts, void 0));
238270
- return { type: "authoring", merged, conflicts, warnings: [] };
238271
- }
238272
- function mergeComments(base, ours, theirs) {
238273
- const byId = /* @__PURE__ */ new Map();
238274
- for (const c2 of [...base, ...ours, ...theirs]) {
238275
- if (isObj(c2) && typeof c2.id === "string" && !byId.has(c2.id)) byId.set(c2.id, c2);
238276
- }
238277
- return [...byId.values()].sort((a, b) => String(a.ts ?? "").localeCompare(String(b.ts ?? "")));
238278
- }
238279
- function lastWriterWins(oursEdits, theirsEdits) {
238280
- return (id, _b, o, t) => {
238281
- const om = asMap(oursEdits[id]).modifiedAt, tm = asMap(theirsEdits[id]).modifiedAt;
238282
- if (typeof om === "string" && typeof tm === "string") return { resolved: true, value: om >= tm ? o : t };
238283
- return { resolved: false, kind: "no-timestamp" };
238284
- };
238285
- }
238286
- function mergeEdits(base, ours, theirs) {
238287
- const out = {};
238288
- for (const id of /* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(ours), ...Object.keys(theirs)])) {
238289
- const o = ours[id], t = theirs[id];
238290
- if (eq(o, t)) {
238291
- if (o !== void 0) out[id] = o;
238292
- continue;
238293
- }
238294
- if (o === void 0) {
238295
- if (t !== void 0) out[id] = t;
238296
- continue;
238297
- }
238298
- if (t === void 0) {
238299
- out[id] = o;
238300
- continue;
238301
- }
238302
- out[id] = mergeEditRecord(asMap(o), asMap(t));
238303
- }
238304
- return out;
238305
- }
238306
- function mergeEditRecord(o, t) {
238307
- const om = typeof o.modifiedAt === "string" ? o.modifiedAt : "";
238308
- const tm = typeof t.modifiedAt === "string" ? t.modifiedAt : "";
238309
- const newer = om >= tm ? o : t;
238310
- const ol = asMap(o.localisedAt), tl = asMap(t.localisedAt);
238311
- const loc = {};
238312
- for (const k of /* @__PURE__ */ new Set([...Object.keys(ol), ...Object.keys(tl)])) {
238313
- const a = typeof ol[k] === "string" ? ol[k] : "";
238314
- const b = typeof tl[k] === "string" ? tl[k] : "";
238315
- loc[k] = a >= b ? ol[k] ?? tl[k] : tl[k] ?? ol[k];
238316
- }
238317
- const merged = { ...newer };
238318
- if (Object.keys(loc).length > 0) merged.localisedAt = loc;
238319
- return merged;
238320
- }
238321
- function mergeKeyedByName(base, ours, theirs, path, conflicts) {
238322
- const byName = (arr) => {
238323
- const m = {};
238324
- for (const p of arr) if (isObj(p) && typeof p.name === "string") m[p.name] = p;
238325
- return m;
238326
- };
238327
- const merged = mergeMap(byName(base), byName(ours), byName(theirs), path, conflicts);
238328
- return Object.keys(merged).sort().map((name) => merged[name]);
238329
- }
238330
- function mergeProject(base, ours, theirs) {
238331
- const conflicts = [];
238332
- const merged = {};
238333
- for (const k of /* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(ours), ...Object.keys(theirs)])) {
238334
- if (k === "properties" || k === "cast") {
238335
- const v = mergeKeyedByName(asArr(base[k]), asArr(ours[k]), asArr(theirs[k]), k, conflicts);
238336
- if (v.length > 0) merged[k] = v;
238337
- } else if (k === "gameDataFields") {
238338
- const v = mergeGameDataFields(asMap(base[k]), asMap(ours[k]), asMap(theirs[k]), conflicts);
238339
- if (Object.keys(v).length > 0) merged[k] = v;
238340
- } else if (k === "locales") {
238341
- merged.locales = mergeLocales(asMap(base.locales), asMap(ours.locales), asMap(theirs.locales), conflicts);
238342
- } else {
238343
- const v = merge3(base[k], ours[k], theirs[k], "", k, conflicts);
238344
- if (v !== void 0) merged[k] = v;
238345
- }
238346
- }
238347
- return { type: "project", merged, conflicts, warnings: [] };
238348
- }
238349
- function mergeGameDataFields(b, o, t, conflicts) {
238350
- const out = {};
238351
- for (const kind of /* @__PURE__ */ new Set([...Object.keys(b), ...Object.keys(o), ...Object.keys(t)])) {
238352
- const v = mergeKeyedByName(asArr(b[kind]), asArr(o[kind]), asArr(t[kind]), `gameDataFields.${kind}`, conflicts);
238353
- if (v.length > 0) out[kind] = v;
238354
- }
238355
- return out;
238356
- }
238357
- function mergeLocales(b, o, t, conflicts) {
238358
- const out = {};
238359
- const def = merge3(b.default, o.default, t.default, "", "locales.default", conflicts);
238360
- if (def !== void 0) out.default = def;
238361
- const all = [];
238362
- for (const arr of [asArr(b.all), asArr(o.all), asArr(t.all)]) {
238363
- for (const x of arr) if (typeof x === "string" && !all.includes(x)) all.push(x);
238364
- }
238365
- out.all = all;
238366
- return out;
238367
- }
238368
- var CHILD_KEYS = ["blocks", "children", "beats"];
238369
- function toTree(raw) {
238370
- const childKey = CHILD_KEYS.find((k) => Array.isArray(raw[k]));
238371
- const fields = {};
238372
- for (const [k, v] of Object.entries(raw)) if (k !== childKey) fields[k] = v;
238373
- const children = childKey ? raw[childKey].filter(isObj).map(toTree) : [];
238374
- return { id: typeof raw.id === "string" ? raw.id : "", fields, childKey, children };
238375
- }
238376
- function fromTree(n) {
238377
- const out = { ...n.fields };
238378
- if (n.childKey) out[n.childKey] = n.children.map(fromTree);
238379
- return out;
238380
- }
238381
- function mergeFlow(base, ours, theirs) {
238382
- const conflicts = [];
238383
- const warnings = [];
238384
- const merged = {};
238385
- merged.schema = merge3(base.schema, ours.schema, theirs.schema, "", "schema", conflicts) ?? ours.schema;
238386
- const bScene = isObj(base.scene) ? toTree(base.scene) : void 0;
238387
- const oScene = toTree(asMap(ours.scene));
238388
- const tScene = isObj(theirs.scene) ? toTree(theirs.scene) : void 0;
238389
- const mergedScene = mergeNode(bScene, oScene, tScene, "scene", conflicts, warnings);
238390
- merged.scene = fromTree(mergedScene);
238391
- checkDuplicateIds(mergedScene, conflicts);
238392
- return { type: "flow", merged, conflicts, warnings };
238393
- }
238394
- function mergeNode(b, o, t, path, conflicts, warnings) {
238395
- const fields = mergeFields(b?.fields ?? {}, o.fields, t?.fields ?? {}, o.id, path, conflicts);
238396
- const childKey = o.childKey ?? t?.childKey ?? b?.childKey;
238397
- const children = childKey ? mergeChildren(b?.children ?? [], o.children, t?.children ?? [], `${path}.${childKey}`, conflicts, warnings) : [];
238398
- return { id: o.id, fields, childKey, children };
238399
- }
238400
- function mergeFields(b, o, t, id, path, conflicts) {
238401
- const out = {};
238402
- for (const k of /* @__PURE__ */ new Set([...Object.keys(b), ...Object.keys(o), ...Object.keys(t)])) {
238403
- const v = merge3(b[k], o[k], t[k], id, `${path}.${k}`, conflicts);
238404
- if (v !== void 0) out[k] = v;
238405
- }
238406
- return out;
238407
- }
238408
- function mergeChildren(B, O, T, path, conflicts, warnings) {
238409
- const Bm = idMap(B), Om = idMap(O), Tm = idMap(T);
238410
- const Bset = new Set(Bm.keys()), Oset = new Set(Om.keys()), Tset = new Set(Tm.keys());
238411
- const merged = /* @__PURE__ */ new Map();
238412
- const here = (id) => `${path}[${id}]`;
238413
- for (const id of /* @__PURE__ */ new Set([...Bm.keys(), ...Om.keys(), ...Tm.keys()])) {
238414
- const b = Bm.get(id), o = Om.get(id), t = Tm.get(id);
238415
- const inB = Bset.has(id), inO = Oset.has(id), inT = Tset.has(id);
238416
- if (inO && inT) {
238417
- if (!inB && !eq(fromTree(o), fromTree(t))) {
238418
- conflicts.push({ id, path: here(id), base: void 0, ours: fromTree(o), theirs: fromTree(t), kind: "added-both" });
238419
- merged.set(id, o);
238420
- } else {
238421
- merged.set(id, mergeNode(b, o, t, here(id), conflicts, warnings));
238422
- }
238423
- } else if (inO && !inT) {
238424
- if (inB && !eq(fromTree(o), fromTree(b))) {
238425
- conflicts.push({ id, path: here(id), base: fromTree(b), ours: fromTree(o), theirs: void 0, kind: "delete-vs-edit" });
238426
- merged.set(id, o);
238427
- } else if (!inB) {
238428
- merged.set(id, o);
238429
- }
238430
- } else if (!inO && inT) {
238431
- if (inB && !eq(fromTree(t), fromTree(b))) {
238432
- conflicts.push({ id, path: here(id), base: fromTree(b), ours: void 0, theirs: fromTree(t), kind: "delete-vs-edit" });
238433
- } else if (!inB) {
238434
- merged.set(id, t);
238435
- }
238436
- }
238437
- }
238438
- return orderChildren(merged, B, O, T, path, conflicts, warnings);
238439
- }
238440
- function orderChildren(merged, B, O, T, path, conflicts, warnings) {
238441
- const Bids = B.map((n) => n.id), Oids = O.map((n) => n.id), Tids = T.map((n) => n.id);
238442
- const Oidset = new Set(Oids);
238443
- const common = new Set(Bids.filter((id) => merged.has(id)));
238444
- const restrict = (ids) => ids.filter((id) => common.has(id));
238445
- const relB = restrict(Bids), relO = restrict(Oids), relT = restrict(Tids);
238446
- const fullO = relO.length === common.size, fullT = relT.length === common.size;
238447
- let commonOrder;
238448
- if (!fullO && !fullT) commonOrder = relB;
238449
- else if (!fullO) commonOrder = relT;
238450
- else if (!fullT) commonOrder = relO;
238451
- else if (arrEq(relO, relB)) commonOrder = relT;
238452
- else if (arrEq(relT, relB)) commonOrder = relO;
238453
- else if (arrEq(relO, relT)) commonOrder = relO;
238454
- else {
238455
- conflicts.push({ id: "", path, base: relB, ours: relO, theirs: relT, kind: "moved" });
238456
- commonOrder = relO;
238457
- }
238458
- const anchorIn = (ids, id) => {
238459
- for (let i = ids.indexOf(id) - 1; i >= 0; i--) if (common.has(ids[i])) return ids[i];
238460
- return null;
238461
- };
238462
- const byAnchor = /* @__PURE__ */ new Map();
238463
- const group = (a) => {
238464
- let g = byAnchor.get(a);
238465
- if (!g) {
238466
- g = { ours: [], theirs: [] };
238467
- byAnchor.set(a, g);
238468
- }
238469
- return g;
238470
- };
238471
- for (const n of O) if (merged.has(n.id) && !common.has(n.id)) group(anchorIn(Oids, n.id)).ours.push(n.id);
238472
- for (const n of T) if (merged.has(n.id) && !common.has(n.id) && !Oidset.has(n.id)) group(anchorIn(Tids, n.id)).theirs.push(n.id);
238473
- const out = [];
238474
- const emit = (anchor) => {
238475
- const g = byAnchor.get(anchor);
238476
- if (!g) return;
238477
- if (g.ours.length > 0 && g.theirs.length > 0) warnings.push({ id: anchor ?? "", path, message: "both sides inserted here; kept ours-then-theirs - review the order" });
238478
- out.push(...g.ours, ...g.theirs);
238479
- };
238480
- emit(null);
238481
- for (const id of commonOrder) {
238482
- out.push(id);
238483
- emit(id);
238484
- }
238485
- return out.map((id) => merged.get(id));
238486
- }
238487
- function checkDuplicateIds(root2, conflicts) {
238488
- const seen = /* @__PURE__ */ new Set(), dup = /* @__PURE__ */ new Set();
238489
- const walk2 = (n) => {
238490
- if (n.id) {
238491
- if (seen.has(n.id)) dup.add(n.id);
238492
- else seen.add(n.id);
238493
- }
238494
- n.children.forEach(walk2);
238495
- };
238496
- walk2(root2);
238497
- for (const id of dup) conflicts.push({ id, path: "scene", base: void 0, ours: void 0, theirs: void 0, kind: "structural" });
238498
- }
238499
- var idMap = (nodes) => new Map(nodes.filter((n) => n.id).map((n) => [n.id, n]));
238500
- var arrEq = (a, b) => a.length === b.length && a.every((x, i) => x === b[i]);
238501
-
238502
- // ../ops/src/unpack.ts
238503
238845
  var MANIFEST = "patter.manifest.json";
238504
238846
  var UnsafeEntryError = class extends Error {
238505
238847
  };
@@ -238562,10 +238904,17 @@ async function runUnpackMerge(returnedBytes, baseBytes, projectDir) {
238562
238904
  shards.push({ path: rel, added: true });
238563
238905
  continue;
238564
238906
  }
238565
- const oursObj = parseSource(readFileSync5(outPath, "utf8"));
238566
- const theirsObj = parseSource(theirText);
238907
+ const readSide = (side, text) => {
238908
+ try {
238909
+ return parseSource(text);
238910
+ } catch (e) {
238911
+ throw new Error(`${rel}: the ${side} copy is not readable Patter source - ${e instanceof Error ? e.message : String(e)}`);
238912
+ }
238913
+ };
238914
+ const oursObj = readSide("ours", readFileSync5(outPath, "utf8"));
238915
+ const theirsObj = readSide("theirs", theirText);
238567
238916
  const baseText = base.get(rel);
238568
- const baseObj = baseText !== void 0 ? parseSource(baseText) : {};
238917
+ const baseObj = baseText !== void 0 ? readSide("base", baseText) : {};
238569
238918
  const result = runMerge(baseObj, oursObj, theirsObj);
238570
238919
  writes.push({ path: outPath, content: canonicalStringify(result.merged) });
238571
238920
  if (result.conflicts.length > 0) {
@@ -240467,6 +240816,44 @@ function writeTextFiles(files, encoding = "utf8") {
240467
240816
  return { success: results.every((r) => r.success), results };
240468
240817
  }
240469
240818
 
240819
+ // package.json
240820
+ var package_default = {
240821
+ name: "@patterkit/cli",
240822
+ version: "0.3.0",
240823
+ description: "The `patter` CLI: validate, format, export, and play a Patter project.",
240824
+ type: "module",
240825
+ license: "MIT",
240826
+ author: "Ian Thomas",
240827
+ homepage: "https://patterkit.dev",
240828
+ repository: {
240829
+ type: "git",
240830
+ url: "git+https://github.com/patterkit/patter.git",
240831
+ directory: "packages/cli"
240832
+ },
240833
+ bugs: "https://github.com/patterkit/patter/issues",
240834
+ publishConfig: {
240835
+ access: "public"
240836
+ },
240837
+ bin: {
240838
+ patter: "./dist/cli.js"
240839
+ },
240840
+ files: [
240841
+ "dist",
240842
+ "README.md"
240843
+ ],
240844
+ scripts: {
240845
+ build: "tsup",
240846
+ "build:standalone": "node scripts/build-standalone.mjs",
240847
+ "build:standalone:mac": "node scripts/build-standalone.mjs --targets=darwin-arm64,darwin-x64",
240848
+ "build:standalone:others": "node scripts/build-standalone.mjs --targets=linux-x64,linux-arm64,windows-x64"
240849
+ },
240850
+ dependencies: {
240851
+ "@patterkit/core": "0.2.1",
240852
+ "@patterkit/ops": "0.5.0",
240853
+ "@wildwinter/simple-vc-lib": "^0.4.1"
240854
+ }
240855
+ };
240856
+
240470
240857
  // src/main.ts
240471
240858
  var isPatterSource = (path) => SHARD_EXTENSIONS.some((ext) => path.endsWith(ext));
240472
240859
  function writeMergeResult(result, out, announce) {
@@ -240486,6 +240873,8 @@ function writeMergeResult(result, out, announce) {
240486
240873
  var USAGE = `patter - Patter CLI
240487
240874
 
240488
240875
  Usage:
240876
+ patter --version Print the version (also -v / version)
240877
+ patter --help Print this usage (also -h / help)
240489
240878
  patter init [dir] Scaffold a new project (the <dir>.patter folder, starter scene, VCS config)
240490
240879
  [--name X] [--vcs git|perforce|plastic|svn] [--bundle commit|ignore]
240491
240880
  patter validate [path] Validate a project (structural + expressions + encoding + bundle)
@@ -240603,6 +240992,14 @@ async function main(argv) {
240603
240992
  console.log(USAGE);
240604
240993
  return 0;
240605
240994
  }
240995
+ if (cmd === "--version" || cmd === "-v" || cmd === "version") {
240996
+ console.log(package_default.version);
240997
+ return 0;
240998
+ }
240999
+ if (cmd === "--help" || cmd === "-h" || cmd === "help") {
241000
+ console.log(USAGE);
241001
+ return 0;
241002
+ }
240606
241003
  const canonical = cmd === "fmt" ? "format" : cmd === "stats" ? "report" : cmd;
240607
241004
  if (!(canonical in FLAGS)) {
240608
241005
  console.log(USAGE);
@@ -240689,15 +241086,18 @@ async function run(cmd, positionals, flags) {
240689
241086
  }
240690
241087
  case "validate": {
240691
241088
  const loaded = loadProject(positionals[0] ?? ".");
240692
- const { structural, conditions, interpolation, hygiene, staleBundles, unresolvedMerges, ok } = runValidate(loaded);
241089
+ const { structural, conditions, interpolation, hygiene, staleBundles, unresolvedMerges, orphans, reachability, ok } = runValidate(loaded);
240693
241090
  for (const i of structural) console.error(` [${i.code}] ${i.message}`);
240694
241091
  for (const i of conditions) console.error(` [${i.field}] ${i.nodeId}: ${i.message} (${i.src})`);
240695
241092
  for (const i of interpolation) console.error(` [${i.field}] ${i.nodeId}: ${i.message} (${i.src})`);
240696
241093
  for (const i of hygiene) console.error(` [hygiene] ${i.file}: ${i.message}`);
240697
241094
  for (const i of staleBundles) console.error(` [stale-bundle] ${i.file}: ${i.message}`);
240698
241095
  for (const i of unresolvedMerges) console.error(` [unresolved-merge] ${i.file}: ${i.message}`);
240699
- const count = structural.length + conditions.length + interpolation.length + hygiene.length + staleBundles.length + unresolvedMerges.length;
240700
- if (ok) console.log(`ok - ${loaded.scenes.length} scene(s), no issues`);
241096
+ for (const i of orphans) console.error(` [not-in-project] ${i.file}: ${i.message}`);
241097
+ for (const i of reachability) console.error(` [unreachable] ${i.nodeId}: ${i.message} (${i.src})`);
241098
+ const count = structural.length + conditions.length + interpolation.length + hygiene.length + staleBundles.length + unresolvedMerges.length + orphans.length;
241099
+ const advisory = reachability.length ? `, ${reachability.length} warning(s) above` : "";
241100
+ if (ok) console.log(`ok - ${loaded.scenes.length} scene(s), no issues${advisory}`);
240701
241101
  else console.error(`
240702
241102
  ${count} issue(s)`);
240703
241103
  return ok ? 0 : 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patterkit/cli",
3
- "version": "0.2.6",
3
+ "version": "0.3.0",
4
4
  "description": "The `patter` CLI: validate, format, export, and play a Patter project.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -30,7 +30,7 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "@patterkit/core": "0.2.1",
33
- "@patterkit/ops": "0.3.6",
33
+ "@patterkit/ops": "0.5.0",
34
34
  "@wildwinter/simple-vc-lib": "^0.4.1"
35
35
  }
36
36
  }