@warmhub/cli 0.66.0 → 0.68.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/wh.js +1267 -304
  2. package/package.json +1 -1
package/dist/wh.js CHANGED
@@ -18478,10 +18478,21 @@ var import_awaitAsyncGenerator = __toESM(require_awaitAsyncGenerator(), 1);
18478
18478
  var import_wrapAsyncGenerator = __toESM(require_wrapAsyncGenerator(), 1);
18479
18479
  var import_objectSpread29 = __toESM(require_objectSpread2(), 1);
18480
18480
  // ../../packages/rules/src/builtin-shapes.ts
18481
- var BUILTIN_SHAPE_NAMES = ["Pair", "Triple", "Set", "List"];
18481
+ var BUILTIN_SHAPE_NAMES = ["Pair", "Set", "List"];
18482
+ var RETIRED_COLLECTION_SHAPE_NAMES = ["Triple"];
18483
+ var RESERVED_COLLECTION_SHAPE_NAMES = [
18484
+ ...BUILTIN_SHAPE_NAMES,
18485
+ ...RETIRED_COLLECTION_SHAPE_NAMES
18486
+ ];
18482
18487
  function isBuiltinCollectionShape(name) {
18483
18488
  return BUILTIN_SHAPE_NAMES.includes(name);
18484
18489
  }
18490
+ function isRetiredCollectionShape(name) {
18491
+ return RETIRED_COLLECTION_SHAPE_NAMES.includes(name);
18492
+ }
18493
+ function isReservedCollectionShape(name) {
18494
+ return RESERVED_COLLECTION_SHAPE_NAMES.includes(name);
18495
+ }
18485
18496
  var BUILTIN_CONTENT_SHAPE_NAMES = ["Content"];
18486
18497
  var STORED_CONTENT_NAMES = ["Readme", "Agents"];
18487
18498
  var SYNTHESIZED_CONTENT_NAMES = ["LlmsTxt"];
@@ -18499,14 +18510,6 @@ var BUILTIN_SHAPE_DEFS = {
18499
18510
  },
18500
18511
  description: "An ordered pair of two things"
18501
18512
  },
18502
- Triple: {
18503
- fields: {
18504
- first: { type: "wref", description: "First member of the triple" },
18505
- second: { type: "wref", description: "Second member of the triple" },
18506
- third: { type: "wref", description: "Third member of the triple" }
18507
- },
18508
- description: "An ordered triple of three things"
18509
- },
18510
18513
  Set: {
18511
18514
  fields: {
18512
18515
  members: [{ type: "wref", description: "Members of the set" }]
@@ -18553,36 +18556,6 @@ var COMMIT_OPERATION_KINDS = [
18553
18556
  "assertion",
18554
18557
  "collection"
18555
18558
  ];
18556
- function isCollectionAbout(value) {
18557
- if (typeof value !== "object" || value === null || Array.isArray(value))
18558
- return false;
18559
- const record = value;
18560
- const keys = Object.keys(record);
18561
- if (keys.length !== 1)
18562
- return false;
18563
- const key = keys[0];
18564
- if (key !== "pair" && key !== "triple" && key !== "set" && key !== "list")
18565
- return false;
18566
- return Array.isArray(record[key]);
18567
- }
18568
- function collectionAboutType(about) {
18569
- if ("pair" in about)
18570
- return "pair";
18571
- if ("triple" in about)
18572
- return "triple";
18573
- if ("set" in about)
18574
- return "set";
18575
- return "list";
18576
- }
18577
- function collectionAboutMembers(about) {
18578
- if ("pair" in about)
18579
- return about.pair;
18580
- if ("triple" in about)
18581
- return about.triple;
18582
- if ("set" in about)
18583
- return about.set;
18584
- return about.list;
18585
- }
18586
18559
  function splitLocalPath(name) {
18587
18560
  const idx = name.indexOf("/");
18588
18561
  if (idx === -1)
@@ -18602,21 +18575,6 @@ function inferOperationKind(op) {
18602
18575
  const segments = op.name ? op.name.split("/").filter(Boolean) : [];
18603
18576
  return segments.length >= 3 ? "assertion" : "thing";
18604
18577
  }
18605
- // ../../packages/rules/src/component-manifest-hash.ts
18606
- function stableJson(value) {
18607
- if (!value || typeof value !== "object") {
18608
- return JSON.stringify(value);
18609
- }
18610
- if (Array.isArray(value)) {
18611
- return `[${value.map((entry) => stableJson(entry)).join(",")}]`;
18612
- }
18613
- const record = value;
18614
- return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableJson(record[key])}`).join(",")}}`;
18615
- }
18616
- function stableJsonEquals(left, right) {
18617
- return stableJson(left) === stableJson(right);
18618
- }
18619
-
18620
18578
  // ../../packages/rules/src/component-name-templates.ts
18621
18579
  var COMPONENT_TEMPLATE_VALUES = {
18622
18580
  "org-name": (ctx) => ctx.orgName,
@@ -18645,6 +18603,21 @@ function resolveComponentTemplate(value, ctx) {
18645
18603
  });
18646
18604
  }
18647
18605
 
18606
+ // ../../packages/rules/src/stable-json.ts
18607
+ function stableJson(value) {
18608
+ if (!value || typeof value !== "object") {
18609
+ return JSON.stringify(value);
18610
+ }
18611
+ if (Array.isArray(value)) {
18612
+ return `[${value.map((entry) => stableJson(entry)).join(",")}]`;
18613
+ }
18614
+ const record = value;
18615
+ return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableJson(record[key])}`).join(",")}}`;
18616
+ }
18617
+ function stableJsonEquals(left, right) {
18618
+ return stableJson(left) === stableJson(right);
18619
+ }
18620
+
18648
18621
  // ../../packages/rules/src/component-install.ts
18649
18622
  function manifestShapeData(shape) {
18650
18623
  const data = { fields: shape.fields };
@@ -19949,9 +19922,99 @@ var PLATFORM_STATUS_PROBE_ARTIFACT_FIELDS = {
19949
19922
  description: "Optional URL associated with this artifact."
19950
19923
  }
19951
19924
  };
19925
+ // ../../packages/rules/src/tokens.ts
19926
+ var COMMIT_TOKEN_SYNTAX_REMOVED_MESSAGE = "$N/#N commit-token syntax is no longer supported. Use explicit names and explicit wrefs. For assertions about newly created things, create the thing with a deterministic name and set about to that wref in the same commit.";
19927
+ var ANY_TOKEN_RE = /[$#]\d+/;
19928
+ function hasAnyTokens(s) {
19929
+ return ANY_TOKEN_RE.test(s);
19930
+ }
19931
+
19932
+ // ../../packages/rules/src/preflight-commit.ts
19933
+ function preflightCommitDiagnostics(operations, options) {
19934
+ const errors = [];
19935
+ rejectCommitTokenSyntax(operations, errors);
19936
+ illegalOpSequences(operations, errors, options?.checkAddAdd ?? true);
19937
+ return errors;
19938
+ }
19939
+ function getOpName(op) {
19940
+ return op.name;
19941
+ }
19942
+ function tokenStringFields(op) {
19943
+ const fields = [getOpName(op), op.newName];
19944
+ if (typeof op.about === "string") {
19945
+ fields.push(op.about);
19946
+ }
19947
+ if (op.members) {
19948
+ fields.push(...op.members);
19949
+ }
19950
+ return fields;
19951
+ }
19952
+ function rejectCommitTokenSyntax(operations, errors) {
19953
+ for (let i = 0;i < operations.length; i++) {
19954
+ const op = operations[i];
19955
+ if (!op)
19956
+ continue;
19957
+ for (const field of tokenStringFields(op)) {
19958
+ if (field && hasAnyTokens(field)) {
19959
+ errors.push({
19960
+ code: "COMMIT_TOKEN_SYNTAX_REMOVED",
19961
+ operationIndex: i,
19962
+ message: COMMIT_TOKEN_SYNTAX_REMOVED_MESSAGE
19963
+ });
19964
+ break;
19965
+ }
19966
+ }
19967
+ }
19968
+ }
19969
+ function illegalOpSequences(operations, errors, checkAddAdd) {
19970
+ const opHistory = new Map;
19971
+ for (let i = 0;i < operations.length; i++) {
19972
+ const op = operations[i];
19973
+ if (!op)
19974
+ continue;
19975
+ const name = getOpName(op);
19976
+ if (!name)
19977
+ continue;
19978
+ if (hasAnyTokens(name))
19979
+ continue;
19980
+ const kind = inferOperationKind({ ...op, name });
19981
+ const qualName = kind === "shape" ? `shape:${name}` : `thing:${name}`;
19982
+ const history = opHistory.get(qualName) ?? [];
19983
+ history.push({ operation: op.operation, index: i });
19984
+ opHistory.set(qualName, history);
19985
+ }
19986
+ for (const [qualName, history] of opHistory) {
19987
+ if (history.length < 2)
19988
+ continue;
19989
+ for (let i = 1;i < history.length; i++) {
19990
+ const prev = history[i - 1];
19991
+ const curr = history[i];
19992
+ if (!prev || !curr)
19993
+ continue;
19994
+ const pair = `${prev.operation}+${curr.operation}`;
19995
+ if (checkAddAdd && pair === "add+add") {
19996
+ errors.push({
19997
+ code: "ILLEGAL_OP_SEQUENCE",
19998
+ operationIndex: curr.index,
19999
+ message: `Cannot add "${qualName}" twice in the same commit`
20000
+ });
20001
+ }
20002
+ if (pair === "revise+add") {
20003
+ errors.push({
20004
+ code: "ILLEGAL_OP_SEQUENCE",
20005
+ operationIndex: curr.index,
20006
+ message: `Cannot revise then add "${qualName}" in the same commit`
20007
+ });
20008
+ }
20009
+ }
20010
+ }
20011
+ }
20012
+
19952
20013
  // ../../packages/rules/src/preflight-operation.ts
19953
- var collectionTypes = ["pair", "triple", "set", "list"];
20014
+ var collectionTypes = ["pair", "set", "list"];
19954
20015
  var collectionOps = ["add", "revise"];
20016
+ var COLLECTION_CREATE_REQUIRES_NAME_MESSAGE = "Collection create requires a name. Collections are ordinary named things (ADR 0004).";
20017
+ var COLLECTION_ABOUT_REMOVED_MESSAGE = 'about accepts a wref. Create the collection as its own named operation, then point the assertion at it. Prefer deterministic relationship names, for example: [{"operation":"add","kind":"collection","type":"pair","name":"a-b-relationship","members":["A","B"]},{"operation":"add","kind":"assertion","about":"Pair/a-b-relationship","name":"Assertion/example","data":{}}]. For CLI usage, use wh commit submit --file with the two operations.';
19955
20018
  function preflightOpDiagnostics(op, operationIndex) {
19956
20019
  const errors = [];
19957
20020
  errors.push(...builtinShapeGuard(op, operationIndex));
@@ -19964,17 +20027,36 @@ function preflightOpDiagnostics(op, operationIndex) {
19964
20027
  function builtinShapeGuard(op, operationIndex) {
19965
20028
  const errors = [];
19966
20029
  const name = op.name;
19967
- if (op.kind === "shape" && name && isBuiltinShape(name)) {
20030
+ if (name && isRetiredCollectionShape(name)) {
20031
+ errors.push({
20032
+ code: "RESERVED_NAME",
20033
+ operationIndex,
20034
+ message: `Shape "${name}" is a retired collection shape and cannot be written manually`
20035
+ });
20036
+ }
20037
+ if (op.operation === "rename" && (op.kind === "shape" || op.name !== undefined && !splitLocalPath(op.name)) && op.newName && isRetiredCollectionShape(op.newName)) {
20038
+ errors.push({
20039
+ code: "RESERVED_NAME",
20040
+ operationIndex,
20041
+ message: `Shape "${op.newName}" is a retired collection shape and cannot be written manually`
20042
+ });
20043
+ } else if (op.operation !== "retract" && op.kind === "shape" && name && isBuiltinShape(name)) {
19968
20044
  errors.push({
19969
20045
  code: "RESERVED_NAME",
19970
20046
  operationIndex,
19971
20047
  message: `Shape "${name}" is a built-in shape and cannot be ${op.operation === "add" ? "created" : "revised"} manually`
19972
20048
  });
19973
20049
  }
19974
- if (op.kind === "thing" && name) {
20050
+ if (name) {
19975
20051
  const local = splitLocalPath(name);
19976
- if (local && isBuiltinCollectionShape(local.shapePrefix)) {
19977
- const message = op.operation === "add" ? `Cannot add kind="thing" under built-in shape "${local.shapePrefix}". Use kind="collection" or the about sugar instead.` : `Cannot revise kind="thing" under built-in shape "${local.shapePrefix}". Collections are immutable.`;
20052
+ if (local && isRetiredCollectionShape(local.shapePrefix)) {
20053
+ errors.push({
20054
+ code: "VALIDATION_ERROR",
20055
+ operationIndex,
20056
+ message: `Cannot ${op.operation} under retired collection shape "${local.shapePrefix}". Triple is read-only and retired for new collection writes.`
20057
+ });
20058
+ } else if (op.kind === "thing" && local && isBuiltinCollectionShape(local.shapePrefix)) {
20059
+ const message = op.operation === "add" ? `Cannot add kind="thing" under built-in shape "${local.shapePrefix}". Use kind="collection" instead.` : `Cannot revise kind="thing" under built-in shape "${local.shapePrefix}". Use kind="collection" to revise a collection.`;
19978
20060
  errors.push({
19979
20061
  code: "VALIDATION_ERROR",
19980
20062
  operationIndex,
@@ -20014,16 +20096,32 @@ function contentNameGuard(op, operationIndex) {
20014
20096
  function plusSignGuard(op, operationIndex) {
20015
20097
  const errors = [];
20016
20098
  const name = op.name;
20017
- if (op.kind === "collection")
20099
+ if (op.newName?.includes("+")) {
20100
+ errors.push({
20101
+ code: "VALIDATION_ERROR",
20102
+ operationIndex,
20103
+ message: `Name "${op.newName}" contains reserved character "+". The "+" character is reserved for the historical collection auto-name namespace and is not allowed in user-supplied names.`
20104
+ });
20018
20105
  return errors;
20106
+ }
20019
20107
  if (name?.includes("+")) {
20108
+ if (op.kind === "collection" && op.operation === "add") {
20109
+ errors.push({
20110
+ code: "VALIDATION_ERROR",
20111
+ operationIndex,
20112
+ message: `Name "${name}" contains reserved character "+". The "+" character is reserved for the historical collection auto-name namespace and is not allowed in user-supplied names.`
20113
+ });
20114
+ return errors;
20115
+ }
20116
+ if (op.kind === "collection")
20117
+ return errors;
20020
20118
  const local = splitLocalPath(name);
20021
- if (local && isBuiltinCollectionShape(local.shapePrefix))
20119
+ if (local && isReservedCollectionShape(local.shapePrefix))
20022
20120
  return errors;
20023
20121
  errors.push({
20024
20122
  code: "VALIDATION_ERROR",
20025
20123
  operationIndex,
20026
- message: `Name "${name}" contains reserved character "+". The "+" character is reserved for collection names.`
20124
+ message: `Name "${name}" contains reserved character "+". The "+" character is reserved for the historical collection auto-name namespace and is not allowed in user-supplied names.`
20027
20125
  });
20028
20126
  }
20029
20127
  return errors;
@@ -20032,102 +20130,79 @@ function validateCollectionAbouts(op, operationIndex) {
20032
20130
  const errors = [];
20033
20131
  if (!op.about || typeof op.about === "string")
20034
20132
  return errors;
20035
- if (!isCollectionAbout(op.about)) {
20036
- const keys = typeof op.about === "object" && op.about !== null ? Object.keys(op.about) : [];
20133
+ errors.push({
20134
+ code: "VALIDATION_ERROR",
20135
+ operationIndex,
20136
+ message: COLLECTION_ABOUT_REMOVED_MESSAGE
20137
+ });
20138
+ return errors;
20139
+ }
20140
+ function validateCollectionOps(op, operationIndex) {
20141
+ const errors = [];
20142
+ if (op.kind !== "collection")
20143
+ return errors;
20144
+ if (!collectionOps.includes(op.operation)) {
20145
+ return errors;
20146
+ }
20147
+ if (op.operation === "add" && !op.name) {
20037
20148
  errors.push({
20038
20149
  code: "VALIDATION_ERROR",
20039
20150
  operationIndex,
20040
- message: `Structured about must have exactly one key: pair, triple, set, or list. Got: ${keys.join(", ") || typeof op.about}`
20151
+ message: COLLECTION_CREATE_REQUIRES_NAME_MESSAGE
20041
20152
  });
20042
- return errors;
20043
- }
20044
- const tag = collectionAboutType(op.about);
20045
- const members = collectionAboutMembers(op.about);
20046
- for (let i = 0;i < members.length; i++) {
20047
- if (typeof members[i] !== "string") {
20048
- errors.push({
20049
- code: "VALIDATION_ERROR",
20050
- operationIndex,
20051
- message: `Collection member at index ${i} must be a string, got ${typeof members[i]}`
20052
- });
20053
- }
20054
20153
  }
20055
- const arityError = collectionArityError(tag, members);
20056
- if (arityError) {
20154
+ if (!op.type || !collectionTypes.includes(op.type)) {
20057
20155
  errors.push({
20058
20156
  code: "VALIDATION_ERROR",
20059
20157
  operationIndex,
20060
- message: arityError
20158
+ message: `Collection "type" must be one of: pair, set, list. Got: "${op.type ?? ""}"`
20061
20159
  });
20062
20160
  }
20063
- return errors;
20064
- }
20065
- function validateCollectionOps(op, operationIndex) {
20066
- const errors = [];
20067
- if (op.kind !== "collection")
20068
- return errors;
20069
- if (!collectionOps.includes(op.operation)) {
20161
+ if (!op.members || !Array.isArray(op.members)) {
20070
20162
  errors.push({
20071
20163
  code: "VALIDATION_ERROR",
20072
20164
  operationIndex,
20073
- message: `Collection kind only supports "add" or "revise" operation, got "${op.operation}"`
20165
+ message: 'Collection requires a "members" array'
20074
20166
  });
20075
- return errors;
20076
- }
20077
- if (op.operation === "add") {
20078
- if (!op.type || !collectionTypes.includes(op.type)) {
20079
- errors.push({
20080
- code: "VALIDATION_ERROR",
20081
- operationIndex,
20082
- message: `Collection "type" must be one of: pair, triple, set, list. Got: "${op.type ?? ""}"`
20083
- });
20084
- }
20085
- if (!op.members || !Array.isArray(op.members)) {
20086
- errors.push({
20087
- code: "VALIDATION_ERROR",
20088
- operationIndex,
20089
- message: 'Collection requires a "members" array'
20090
- });
20091
- } else {
20092
- for (let i = 0;i < op.members.length; i++) {
20093
- if (typeof op.members[i] !== "string") {
20094
- errors.push({
20095
- code: "VALIDATION_ERROR",
20096
- operationIndex,
20097
- message: `Collection member at index ${i} must be a string`
20098
- });
20099
- }
20100
- }
20101
- }
20102
- if (op.type && op.members) {
20103
- const arityError = collectionArityError(op.type, op.members);
20104
- if (arityError) {
20167
+ } else {
20168
+ for (let i = 0;i < op.members.length; i++) {
20169
+ if (typeof op.members[i] !== "string") {
20105
20170
  errors.push({
20106
20171
  code: "VALIDATION_ERROR",
20107
20172
  operationIndex,
20108
- message: arityError
20173
+ message: `Collection member at index ${i} must be a string`
20109
20174
  });
20110
20175
  }
20111
20176
  }
20112
20177
  }
20178
+ if (op.type && op.members) {
20179
+ const arityError = collectionArityError(op.type, op.members);
20180
+ if (arityError) {
20181
+ errors.push({
20182
+ code: "VALIDATION_ERROR",
20183
+ operationIndex,
20184
+ message: arityError
20185
+ });
20186
+ }
20187
+ }
20113
20188
  return errors;
20114
20189
  }
20115
20190
  function collectionArityError(tag, members) {
20116
20191
  switch (tag) {
20117
20192
  case "pair":
20118
20193
  return members.length !== 2 ? `Pair requires exactly 2 members, got ${members.length}` : null;
20119
- case "triple":
20120
- return members.length !== 3 ? `Triple requires exactly 3 members, got ${members.length}` : null;
20121
20194
  case "set":
20122
20195
  case "list":
20123
20196
  return members.length < 1 ? `${tag === "set" ? "Set" : "List"} requires at least 1 member, got 0` : null;
20124
20197
  }
20125
20198
  }
20126
-
20127
20199
  // ../../packages/rules/src/preflight.ts
20128
20200
  function preflightOpDiagnostics2(op, operationIndex) {
20129
20201
  return preflightOpDiagnostics(op, operationIndex);
20130
20202
  }
20203
+ function preflightCommitDiagnostics2(operations, options) {
20204
+ return preflightCommitDiagnostics(operations, options);
20205
+ }
20131
20206
  // ../../packages/rules/src/reserved-orgs.ts
20132
20207
  var RESERVED_RAW_CONTENT_TOP_LEVEL_NAMES = [
20133
20208
  "_app",
@@ -27352,7 +27427,7 @@ function findSystemComponent(componentId) {
27352
27427
  // ../../packages/sdk-ts/package.json
27353
27428
  var package_default = {
27354
27429
  name: "@warmhub/sdk-ts",
27355
- version: "0.65.0",
27430
+ version: "0.67.0",
27356
27431
  private: false,
27357
27432
  type: "module",
27358
27433
  description: "The TypeScript SDK for WarmHub — create repos, commit and query data, and compound knowledge with your AI agents.",
@@ -27460,16 +27535,38 @@ function shapeDefinitionPreflightError(name, data, verb) {
27460
27535
  }
27461
27536
 
27462
27537
  // ../../packages/sdk-ts/src/collection-operation-normalize.ts
27463
- var collectionTypes2 = new Set([
27464
- "pair",
27465
- "triple",
27466
- "set",
27467
- "list"
27468
- ]);
27538
+ var collectionTypes2 = new Set(["pair", "set", "list"]);
27469
27539
  function normalizeBackendCollectionAdd(operation, source) {
27470
- const diagnostics = preflightOpDiagnostics2({
27540
+ const normalized = normalizeCollectionWrite(operation, source, "add");
27541
+ return {
27471
27542
  operation: "add",
27472
27543
  kind: "collection",
27544
+ name: normalized.name,
27545
+ type: normalized.type,
27546
+ members: normalized.members,
27547
+ ...operation.skipExisting === true ? { skipExisting: true } : {}
27548
+ };
27549
+ }
27550
+ function normalizeBackendCollectionRevise(operation, source) {
27551
+ const normalized = normalizeCollectionWrite(operation, source, "revise");
27552
+ if (!normalized.name) {
27553
+ throw new Error(`${source}: collection revise requires a target name`);
27554
+ }
27555
+ return {
27556
+ operation: "revise",
27557
+ kind: "collection",
27558
+ name: normalized.name,
27559
+ type: normalized.type,
27560
+ members: normalized.members,
27561
+ ...typeof operation.expectedVersion === "number" ? { expectedVersion: operation.expectedVersion } : {},
27562
+ ...typeof operation.leaseId === "string" && operation.leaseId.length > 0 ? { leaseId: operation.leaseId } : {}
27563
+ };
27564
+ }
27565
+ function normalizeCollectionWrite(operation, source, writeOperation) {
27566
+ const diagnostics = preflightOpDiagnostics2({
27567
+ operation: writeOperation,
27568
+ kind: "collection",
27569
+ name: typeof operation.name === "string" ? operation.name : undefined,
27473
27570
  type: typeof operation.type === "string" ? operation.type : undefined,
27474
27571
  members: Array.isArray(operation.members) ? operation.members : undefined
27475
27572
  }, 0);
@@ -27478,29 +27575,32 @@ function normalizeBackendCollectionAdd(operation, source) {
27478
27575
  }
27479
27576
  const type = normalizeCollectionType(operation.type);
27480
27577
  if (!type) {
27481
- throw new Error(`${source}: collection add requires 'type' to be one of: pair, triple, set, list`);
27578
+ throw new Error(`${source}: collection ${writeOperation} requires 'type' to be one of: pair, set, list`);
27482
27579
  }
27483
27580
  if (!Array.isArray(operation.members)) {
27484
- throw new Error(`${source}: collection add requires a 'members' array`);
27581
+ throw new Error(`${source}: collection ${writeOperation} requires a 'members' array`);
27485
27582
  }
27486
27583
  const name = normalizeOptionalName(operation.name);
27487
- if (operation.name !== undefined && !name) {
27488
- throw new Error(`${source}: collection add name must be a non-empty string when provided`);
27584
+ if (!name) {
27585
+ throw new Error(`${source}: collection ${writeOperation} name must be a non-empty string`);
27489
27586
  }
27490
- assertNoUnsupportedCollectionAddFields(operation, source);
27587
+ assertNoUnsupportedCollectionFields(operation, source, writeOperation);
27491
27588
  return {
27492
- operation: "add",
27493
- kind: "collection",
27494
- ...name ? { name } : {},
27589
+ name,
27495
27590
  type,
27496
- members: operation.members,
27497
- ...operation.skipExisting === true ? { skipExisting: true } : {}
27591
+ members: operation.members
27498
27592
  };
27499
27593
  }
27500
- function assertNoUnsupportedCollectionAddFields(operation, source) {
27501
- const unsupportedFields = ["about", "aboutWref", "shapeWref", "data"].filter((field) => operation[field] !== undefined);
27594
+ function assertNoUnsupportedCollectionFields(operation, source, writeOperation) {
27595
+ const unsupportedFields = [
27596
+ "about",
27597
+ "aboutWref",
27598
+ "shapeWref",
27599
+ "data",
27600
+ ...writeOperation === "revise" ? ["skipExisting"] : []
27601
+ ].filter((field) => operation[field] !== undefined);
27502
27602
  if (unsupportedFields.length > 0) {
27503
- throw new Error(`${source}: collection add does not support ${unsupportedFields.map((field) => `'${field}'`).join(", ")}`);
27603
+ throw new Error(`${source}: collection ${writeOperation} does not support ${unsupportedFields.map((field) => `'${field}'`).join(", ")}`);
27504
27604
  }
27505
27605
  }
27506
27606
  function normalizeCollectionType(value) {
@@ -27550,12 +27650,12 @@ function toBackendStreamOperation(operation) {
27550
27650
  if (Object.hasOwn(operation, "active")) {
27551
27651
  throw new Error(`${kind2} revise operation no longer supports 'active' — use retract('${name}') instead`);
27552
27652
  }
27653
+ if (kind2 === "collection") {
27654
+ return normalizeBackendCollectionRevise(operation, "commit.apply");
27655
+ }
27553
27656
  if (operation.data === undefined) {
27554
27657
  throw new Error(`${kind2} revise operation requires 'data'`);
27555
27658
  }
27556
- if (kind2 === "collection") {
27557
- throw new Error(`collection revise is no longer supported — use retract('${name}') instead`);
27558
- }
27559
27659
  if (kind2 === "assertion") {
27560
27660
  return {
27561
27661
  operation: "revise",
@@ -27590,6 +27690,9 @@ function toBackendStreamOperation(operation) {
27590
27690
  if (!("about" in operation) || operation.about === undefined) {
27591
27691
  throw new Error("assertion add operation requires 'about'");
27592
27692
  }
27693
+ if (typeof operation.about !== "string") {
27694
+ throw new Error(COLLECTION_ABOUT_REMOVED_MESSAGE);
27695
+ }
27593
27696
  if (operation.data === undefined) {
27594
27697
  throw new Error("assertion add operation requires 'data'");
27595
27698
  }
@@ -27782,34 +27885,14 @@ function computeBackoffDelayMs(attempt, policy) {
27782
27885
  function sleep2(ms) {
27783
27886
  return new Promise((resolve) => setTimeout(resolve, ms));
27784
27887
  }
27785
- var TOKEN_REF_REGEX = /[$#]\d+/;
27786
- function stringHasTokenRef(value) {
27787
- return typeof value === "string" && TOKEN_REF_REGEX.test(value);
27788
- }
27789
- function structuralValueHasTokenRef(value, seen = new WeakSet) {
27790
- if (stringHasTokenRef(value))
27791
- return true;
27792
- if (value !== null && typeof value === "object") {
27793
- if (seen.has(value))
27794
- return false;
27795
- seen.add(value);
27796
- if (Array.isArray(value)) {
27797
- return value.some((entry) => structuralValueHasTokenRef(entry, seen));
27798
- }
27799
- return Object.values(value).some((entry) => structuralValueHasTokenRef(entry, seen));
27800
- }
27801
- return false;
27802
- }
27803
- function opUsesTokens(op) {
27804
- return stringHasTokenRef(op.name) || stringHasTokenRef(op.wref) || structuralValueHasTokenRef(op.about) || stringHasTokenRef(op.aboutWref) || structuralValueHasTokenRef(op.members) || structuralValueHasTokenRef(op.data);
27805
- }
27806
27888
 
27807
27889
  // ../../packages/sdk-ts/src/stream-submit-submit.ts
27808
27890
  class StreamValidationError extends Error {
27809
- code = "VALIDATION_ERROR";
27891
+ code;
27810
27892
  status = 400;
27811
- constructor(message) {
27893
+ constructor(message, code = "VALIDATION_ERROR") {
27812
27894
  super(message);
27895
+ this.code = code;
27813
27896
  this.name = "WarmHubError";
27814
27897
  }
27815
27898
  }
@@ -27830,6 +27913,7 @@ async function submitOperationsViaStream(client, args) {
27830
27913
  }
27831
27914
  return streamOperation;
27832
27915
  });
27916
+ validateNormalizedOperations(operations);
27833
27917
  const chunkSize = normalizeChunkSize(args.chunkSize);
27834
27918
  let streamId = args.streamId ?? createStreamId();
27835
27919
  const policy = args.streamId !== undefined ? false : resolveRetryPolicy(args.retry);
@@ -27841,7 +27925,6 @@ async function submitOperationsViaStream(client, args) {
27841
27925
  let attempt = 1;
27842
27926
  let priorAttemptAmbiguous = false;
27843
27927
  const chunkIsAtomic = chunk.length === 1;
27844
- const chunkUsesTokens = chunk.some(opUsesTokens);
27845
27928
  while (true) {
27846
27929
  try {
27847
27930
  const appendResult = await client.stream.append({
@@ -27864,7 +27947,7 @@ async function submitOperationsViaStream(client, args) {
27864
27947
  if (chunkResults.length === 0 && !priorAttemptAmbiguous && isDefiniteClientError(cause)) {
27865
27948
  throw cause;
27866
27949
  }
27867
- if (chunkResults.length === 0 && chunkIsAtomic && !chunkUsesTokens && policy !== false && attempt < policy.maxAttempts && isTransientStreamFailure(cause)) {
27950
+ if (chunkResults.length === 0 && chunkIsAtomic && policy !== false && attempt < policy.maxAttempts && isTransientStreamFailure(cause)) {
27868
27951
  await sleep2(computeBackoffDelayMs(attempt, policy));
27869
27952
  attempt += 1;
27870
27953
  priorAttemptAmbiguous = true;
@@ -27898,6 +27981,16 @@ async function submitOperationsViaStream(client, args) {
27898
27981
  }
27899
27982
  return result;
27900
27983
  }
27984
+ function validateNormalizedOperations(operations) {
27985
+ const diagnostics = preflightCommitDiagnostics2(operations).filter((diagnostic) => !isServerAuthoritativeSequenceDiagnostic(diagnostic));
27986
+ const firstDiagnostic = diagnostics[0];
27987
+ if (!firstDiagnostic)
27988
+ return;
27989
+ throw new StreamValidationError(`Invalid operation at index ${firstDiagnostic.operationIndex}: ${firstDiagnostic.message}`, firstDiagnostic.code);
27990
+ }
27991
+ function isServerAuthoritativeSequenceDiagnostic(diagnostic) {
27992
+ return diagnostic.code === "ILLEGAL_OP_SEQUENCE" && diagnostic.message.includes("Cannot revise then add ");
27993
+ }
27901
27994
  function isAllSubmittedOperationsFailed(result, submittedOperationCount) {
27902
27995
  if (submittedOperationCount <= 0) {
27903
27996
  return false;
@@ -28098,6 +28191,35 @@ function sanitizeSubscriptionUpdateInput(input) {
28098
28191
  } = input;
28099
28192
  return supported;
28100
28193
  }
28194
+ function hasCollectionQuerySource(source) {
28195
+ return !!source && (!!source.shape || !!source.kind || !!source.about || !!source.match || !!source.componentRef || source.excludeComponents === true || (source.where?.length ?? 0) > 0);
28196
+ }
28197
+ function hasCollectionSelectorAnchor(source) {
28198
+ return !!source && (!!source.shape || !!source.about || !!source.match || !!source.componentRef || (source.where?.length ?? 0) > 0);
28199
+ }
28200
+ function hasExplicitCollectionMembers(opts) {
28201
+ const members = opts.members;
28202
+ return Array.isArray(members) && members.length > 0;
28203
+ }
28204
+ function collectionTypeFromWref(wref) {
28205
+ const local = wref.replace(/^wh:[^/]+\/[^/]+\//, "").replace(/@(?:HEAD|ALL|v\d+)$/, "");
28206
+ const shape = local.split("/")[0]?.toLowerCase();
28207
+ return shape === "pair" || shape === "set" || shape === "list" ? shape : undefined;
28208
+ }
28209
+ function assertSelectorBackedCollectionType(type, opts) {
28210
+ if (hasCollectionQuerySource(opts.query) && !hasCollectionSelectorAnchor(opts.query)) {
28211
+ throw new WarmHubError("VALIDATION_ERROR", "Selector-backed collection sources require shape, about, match, componentRef, or where; kind and excludeComponents only narrow an existing selector.");
28212
+ }
28213
+ if (opts.sourceRepo && !hasCollectionSelectorAnchor(opts.query)) {
28214
+ throw new WarmHubError("VALIDATION_ERROR", "sourceRepo requires a selector-backed collection query");
28215
+ }
28216
+ if (opts.sourceRepo && hasExplicitCollectionMembers(opts)) {
28217
+ throw new WarmHubError("VALIDATION_ERROR", "sourceRepo cannot be combined with explicit members; use canonical wh:org/repo/... member wrefs or omit sourceRepo.");
28218
+ }
28219
+ if (hasCollectionQuerySource(opts.query) && type !== "set") {
28220
+ throw new WarmHubError("VALIDATION_ERROR", "Selector-backed collection sources are only supported for set collections");
28221
+ }
28222
+ }
28101
28223
  function trpcClientCodeToWarmHubCode(code) {
28102
28224
  switch (code) {
28103
28225
  case "BAD_REQUEST":
@@ -28926,6 +29048,23 @@ class WarmHubClient {
28926
29048
  throw toWarmHubError(error);
28927
29049
  }
28928
29050
  },
29051
+ explore: async (opts) => {
29052
+ try {
29053
+ return await this.trpc.repo.explore.query({
29054
+ search: opts?.search,
29055
+ org: opts?.org,
29056
+ activity: opts?.activity,
29057
+ hasSubscriptions: opts?.hasSubscriptions,
29058
+ minThings: opts?.minThings,
29059
+ sort: opts?.sort,
29060
+ limit: opts?.limit,
29061
+ cursor: opts?.cursor,
29062
+ slugs: opts?.slugs
29063
+ });
29064
+ } catch (error) {
29065
+ throw toWarmHubError(error);
29066
+ }
29067
+ },
28929
29068
  getReadme: async (orgName, repoName) => {
28930
29069
  try {
28931
29070
  return await this.trpc.repo.getReadme.query({
@@ -29302,87 +29441,226 @@ class WarmHubClient {
29302
29441
  }
29303
29442
  }
29304
29443
  };
29305
- thing = {
29306
- head: async (orgName, repoName, opts) => {
29444
+ collection = {
29445
+ create: async (orgName, repoName, opts) => {
29307
29446
  try {
29308
- const kind = opts?.kind === "shape" || opts?.kind === "thing" || opts?.kind === "assertion" || opts?.kind === "collection" ? opts.kind : undefined;
29309
- return await this.trpc.thing.head.query({
29447
+ assertSelectorBackedCollectionType(opts.type, opts);
29448
+ return await this.trpc.collection.create.mutate({
29310
29449
  orgName,
29311
29450
  repoName,
29312
- shape: opts?.shape,
29313
- kind,
29314
- match: opts?.match,
29315
- dataMode: opts?.dataMode,
29316
- includeRetracted: opts?.includeRetracted,
29451
+ type: opts.type,
29452
+ name: opts.name,
29453
+ members: opts.members,
29454
+ from: opts.from,
29455
+ add: opts.add,
29456
+ remove: opts.remove,
29457
+ replaceMembers: opts.replaceMembers,
29458
+ query: opts.query,
29459
+ sourceOrgName: opts.sourceRepo?.orgName,
29460
+ sourceRepoName: opts.sourceRepo?.repoName,
29461
+ skipExisting: opts.skipExisting,
29462
+ message: opts.message,
29463
+ committer: opts.committer
29464
+ });
29465
+ } catch (error) {
29466
+ throw toWarmHubError(error);
29467
+ }
29468
+ },
29469
+ members: async (orgName, repoName, wref, opts) => {
29470
+ try {
29471
+ return await this.trpc.collection.members.query({
29472
+ orgName,
29473
+ repoName,
29474
+ wref,
29475
+ version: opts?.version,
29317
29476
  limit: opts?.limit,
29318
- cursor: opts?.cursor,
29319
- componentRef: opts?.componentRef,
29320
- excludeComponents: opts?.excludeComponents,
29321
- excludeInfraShapes: opts?.excludeInfraShapes,
29322
- where: opts?.where
29477
+ cursor: opts?.cursor
29323
29478
  });
29324
29479
  } catch (error) {
29325
29480
  throw toWarmHubError(error);
29326
29481
  }
29327
29482
  },
29328
- headIter: (orgName, repoName, opts) => {
29329
- return paginate((cursor) => this.thing.head(orgName, repoName, { ...opts, cursor }), (page) => page.items, opts?.cursor);
29483
+ membersIter: (orgName, repoName, wref, opts) => {
29484
+ let snapshotVersion = opts?.version;
29485
+ return paginate(async (cursor) => {
29486
+ const page = await this.collection.members(orgName, repoName, wref, {
29487
+ ...opts,
29488
+ version: snapshotVersion,
29489
+ cursor
29490
+ });
29491
+ snapshotVersion ??= page.version;
29492
+ return page;
29493
+ }, (page) => page.items, opts?.cursor);
29330
29494
  },
29331
- headAll: async (orgName, repoName, opts) => {
29495
+ membersAll: async (orgName, repoName, wref, opts) => {
29332
29496
  const { max, ...pageOpts } = opts ?? {};
29333
- return await collectPaginatedPages((cursor) => this.thing.head(orgName, repoName, { ...pageOpts, cursor }), (page) => page.items, max, pageOpts.cursor);
29497
+ let snapshotVersion = pageOpts.version;
29498
+ return await collectPaginatedPages(async (cursor) => {
29499
+ const page = await this.collection.members(orgName, repoName, wref, {
29500
+ ...pageOpts,
29501
+ version: snapshotVersion,
29502
+ cursor
29503
+ });
29504
+ snapshotVersion ??= page.version;
29505
+ return page;
29506
+ }, (page) => page.items, max, pageOpts.cursor);
29334
29507
  },
29335
- get: async (orgName, repoName, wref, version, opts) => {
29508
+ contains: async (orgName, repoName, wref, members, opts) => {
29336
29509
  try {
29337
- return await this.trpc.thing.get.query({
29510
+ return await this.trpc.collection.contains.query({
29338
29511
  orgName,
29339
29512
  repoName,
29340
29513
  wref,
29341
- version,
29342
- includeRetracted: opts?.includeRetracted
29514
+ version: opts?.version,
29515
+ members,
29516
+ position: opts?.position
29343
29517
  });
29344
29518
  } catch (error) {
29345
29519
  throw toWarmHubError(error);
29346
29520
  }
29347
29521
  },
29348
- getWithLease: async (orgName, repoName, wref, opts) => {
29522
+ diff: async (orgName, repoName, leftWref, rightWref, opts) => {
29349
29523
  try {
29350
- return await this.trpc.thing.getWithLease.mutate({
29524
+ return await this.trpc.collection.diff.query({
29351
29525
  orgName,
29352
29526
  repoName,
29353
- wref,
29354
- ttlMs: opts?.ttlMs
29527
+ leftWref,
29528
+ rightWref,
29529
+ leftVersion: opts?.leftVersion,
29530
+ rightVersion: opts?.rightVersion,
29531
+ mode: opts?.mode
29355
29532
  });
29356
29533
  } catch (error) {
29357
29534
  throw toWarmHubError(error);
29358
29535
  }
29359
29536
  },
29360
- releaseLease: async (orgName, repoName, wref, leaseId) => {
29537
+ revise: async (orgName, repoName, wref, opts) => {
29361
29538
  try {
29362
- await this.trpc.thing.releaseLease.mutate({
29539
+ const targetType = collectionTypeFromWref(wref);
29540
+ if (hasCollectionQuerySource(opts.query) && !hasCollectionSelectorAnchor(opts.query)) {
29541
+ throw new WarmHubError("VALIDATION_ERROR", "Selector-backed collection sources require shape, about, match, componentRef, or where; kind and excludeComponents only narrow an existing selector.");
29542
+ }
29543
+ if (opts.sourceRepo && !hasCollectionSelectorAnchor(opts.query)) {
29544
+ throw new WarmHubError("VALIDATION_ERROR", "sourceRepo requires a selector-backed collection query");
29545
+ }
29546
+ if (opts.sourceRepo && hasExplicitCollectionMembers(opts)) {
29547
+ throw new WarmHubError("VALIDATION_ERROR", "sourceRepo cannot be combined with explicit members; use canonical wh:org/repo/... member wrefs or omit sourceRepo.");
29548
+ }
29549
+ if (hasCollectionQuerySource(opts.query) && targetType && targetType !== "set") {
29550
+ throw new WarmHubError("VALIDATION_ERROR", "Selector-backed revise requires a Set/<name> target wref");
29551
+ }
29552
+ return await this.trpc.collection.revise.mutate({
29363
29553
  orgName,
29364
29554
  repoName,
29365
29555
  wref,
29366
- leaseId
29556
+ members: opts.members,
29557
+ add: opts.add,
29558
+ remove: opts.remove,
29559
+ query: opts.query,
29560
+ sourceOrgName: opts.sourceRepo?.orgName,
29561
+ sourceRepoName: opts.sourceRepo?.repoName,
29562
+ message: opts.message,
29563
+ committer: opts.committer
29367
29564
  });
29368
29565
  } catch (error) {
29369
29566
  throw toWarmHubError(error);
29370
29567
  }
29371
29568
  },
29372
- graph: async (orgName, repoName, wref, opts) => {
29569
+ stats: async (orgName, repoName, wref, opts) => {
29373
29570
  try {
29374
- return await this.trpc.thing.graph.query({
29571
+ return await this.trpc.collection.stats.query({
29375
29572
  orgName,
29376
29573
  repoName,
29377
29574
  wref,
29378
- version: opts?.version,
29379
- depth: opts?.depth,
29380
- limit: opts?.limit
29575
+ version: opts?.version
29381
29576
  });
29382
29577
  } catch (error) {
29383
29578
  throw toWarmHubError(error);
29384
29579
  }
29385
- },
29580
+ }
29581
+ };
29582
+ thing = {
29583
+ head: async (orgName, repoName, opts) => {
29584
+ try {
29585
+ const kind = opts?.kind === "shape" || opts?.kind === "thing" || opts?.kind === "assertion" || opts?.kind === "collection" ? opts.kind : undefined;
29586
+ return await this.trpc.thing.head.query({
29587
+ orgName,
29588
+ repoName,
29589
+ shape: opts?.shape,
29590
+ kind,
29591
+ match: opts?.match,
29592
+ dataMode: opts?.dataMode,
29593
+ includeRetracted: opts?.includeRetracted,
29594
+ limit: opts?.limit,
29595
+ cursor: opts?.cursor,
29596
+ componentRef: opts?.componentRef,
29597
+ excludeComponents: opts?.excludeComponents,
29598
+ excludeInfraShapes: opts?.excludeInfraShapes,
29599
+ where: opts?.where
29600
+ });
29601
+ } catch (error) {
29602
+ throw toWarmHubError(error);
29603
+ }
29604
+ },
29605
+ headIter: (orgName, repoName, opts) => {
29606
+ return paginate((cursor) => this.thing.head(orgName, repoName, { ...opts, cursor }), (page) => page.items, opts?.cursor);
29607
+ },
29608
+ headAll: async (orgName, repoName, opts) => {
29609
+ const { max, ...pageOpts } = opts ?? {};
29610
+ return await collectPaginatedPages((cursor) => this.thing.head(orgName, repoName, { ...pageOpts, cursor }), (page) => page.items, max, pageOpts.cursor);
29611
+ },
29612
+ get: async (orgName, repoName, wref, version, opts) => {
29613
+ try {
29614
+ return await this.trpc.thing.get.query({
29615
+ orgName,
29616
+ repoName,
29617
+ wref,
29618
+ version,
29619
+ includeRetracted: opts?.includeRetracted,
29620
+ dataMode: opts?.dataMode
29621
+ });
29622
+ } catch (error) {
29623
+ throw toWarmHubError(error);
29624
+ }
29625
+ },
29626
+ getWithLease: async (orgName, repoName, wref, opts) => {
29627
+ try {
29628
+ return await this.trpc.thing.getWithLease.mutate({
29629
+ orgName,
29630
+ repoName,
29631
+ wref,
29632
+ ttlMs: opts?.ttlMs
29633
+ });
29634
+ } catch (error) {
29635
+ throw toWarmHubError(error);
29636
+ }
29637
+ },
29638
+ releaseLease: async (orgName, repoName, wref, leaseId) => {
29639
+ try {
29640
+ await this.trpc.thing.releaseLease.mutate({
29641
+ orgName,
29642
+ repoName,
29643
+ wref,
29644
+ leaseId
29645
+ });
29646
+ } catch (error) {
29647
+ throw toWarmHubError(error);
29648
+ }
29649
+ },
29650
+ graph: async (orgName, repoName, wref, opts) => {
29651
+ try {
29652
+ return await this.trpc.thing.graph.query({
29653
+ orgName,
29654
+ repoName,
29655
+ wref,
29656
+ version: opts?.version,
29657
+ depth: opts?.depth,
29658
+ limit: opts?.limit
29659
+ });
29660
+ } catch (error) {
29661
+ throw toWarmHubError(error);
29662
+ }
29663
+ },
29386
29664
  getMany: async (orgName, repoName, wrefs, version, opts) => {
29387
29665
  try {
29388
29666
  if (wrefs.length === 0) {
@@ -29400,7 +29678,8 @@ class WarmHubClient {
29400
29678
  repoName,
29401
29679
  wrefs: chunkWrefs,
29402
29680
  version,
29403
- includeRetracted: opts?.includeRetracted
29681
+ includeRetracted: opts?.includeRetracted,
29682
+ dataMode: opts?.dataMode
29404
29683
  });
29405
29684
  };
29406
29685
  if (wrefs.length <= chunkSize) {
@@ -29571,7 +29850,8 @@ class WarmHubClient {
29571
29850
  componentRef: opts?.componentRef,
29572
29851
  excludeComponents: opts?.excludeComponents,
29573
29852
  excludeInfraShapes: opts?.excludeInfraShapes,
29574
- mode: opts?.mode
29853
+ mode: opts?.mode,
29854
+ nameMatch: opts?.nameMatch
29575
29855
  });
29576
29856
  } catch (error) {
29577
29857
  throw toWarmHubError(error);
@@ -32864,7 +33144,7 @@ function emitPartialPageHint(ctx, count, _nextCursor, _limit) {
32864
33144
  }
32865
33145
 
32866
33146
  // ../../packages/warmhub-cli/src/domains/assertion/shared.ts
32867
- var COLLECTION_TAGS = ["pair", "triple", "set", "list"];
33147
+ var COLLECTION_TAGS = ["pair", "set", "list"];
32868
33148
  function parseAbout(raw) {
32869
33149
  const colonIdx = raw.indexOf(":");
32870
33150
  if (colonIdx === -1) {
@@ -32874,32 +33154,7 @@ function parseAbout(raw) {
32874
33154
  if (!COLLECTION_TAGS.includes(tag)) {
32875
33155
  return raw;
32876
33156
  }
32877
- const membersStr = raw.slice(colonIdx + 1);
32878
- const members = membersStr.split(",").map((m) => m.trim()).filter(Boolean);
32879
- switch (tag) {
32880
- case "pair":
32881
- if (members.length !== 2) {
32882
- throw new CliError(2 /* UserInput */, "USER_INPUT", `pair requires exactly 2 members, got ${members.length}`, undefined, "Example: --about pair:Location/a,Location/b");
32883
- }
32884
- return { pair: members };
32885
- case "triple":
32886
- if (members.length !== 3) {
32887
- throw new CliError(2 /* UserInput */, "USER_INPUT", `triple requires exactly 3 members, got ${members.length}`, undefined, "Example: --about triple:Location/a,Location/b,Location/c");
32888
- }
32889
- return { triple: members };
32890
- case "set":
32891
- if (members.length === 0) {
32892
- throw new CliError(2 /* UserInput */, "USER_INPUT", "set requires at least 1 member", undefined, "Example: --about set:Location/a,Location/b");
32893
- }
32894
- return { set: members };
32895
- case "list":
32896
- if (members.length === 0) {
32897
- throw new CliError(2 /* UserInput */, "USER_INPUT", "list requires at least 1 member", undefined, "Example: --about list:Location/a,Location/b");
32898
- }
32899
- return { list: members };
32900
- default:
32901
- return raw;
32902
- }
33157
+ throw new CliError(2 /* UserInput */, "USER_INPUT", COLLECTION_ABOUT_REMOVED_MESSAGE, undefined, "Use wh commit submit --file with a named collection add followed by an assertion add.");
32903
33158
  }
32904
33159
  function renderAbout(out, c, result) {
32905
33160
  const target = result.target;
@@ -32997,7 +33252,7 @@ var createFlags = {
32997
33252
  name: flag.string({ description: "Assertion name" }),
32998
33253
  shape: flag.string({ description: "Shape for assertion (required)" }),
32999
33254
  data: flag.string({ description: "Data payload (JSON)" }),
33000
- about: flag.string({ description: "Target wref or collection (pair:a,b)" }),
33255
+ about: flag.string({ description: "Target wref" }),
33001
33256
  message: flag.string({ short: "m", description: "Commit message" }),
33002
33257
  committer: flag.string({
33003
33258
  description: "Committer thing wref (e.g. Agent/bot-1)"
@@ -33356,6 +33611,9 @@ function renderThing(out, c, result) {
33356
33611
  out(` ${c.dim}revisedOn:${c.reset} ${formatTime(meta.revisedOn, now)}`);
33357
33612
  }
33358
33613
  }
33614
+ if (result.collection) {
33615
+ renderCollectionSummary(out, c, result.collection);
33616
+ }
33359
33617
  const fields = shapeName && result.data ? collectionFields(shapeName, result.data) : null;
33360
33618
  if (fields) {
33361
33619
  for (const field of fields) {
@@ -33378,6 +33636,21 @@ function renderThing(out, c, result) {
33378
33636
  }
33379
33637
  }
33380
33638
  }
33639
+ function renderCollectionSummary(out, c, collection) {
33640
+ out(` ${c.dim}collection:${c.reset} ${collection.type}`);
33641
+ out(` ${c.dim}members:${c.reset} ${collection.memberCount}`);
33642
+ if (collection.fullData)
33643
+ return;
33644
+ const limit = collection.inlineLimit ? ` > ${collection.inlineLimit}` : " above inline limit";
33645
+ out(` ${c.dim}data:${c.reset} elided (${collection.memberCount}${limit}; use --data-mode full)`);
33646
+ const preview = collection.preview ?? [];
33647
+ if (preview.length === 0)
33648
+ return;
33649
+ out(` ${c.dim}preview:${c.reset}`);
33650
+ for (const wref of preview) {
33651
+ out(` ${pinnedWref(c, wref)}`);
33652
+ }
33653
+ }
33381
33654
  function renderDataBlock(out, data, indent) {
33382
33655
  const lines = JSON.stringify(data, null, 2).split(`
33383
33656
  `);
@@ -33569,7 +33842,7 @@ var historyFlags = {
33569
33842
  description: "Allow retracted shape/about targets to resolve"
33570
33843
  }),
33571
33844
  "resolve-collections": flag.boolean({
33572
- description: "Include assertions about collections (Pair/Triple/Set/List) containing the target. Only applies with --about."
33845
+ description: "Include assertions about collections (Pair/Set/List) containing the target. Only applies with --about."
33573
33846
  })
33574
33847
  };
33575
33848
  var handleHistory = async (ctx, { flags, args }) => {
@@ -33960,7 +34233,7 @@ var queryFlags = {
33960
34233
  description: "Include retracted things"
33961
34234
  }),
33962
34235
  "resolve-collections": flag.boolean({
33963
- description: "Include assertions about collections (Pair/Triple/Set/List) containing the target. Only applies with --about."
34236
+ description: "Include assertions about collections (Pair/Set/List) containing the target. Only applies with --about."
33964
34237
  }),
33965
34238
  component: flag.string({
33966
34239
  description: "Filter to things owned by this component (Org/Name ref)"
@@ -34373,7 +34646,7 @@ var searchFlags = {
34373
34646
  description: "Include retracted things"
34374
34647
  }),
34375
34648
  "resolve-collections": flag.boolean({
34376
- description: "Include assertions about collections (Pair/Triple/Set/List) containing the target. Only applies with --about."
34649
+ description: "Include assertions about collections (Pair/Set/List) containing the target. Only applies with --about."
34377
34650
  }),
34378
34651
  limit: flag.number({ description: "Max results (default: 25, max: 500)" }),
34379
34652
  cursor: flag.string({ description: "Opaque pagination cursor (text mode)" }),
@@ -34536,6 +34809,9 @@ var viewFlags = {
34536
34809
  }),
34537
34810
  file: flag.string({
34538
34811
  description: "Read additional wrefs from <path>, one per line. Use `--file=-` for stdin (the `=` form is required) or pass bare `-` as a positional. Lines starting with '#' and blank lines are ignored; lines are not split on any other character."
34812
+ }),
34813
+ "data-mode": flag.string({
34814
+ description: "Collection data mode: auto (default) or full. Use full to force large collection bodies into thing view output."
34539
34815
  })
34540
34816
  };
34541
34817
  var MAX_GET_MANY_WREFS = 500;
@@ -34550,6 +34826,13 @@ async function readStreamUtf8(input) {
34550
34826
  function parseLineList(text) {
34551
34827
  return text.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
34552
34828
  }
34829
+ function validateDataMode(value) {
34830
+ if (value === undefined)
34831
+ return;
34832
+ if (value === "auto" || value === "full")
34833
+ return value;
34834
+ usageError("--data-mode must be auto or full", "wh thing view Set/wake-voters --data-mode full");
34835
+ }
34553
34836
  async function collectWrefs(opts) {
34554
34837
  const dashPositional = opts.positionals.includes("-");
34555
34838
  const cleanPositionals = opts.positionals.filter((a) => a !== "-");
@@ -34588,12 +34871,16 @@ async function runSingleView(ctx, wref, flags) {
34588
34871
  const depth = flags.depth;
34589
34872
  const { org, repo } = looksLikeDurableId(wref) && depth === undefined ? parseOrgRepoOptional(getRepoRef(ctx), ctx.config) : parseOrgRepo(getRepoRef(ctx), ctx.config);
34590
34873
  const includeRetracted = flags["include-retracted"] || version !== undefined;
34874
+ const dataMode = validateDataMode(flags["data-mode"]);
34591
34875
  if (depth !== undefined && (depth < 1 || depth > 5)) {
34592
34876
  usageError("Usage: wh thing view <wref> --depth <1-5>", "wh thing view Game/base --depth 2");
34593
34877
  }
34594
34878
  if (depth !== undefined && ctx.liveMode) {
34595
34879
  usageError("--depth is not supported with --live", "wh thing view Game/base --depth 2");
34596
34880
  }
34881
+ if (depth !== undefined && dataMode !== undefined) {
34882
+ usageError("--data-mode cannot be combined with --depth on `view` (graph reads use a separate payload shape).", "wh thing view Set/wake-voters --data-mode full");
34883
+ }
34597
34884
  if (depth !== undefined && (flags["include-retracted"] || version !== undefined)) {
34598
34885
  usageError("--depth cannot be combined with --include-retracted or --version on `view` (graph reads are active-only). Use `wh thing graph` for version-pinned graph reads.", "wh thing view Game/base --depth 2");
34599
34886
  }
@@ -34601,7 +34888,8 @@ async function runSingleView(ctx, wref, flags) {
34601
34888
  await runLive({
34602
34889
  apiUrl: ctx.config.apiUrl,
34603
34890
  poll: (c) => c.thing.get(org, repo, wref, version, {
34604
- includeRetracted
34891
+ includeRetracted,
34892
+ dataMode
34605
34893
  }),
34606
34894
  render: (r) => renderThing(ctx.out, ctx.colors, r),
34607
34895
  out: ctx.out,
@@ -34626,7 +34914,8 @@ async function runSingleView(ctx, wref, flags) {
34626
34914
  return;
34627
34915
  }
34628
34916
  const result = await ctx.client.thing.get(org, repo, wref, version, {
34629
- includeRetracted
34917
+ includeRetracted,
34918
+ dataMode
34630
34919
  });
34631
34920
  writeOutput(ctx, result, () => renderThing(ctx.out, ctx.colors, result));
34632
34921
  }
@@ -34634,7 +34923,8 @@ async function runBatchView(ctx, wrefs, flags) {
34634
34923
  const allDurable = wrefs.length > 0 && wrefs.every(looksLikeDurableId);
34635
34924
  const { org, repo } = allDurable ? parseOrgRepoOptional(getRepoRef(ctx), ctx.config) : parseOrgRepo(getRepoRef(ctx), ctx.config);
34636
34925
  const includeRetracted = flags["include-retracted"] || flags.version !== undefined;
34637
- const result = await ctx.client.thing.getMany(org, repo, wrefs, flags.version, { includeRetracted });
34926
+ const dataMode = validateDataMode(flags["data-mode"]);
34927
+ const result = await ctx.client.thing.getMany(org, repo, wrefs, flags.version, { includeRetracted, dataMode });
34638
34928
  if (ctx.format === "jsonl") {
34639
34929
  for (const event of walkBatchResult(wrefs, result, flags.version)) {
34640
34930
  if (event.kind === "miss") {
@@ -34977,7 +35267,9 @@ var handleHistory2 = async (ctx, { flags, args }) => {
34977
35267
  writePageOutput(ctx, result.versions, { limit, nextCursor: result.nextCursor ?? null }, () => renderHistory(ctx.out, ctx.colors, result));
34978
35268
  };
34979
35269
  var listFlags = {
34980
- about: flag.string({ description: "Target thing wref (required)" }),
35270
+ about: flag.string({
35271
+ description: "Target wref (thing or shape, required)"
35272
+ }),
34981
35273
  shape: flag.string({ description: "Filter by shape" }),
34982
35274
  depth: flag.number({ description: "Assertion depth" }),
34983
35275
  limit: flag.number({
@@ -34988,7 +35280,7 @@ var listFlags = {
34988
35280
  count: flag.boolean({ description: "Return count of matching assertions" }),
34989
35281
  match: flag.string({ description: "Filter by wref glob pattern" }),
34990
35282
  "resolve-collections": flag.boolean({
34991
- description: "Include assertions about collections (Pair/Triple/Set/List) containing the target."
35283
+ description: "Include assertions about collections (Pair/Set/List) containing the target."
34992
35284
  }),
34993
35285
  "include-retracted": flag.boolean({
34994
35286
  description: "Include retracted assertions"
@@ -35171,7 +35463,7 @@ var ASSERTION_DOMAIN = defineDomain({
35171
35463
  flags: createFlags,
35172
35464
  examples: [
35173
35465
  `wh assertion create --shape belief --about player --data '{"confidence":0.8}'`,
35174
- `wh assertion create --shape Distance --about pair:Location/a,Location/b --data '{"value":5}'`
35466
+ `wh assertion create --shape Distance --about Pair/location-distance --data '{"value":5}'`
35175
35467
  ],
35176
35468
  handler: handleCreate
35177
35469
  },
@@ -36562,6 +36854,693 @@ var CHANNEL_DOMAIN = defineDomain({
36562
36854
  handler: handleChannel
36563
36855
  });
36564
36856
 
36857
+ // ../../packages/warmhub-cli/src/domains/collection-helpers.ts
36858
+ import { readFileSync as readFileSync6 } from "node:fs";
36859
+ function parseMemberList(values) {
36860
+ return (values ?? []).flatMap((value) => value.split(",")).map((value) => value.trim()).filter(Boolean);
36861
+ }
36862
+ async function readMembersFromSources(ctx, opts) {
36863
+ const field = opts.wrefField ?? "wref";
36864
+ const members = [
36865
+ ...opts.positionals ?? [],
36866
+ ...parseMemberList(opts.members)
36867
+ ];
36868
+ const fileIsStdin = opts.file === "-";
36869
+ if ((opts.stdin || fileIsStdin) && isTTY(ctx.stdin ?? process.stdin)) {
36870
+ throw new CliError(2 /* UserInput */, "USER_INPUT", "Stdin requested but stdin is a terminal. Pipe input or remove the stdin flag.", undefined, "Example: wh collection create --type set --name audited --stdin");
36871
+ }
36872
+ if (opts.file && !fileIsStdin) {
36873
+ let raw;
36874
+ try {
36875
+ raw = readFileSync6(opts.file, "utf8");
36876
+ } catch (error) {
36877
+ const code = error.code;
36878
+ throw new CliError(2 /* UserInput */, "USER_INPUT", `Cannot read ${opts.file}${code ? ` (${code})` : ""}.`, error, "Check the path or use --file - to read collection members from stdin.");
36879
+ }
36880
+ members.push(...parseMembersPayload(raw, opts.label ?? "--file", field));
36881
+ }
36882
+ if (opts.stdin || fileIsStdin) {
36883
+ const raw = await readStreamUtf82(ctx.stdin ?? process.stdin);
36884
+ members.push(...parseMembersPayload(raw, opts.label ?? "stdin", field));
36885
+ }
36886
+ return members;
36887
+ }
36888
+ function hasCollectionQuerySource2(source) {
36889
+ return !!source.shape || !!source.kind || !!source.about || !!source.match || !!source.componentRef || source.excludeComponents === true || (source.where?.length ?? 0) > 0;
36890
+ }
36891
+ function hasCollectionSelectorAnchor2(source) {
36892
+ return !!source.shape || !!source.about || !!source.match || !!source.componentRef || (source.where?.length ?? 0) > 0;
36893
+ }
36894
+ function requireMembers(members, example) {
36895
+ if (members.length === 0) {
36896
+ usageError("At least one collection member is required", example);
36897
+ }
36898
+ }
36899
+ function renderMutation(ctx, result) {
36900
+ const c = ctx.colors;
36901
+ const action = result.status === "noop" ? "=" : "+";
36902
+ const color = result.status === "noop" ? c.dim : c.green;
36903
+ ctx.out(`${color}${action}${c.reset} ${displayName(c, result.wref)} ${c.dim}${result.type} ${result.memberCount} members${c.reset}`);
36904
+ }
36905
+ function renderMembers(ctx, result) {
36906
+ const c = ctx.colors;
36907
+ if (result.items.length === 0) {
36908
+ ctx.out(`${c.dim}No members${c.reset}`);
36909
+ return;
36910
+ }
36911
+ for (const item of result.items) {
36912
+ const position = item.position ?? 0;
36913
+ ctx.out(`${String(position).padStart(4, " ")} ${pinnedWref(c, item.wref)}`);
36914
+ }
36915
+ }
36916
+ function renderContains(ctx, result) {
36917
+ const c = ctx.colors;
36918
+ for (const item of result.results) {
36919
+ const marker = item.contains ? ctx.chars.check : ctx.chars.cross;
36920
+ const color = item.contains ? c.green : c.red;
36921
+ const at = item.positions === undefined || item.positions.length === 0 ? "" : ` ${c.dim}@${item.positions.join(",")}${c.reset}`;
36922
+ ctx.out(`${color}${marker}${c.reset} ${pinnedWref(c, item.member)}${at}`);
36923
+ }
36924
+ }
36925
+ function renderDiff(ctx, result) {
36926
+ const c = ctx.colors;
36927
+ if (result.mode === "ordered") {
36928
+ if (result.changed.length === 0) {
36929
+ ctx.out(`${c.dim}No ordered differences${c.reset}`);
36930
+ return;
36931
+ }
36932
+ for (const item of result.changed) {
36933
+ const left = item.left?.wref ?? `${c.dim}(none)${c.reset}`;
36934
+ const right = item.right?.wref ?? `${c.dim}(none)${c.reset}`;
36935
+ ctx.out(`${String(item.position).padStart(4, " ")} ${left} -> ${right}`);
36936
+ }
36937
+ return;
36938
+ }
36939
+ if (result.added.length === 0 && result.removed.length === 0) {
36940
+ ctx.out(`${c.dim}No membership differences${c.reset}`);
36941
+ return;
36942
+ }
36943
+ for (const member of result.added) {
36944
+ ctx.out(`${c.green}+${c.reset} ${member.wref}`);
36945
+ }
36946
+ for (const member of result.removed) {
36947
+ ctx.out(`${c.red}-${c.reset} ${member.wref}`);
36948
+ }
36949
+ }
36950
+ function renderStats(ctx, result) {
36951
+ const c = ctx.colors;
36952
+ ctx.out(`${pinnedWref(c, result.wref)} ${c.dim}${result.type}${c.reset}`);
36953
+ ctx.out(`members: ${result.memberCount}`);
36954
+ ctx.out(`unique: ${result.uniqueMemberCount}`);
36955
+ }
36956
+ function memberFromUnknown(value, field, label) {
36957
+ if (typeof value === "string" && value.trim().length > 0) {
36958
+ return value.trim();
36959
+ }
36960
+ if (value && typeof value === "object" && !Array.isArray(value)) {
36961
+ const raw = value[field];
36962
+ if (typeof raw === "string" && raw.trim().length > 0) {
36963
+ return raw.trim();
36964
+ }
36965
+ }
36966
+ throw new CliError(2 /* UserInput */, "USER_INPUT", `Invalid member entry in ${label}. Expected a string wref or an object with "${field}".`, undefined, `Use string wrefs or objects like {"${field}":"Shape/name"}.`);
36967
+ }
36968
+ function parseMembersPayload(raw, label, field = "wref") {
36969
+ const trimmed = raw.trim();
36970
+ if (!trimmed)
36971
+ return [];
36972
+ if (trimmed.startsWith("[")) {
36973
+ const parsed = safeParseJson(trimmed, label);
36974
+ if (!Array.isArray(parsed)) {
36975
+ throw new CliError(2 /* UserInput */, "USER_INPUT", `Invalid ${label}. Expected a JSON array.`, undefined, `Use a JSON array of string wrefs or objects with "${field}".`);
36976
+ }
36977
+ return parsed.map((entry) => memberFromUnknown(entry, field, label));
36978
+ }
36979
+ if (trimmed.startsWith("{")) {
36980
+ let objectParseError;
36981
+ try {
36982
+ const parsed = safeParseJson(trimmed, label);
36983
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed) && Array.isArray(parsed.members)) {
36984
+ return parsed.members.map((entry) => memberFromUnknown(entry, field, label));
36985
+ }
36986
+ return [memberFromUnknown(parsed, field, label)];
36987
+ } catch (error) {
36988
+ objectParseError = error;
36989
+ }
36990
+ const lines = trimmed.split(/\r?\n/).filter((line) => line.trim());
36991
+ if (lines.length > 1) {
36992
+ return lines.map((line) => memberFromUnknown(safeParseJson(line, label), field, label));
36993
+ }
36994
+ throw objectParseError;
36995
+ }
36996
+ return trimmed.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
36997
+ }
36998
+ async function readStreamUtf82(input) {
36999
+ let data = "";
37000
+ input.setEncoding?.("utf8");
37001
+ for await (const chunk of input) {
37002
+ data += typeof chunk === "string" ? chunk : chunk.toString("utf8");
37003
+ }
37004
+ return data;
37005
+ }
37006
+
37007
+ // ../../packages/warmhub-cli/src/domains/collection.ts
37008
+ var COLLECTION_TYPES = ["pair", "set", "list"];
37009
+ var commonWriteFlags = {
37010
+ message: flag.string({ short: "m", description: "Commit message" }),
37011
+ committer: flag.string({
37012
+ description: "Committer thing wref (e.g. Agent/bot-1)"
37013
+ })
37014
+ };
37015
+ var collectionInputFlags = {
37016
+ members: flag.string({
37017
+ multiple: true,
37018
+ description: "Collection member wrefs. May be repeated; comma-separated values are also accepted."
37019
+ }),
37020
+ file: flag.string({
37021
+ description: "Read member wrefs from a file. Supports newline text, JSON array, JSON object with members, or JSONL. Use --file=- for stdin."
37022
+ }),
37023
+ stdin: flag.boolean({
37024
+ description: "Read member wrefs from stdin. Supports newline text, JSON array, JSON object with members, or JSONL."
37025
+ }),
37026
+ "wref-field": flag.string({
37027
+ description: "Object field to read when --file/--stdin contains JSON objects (default: wref)."
37028
+ })
37029
+ };
37030
+ var collectionQueryFlags = {
37031
+ shape: flag.string({ description: "Select set members by shape" }),
37032
+ kind: flag.string({ description: "Select set members by kind" }),
37033
+ about: flag.string({ description: "Select set members by about wref" }),
37034
+ match: flag.string({
37035
+ description: "Select set members by the same name pattern accepted by wh thing query --match"
37036
+ }),
37037
+ component: flag.string({
37038
+ description: "Select set members owned by this component (Org/Name ref)"
37039
+ }),
37040
+ "exclude-components": flag.boolean({
37041
+ description: "Exclude component-owned records from selector-backed sets"
37042
+ }),
37043
+ where: flag.string({
37044
+ multiple: true,
37045
+ description: `Select set members by field-value WHERE predicate (repeatable). Same syntax as wh thing query --where; in:[...] accepts at most ${MAX_WHERE_IN_VALUES} values.`
37046
+ }),
37047
+ "source-repo": flag.string({
37048
+ description: "Read selector-backed set members from this source repo (org/repo). The collection is written to the target repo."
37049
+ })
37050
+ };
37051
+ var collectionCreateFlags = {
37052
+ type: flag.string({
37053
+ description: "Collection type: pair, set, or list"
37054
+ }),
37055
+ name: flag.string({
37056
+ description: "Collection local name. Collections are ordinary named things."
37057
+ }),
37058
+ "skip-existing": flag.boolean({
37059
+ description: "No-op if a named collection already exists. Only valid with --name."
37060
+ }),
37061
+ from: flag.string({
37062
+ description: "Initialize from an existing collection wref"
37063
+ }),
37064
+ add: flag.string({
37065
+ multiple: true,
37066
+ description: "With --from, member wrefs to add. May be repeated; comma-separated values are also accepted."
37067
+ }),
37068
+ remove: flag.string({
37069
+ multiple: true,
37070
+ description: "With --from, member wrefs to remove. May be repeated; comma-separated values are also accepted."
37071
+ }),
37072
+ "replace-file": flag.string({
37073
+ description: "With --from, replace membership from a file. Supports newline text, JSON array, JSON object with members, or JSONL."
37074
+ }),
37075
+ "replace-stdin": flag.boolean({
37076
+ description: "With --from, replace membership from stdin. Supports newline text, JSON array, JSON object with members, or JSONL."
37077
+ }),
37078
+ ...collectionInputFlags,
37079
+ ...collectionQueryFlags,
37080
+ ...commonWriteFlags
37081
+ };
37082
+ var collectionMembersFlags = {
37083
+ limit: flag.number({
37084
+ description: "Max members per page (default: 50, max: 500)"
37085
+ }),
37086
+ cursor: flag.string({ description: "Opaque pagination cursor" }),
37087
+ all: flag.boolean({ description: "Fetch all pages" }),
37088
+ version: flag.number({ description: "Specific version number" })
37089
+ };
37090
+ var collectionContainsFlags = {
37091
+ position: flag.number({
37092
+ description: "For list checks, require the member at this zero-based index"
37093
+ }),
37094
+ version: flag.number({
37095
+ description: "Specific collection version number. Member inputs are still pinned before comparison; use pinned @vN members for historical membership checks."
37096
+ }),
37097
+ ...collectionInputFlags
37098
+ };
37099
+ var collectionDiffFlags = {
37100
+ mode: flag.string({
37101
+ description: "Diff mode: auto, membership, or ordered"
37102
+ }),
37103
+ "left-version": flag.number({
37104
+ description: "Specific left collection version"
37105
+ }),
37106
+ "right-version": flag.number({
37107
+ description: "Specific right collection version"
37108
+ })
37109
+ };
37110
+ var collectionReviseFlags = {
37111
+ add: flag.string({
37112
+ multiple: true,
37113
+ description: "For set collections, member wrefs to add. May be repeated; comma-separated values are also accepted."
37114
+ }),
37115
+ remove: flag.string({
37116
+ multiple: true,
37117
+ description: "For set collections, member wrefs to remove. May be repeated; comma-separated values are also accepted."
37118
+ }),
37119
+ ...collectionInputFlags,
37120
+ ...collectionQueryFlags,
37121
+ ...commonWriteFlags
37122
+ };
37123
+ var collectionStatsFlags = {
37124
+ version: flag.number({ description: "Specific collection version number" })
37125
+ };
37126
+ function validateCollectionType(value) {
37127
+ if (!value || !COLLECTION_TYPES.includes(value)) {
37128
+ usageError("Usage: wh collection create --type <pair|set|list> --members <wref...>", 'wh collection create --type set --name audited --members Location/a,Location/b -m "audit snapshot"');
37129
+ }
37130
+ return value;
37131
+ }
37132
+ function validateDiffMode(value) {
37133
+ if (value === undefined)
37134
+ return;
37135
+ if (value === "auto" || value === "membership" || value === "ordered") {
37136
+ return value;
37137
+ }
37138
+ usageError("--mode must be one of: auto, membership, ordered", "wh collection diff Set/a Set/b --mode membership");
37139
+ }
37140
+ function collectionQuerySourceFromFlags(flags) {
37141
+ const where = (flags.where ?? []).map(parseWhereFlag);
37142
+ const kind = validateKind(flags.kind);
37143
+ return {
37144
+ ...flags.shape ? { shape: flags.shape } : {},
37145
+ ...kind ? { kind } : {},
37146
+ ...flags.about ? { about: flags.about } : {},
37147
+ ...flags.match ? { match: flags.match } : {},
37148
+ ...flags.component ? { componentRef: flags.component } : {},
37149
+ ...flags["exclude-components"] ? { excludeComponents: flags["exclude-components"] } : {},
37150
+ ...where.length > 0 ? { where } : {}
37151
+ };
37152
+ }
37153
+ function parseSourceRepoFlag(value) {
37154
+ if (!value)
37155
+ return;
37156
+ const parsed = splitRepoSlug(value);
37157
+ if (!parsed) {
37158
+ usageError("--source-repo must be an org/repo slug", "wh collection create --type set --name wake-voters --source-repo data/nc-voters --shape Voter --where county=Wake");
37159
+ }
37160
+ return { orgName: parsed.org, repoName: parsed.repo };
37161
+ }
37162
+ function requireQuerySourceForSourceRepo(sourceRepo, querySource, example) {
37163
+ if (!sourceRepo || hasCollectionSelectorAnchor2(querySource))
37164
+ return;
37165
+ usageError("--source-repo requires a selector source", example);
37166
+ }
37167
+ function requireSelectorAnchorForQuerySource(querySource, example) {
37168
+ if (!hasCollectionQuerySource2(querySource) || hasCollectionSelectorAnchor2(querySource)) {
37169
+ return;
37170
+ }
37171
+ usageError("Selector-backed collection sources require shape, about, match, componentRef, or where; kind and exclude-components only narrow an existing selector.", example);
37172
+ }
37173
+ function requireSetForQuerySource(type, source, example) {
37174
+ if (!hasCollectionQuerySource2(source) || type === "set")
37175
+ return;
37176
+ usageError("Selector-backed collection sources are only supported for set collections", example);
37177
+ }
37178
+ function normalizeCreateMembers(type, members) {
37179
+ return type === "set" ? Array.from(new Set(members)) : members;
37180
+ }
37181
+ function hasDeferredMemberSource(flags) {
37182
+ return !!flags.file || !!flags.stdin;
37183
+ }
37184
+ function isMissingCollectionMemberError(error) {
37185
+ const candidate = error;
37186
+ const code = candidate.code ?? candidate.kind;
37187
+ return code === "VALIDATION_ERROR" && /requires (?:at least|exactly) \d+ member/.test(candidate.message ?? "");
37188
+ }
37189
+ function collectionTypeFromWref2(wref) {
37190
+ const local = wref.replace(/^wh:[^/]+\/[^/]+\//, "").replace(/@(?:HEAD|ALL|v\d+)$/, "");
37191
+ const shape = local.split("/")[0]?.toLowerCase();
37192
+ return COLLECTION_TYPES.includes(shape) ? shape : undefined;
37193
+ }
37194
+ function parseCollectionReadRepo(ctx, wrefs) {
37195
+ const allDurable = wrefs.length > 0 && wrefs.every(looksLikeDurableId);
37196
+ return allDurable ? parseOrgRepoOptional(getRepoRef(ctx), ctx.config) : parseOrgRepo(getRepoRef(ctx), ctx.config);
37197
+ }
37198
+ var handleCollectionCreate = async (ctx, { flags, args }) => {
37199
+ if (args.length > 0) {
37200
+ usageError(`Unexpected argument: '${args[0]}'`, "Use --members for explicit collection members: wh collection create --type set --name audited --members Location/a,Location/b");
37201
+ }
37202
+ const { org, repo } = parseOrgRepo(getRepoRef(ctx), ctx.config);
37203
+ const type = validateCollectionType(flags.type);
37204
+ const querySource = collectionQuerySourceFromFlags(flags);
37205
+ const sourceRepo = parseSourceRepoFlag(flags["source-repo"]);
37206
+ const add = parseMemberList(flags.add);
37207
+ const remove = parseMemberList(flags.remove);
37208
+ requireQuerySourceForSourceRepo(sourceRepo, querySource, "wh collection create --type set --name wake-voters --source-repo data/nc-voters --shape Voter --where county=Wake");
37209
+ requireSelectorAnchorForQuerySource(querySource, 'wh collection create --type set --name voters --match "Voter/*" -m "snapshot"');
37210
+ requireSetForQuerySource(type, querySource, 'wh collection create --type set --name voters --match "Voter/*" -m "snapshot"');
37211
+ if (flags.from && hasCollectionQuerySource2(querySource)) {
37212
+ usageError("--from cannot be combined with selector flags", 'wh collection create --type set --name today --from Set/yesterday --add Location/c -m "delta"');
37213
+ }
37214
+ if (flags.from && sourceRepo) {
37215
+ usageError("--from cannot be combined with --source-repo", 'wh collection create --type set --name today --from Set/yesterday --add Location/c -m "delta"');
37216
+ }
37217
+ if (flags.from && (flags.members || flags.file || flags.stdin)) {
37218
+ usageError("--from cannot be combined with --members, --file, or --stdin; use --add/--remove for set deltas or --replace-file/--replace-stdin for full replacement", 'wh collection create --type set --name today --from Set/yesterday --add Location/c -m "delta"');
37219
+ }
37220
+ if ((flags["replace-file"] || flags["replace-stdin"]) && (add.length > 0 || remove.length > 0)) {
37221
+ usageError("--replace-file/--replace-stdin cannot be combined with --add or --remove", 'wh collection create --type set --name today --from Set/yesterday --replace-file members.txt -m "snapshot copy"');
37222
+ }
37223
+ if (!flags.from && (add.length > 0 || remove.length > 0 || flags["replace-file"] || flags["replace-stdin"])) {
37224
+ usageError("--add, --remove, --replace-file, and --replace-stdin require --from", 'wh collection create --type set --name today --from Set/yesterday --add Location/c -m "delta"');
37225
+ }
37226
+ if (!flags.name) {
37227
+ usageError("Collection create requires --name", 'wh collection create --type set --name audited --members Location/a,Location/b -m "audit snapshot"');
37228
+ }
37229
+ if (flags["skip-existing"] && !flags.name) {
37230
+ usageError("--skip-existing requires --name", "wh collection create --type set --name voters --members Voter/a --skip-existing");
37231
+ }
37232
+ const queryBacked = hasCollectionQuerySource2(querySource);
37233
+ const replaceMembers = flags.from && (flags["replace-file"] || flags["replace-stdin"]) ? await readMembersFromSources(ctx, {
37234
+ file: flags["replace-file"],
37235
+ stdin: flags["replace-stdin"],
37236
+ wrefField: flags["wref-field"],
37237
+ label: "replacement members"
37238
+ }) : undefined;
37239
+ const inlineMembers = normalizeCreateMembers(type, parseMemberList(flags.members));
37240
+ if (flags["skip-existing"] && flags.name && !queryBacked && inlineMembers.length === 0 && hasDeferredMemberSource(flags)) {
37241
+ try {
37242
+ const result2 = await ctx.client.collection.create(org, repo, {
37243
+ type,
37244
+ name: flags.name,
37245
+ members: [],
37246
+ skipExisting: true,
37247
+ message: flags.message,
37248
+ committer: flags.committer
37249
+ });
37250
+ writeOutput(ctx, result2, () => renderMutation(ctx, result2));
37251
+ return;
37252
+ } catch (error) {
37253
+ if (!isMissingCollectionMemberError(error)) {
37254
+ throw error;
37255
+ }
37256
+ }
37257
+ }
37258
+ const explicitMembers = await readMembersFromSources(ctx, {
37259
+ members: flags.members,
37260
+ file: flags.from ? undefined : flags.file,
37261
+ stdin: flags.from ? undefined : flags.stdin,
37262
+ wrefField: flags["wref-field"],
37263
+ label: "collection members"
37264
+ });
37265
+ const members = normalizeCreateMembers(type, explicitMembers);
37266
+ if (sourceRepo && members.length > 0) {
37267
+ usageError("--source-repo cannot be combined with explicit members; use canonical wh:org/repo/... member wrefs or omit --source-repo.", "wh collection create --type set --name wake-voters --source-repo data/nc-voters --shape Voter --where county=Wake");
37268
+ }
37269
+ if (!queryBacked && !flags.from) {
37270
+ requireMembers(members, 'wh collection create --type set --name audited --members Location/a,Location/b -m "audit snapshot"');
37271
+ }
37272
+ const result = queryBacked ? sourceRepo ? await ctx.client.collection.create(org, repo, {
37273
+ type,
37274
+ name: flags.name,
37275
+ ...flags.from ? { from: flags.from } : {},
37276
+ ...add.length > 0 ? { add } : {},
37277
+ ...remove.length > 0 ? { remove } : {},
37278
+ ...replaceMembers ? { replaceMembers } : {},
37279
+ query: querySource,
37280
+ sourceRepo,
37281
+ skipExisting: flags["skip-existing"],
37282
+ message: flags.message,
37283
+ committer: flags.committer
37284
+ }) : await ctx.client.collection.create(org, repo, {
37285
+ type,
37286
+ name: flags.name,
37287
+ members,
37288
+ ...flags.from ? { from: flags.from } : {},
37289
+ ...add.length > 0 ? { add } : {},
37290
+ ...remove.length > 0 ? { remove } : {},
37291
+ ...replaceMembers ? { replaceMembers } : {},
37292
+ query: querySource,
37293
+ skipExisting: flags["skip-existing"],
37294
+ message: flags.message,
37295
+ committer: flags.committer
37296
+ }) : await ctx.client.collection.create(org, repo, {
37297
+ type,
37298
+ name: flags.name,
37299
+ members,
37300
+ ...flags.from ? { from: flags.from } : {},
37301
+ ...add.length > 0 ? { add } : {},
37302
+ ...remove.length > 0 ? { remove } : {},
37303
+ ...replaceMembers ? { replaceMembers } : {},
37304
+ skipExisting: flags["skip-existing"],
37305
+ message: flags.message,
37306
+ committer: flags.committer
37307
+ });
37308
+ writeOutput(ctx, result, () => renderMutation(ctx, result));
37309
+ };
37310
+ var handleCollectionMembers = async (ctx, { flags, args }) => {
37311
+ const wref = args[0];
37312
+ if (!wref) {
37313
+ usageError("Usage: wh collection members <wref> [--limit N] [--cursor TOKEN] [--all]", "wh collection members Set/audited --all");
37314
+ }
37315
+ if (flags.cursor && !flags.limit) {
37316
+ usageError("Usage: wh collection members <wref> --limit N --cursor TOKEN", "wh collection members Set/audited --limit 50 --cursor <token>");
37317
+ }
37318
+ const { org, repo } = parseCollectionReadRepo(ctx, [wref]);
37319
+ const boundedLimit = Math.min(flags.limit ?? DEFAULT_PAGE_LIMIT, MAX_PAGE_LIMIT);
37320
+ const pageLimit = flags.all ? Math.min(flags.limit ?? AUTO_PAGE_LIMIT, MAX_PAGE_LIMIT) : boundedLimit;
37321
+ if (flags.all) {
37322
+ const items = [];
37323
+ let cursor = flags.cursor;
37324
+ let snapshotVersion = flags.version;
37325
+ let firstPage;
37326
+ while (true) {
37327
+ const page = await ctx.client.collection.members(org, repo, wref, {
37328
+ version: snapshotVersion,
37329
+ limit: pageLimit,
37330
+ cursor
37331
+ });
37332
+ firstPage ??= {
37333
+ type: page.type,
37334
+ wref: page.wref,
37335
+ version: page.version
37336
+ };
37337
+ items.push(...page.items);
37338
+ snapshotVersion ??= page.version;
37339
+ if (!page.nextCursor)
37340
+ break;
37341
+ cursor = page.nextCursor;
37342
+ }
37343
+ writeCollectionMembersOutput(ctx, {
37344
+ type: firstPage?.type ?? "set",
37345
+ wref: firstPage?.wref ?? wref,
37346
+ version: snapshotVersion ?? firstPage?.version ?? flags.version ?? 1,
37347
+ items,
37348
+ nextCursor: undefined
37349
+ }, { limit: pageLimit, nextCursor: null });
37350
+ return;
37351
+ }
37352
+ const result = await ctx.client.collection.members(org, repo, wref, {
37353
+ version: flags.version,
37354
+ limit: boundedLimit,
37355
+ cursor: flags.cursor
37356
+ });
37357
+ if (result.nextCursor) {
37358
+ emitPartialPageHint(ctx, result.items.length, result.nextCursor, boundedLimit);
37359
+ }
37360
+ writeCollectionMembersOutput(ctx, result, {
37361
+ limit: boundedLimit,
37362
+ nextCursor: result.nextCursor ?? null
37363
+ });
37364
+ };
37365
+ function writeCollectionMembersOutput(ctx, result, page) {
37366
+ if (ctx.format === "json") {
37367
+ const envelope = pageEnvelope(result.items, page);
37368
+ printJson(ctx.out, {
37369
+ type: result.type,
37370
+ wref: result.wref,
37371
+ version: result.version,
37372
+ items: result.items,
37373
+ page: envelope.page
37374
+ });
37375
+ return;
37376
+ }
37377
+ if (ctx.format === "jsonl") {
37378
+ printJsonl(ctx.out, result.items);
37379
+ return;
37380
+ }
37381
+ renderMembers(ctx, result);
37382
+ }
37383
+ var handleCollectionContains = async (ctx, { flags, args }) => {
37384
+ const wref = args[0];
37385
+ if (!wref) {
37386
+ usageError("Usage: wh collection contains <wref> <member...>", "wh collection contains Set/audited Location/a Location/b");
37387
+ }
37388
+ const members = await readMembersFromSources(ctx, {
37389
+ positionals: args.slice(1),
37390
+ members: flags.members,
37391
+ file: flags.file,
37392
+ stdin: flags.stdin,
37393
+ wrefField: flags["wref-field"],
37394
+ label: "collection contains members"
37395
+ });
37396
+ requireMembers(members, "wh collection contains Set/audited Location/a Location/b");
37397
+ const { org, repo } = parseCollectionReadRepo(ctx, [wref]);
37398
+ const result = await ctx.client.collection.contains(org, repo, wref, members, {
37399
+ version: flags.version,
37400
+ position: flags.position
37401
+ });
37402
+ writeOutput(ctx, result, () => renderContains(ctx, result));
37403
+ };
37404
+ var handleCollectionDiff = async (ctx, { flags, args }) => {
37405
+ const [leftWref, rightWref] = args;
37406
+ if (!leftWref || !rightWref) {
37407
+ usageError("Usage: wh collection diff <left-wref> <right-wref> [--mode auto|membership|ordered]", "wh collection diff Set/yesterday Set/today --mode membership");
37408
+ }
37409
+ const { org, repo } = parseCollectionReadRepo(ctx, [leftWref, rightWref]);
37410
+ const result = await ctx.client.collection.diff(org, repo, leftWref, rightWref, {
37411
+ leftVersion: flags["left-version"],
37412
+ rightVersion: flags["right-version"],
37413
+ mode: validateDiffMode(flags.mode)
37414
+ });
37415
+ writeOutput(ctx, result, () => renderDiff(ctx, result));
37416
+ };
37417
+ var handleCollectionRevise = async (ctx, { flags, args }) => {
37418
+ const wref = args[0];
37419
+ if (!wref) {
37420
+ usageError('Usage: wh collection revise <named-wref> --file members.txt -m "message"', 'wh collection revise Set/audited --file members.txt -m "refresh audit set"');
37421
+ }
37422
+ const add = parseMemberList(flags.add);
37423
+ const remove = parseMemberList(flags.remove);
37424
+ if ((add.length > 0 || remove.length > 0) && (flags.members || flags.file || flags.stdin)) {
37425
+ usageError("--add/--remove cannot be combined with replacement members or selector flags", 'wh collection revise Set/audited --add Location/c --remove Location/a -m "delta"');
37426
+ }
37427
+ const members = await readMembersFromSources(ctx, {
37428
+ members: flags.members,
37429
+ file: add.length > 0 || remove.length > 0 ? undefined : flags.file,
37430
+ stdin: add.length > 0 || remove.length > 0 ? undefined : flags.stdin,
37431
+ wrefField: flags["wref-field"],
37432
+ label: "replacement members"
37433
+ });
37434
+ const querySource = collectionQuerySourceFromFlags(flags);
37435
+ const sourceRepo = parseSourceRepoFlag(flags["source-repo"]);
37436
+ requireQuerySourceForSourceRepo(sourceRepo, querySource, 'wh collection revise Set/audited --source-repo data/nc-voters --shape Voter -m "refresh audit set"');
37437
+ requireSelectorAnchorForQuerySource(querySource, 'wh collection revise Set/audited --match "Location/*" -m "refresh audit set"');
37438
+ if (sourceRepo && members.length > 0) {
37439
+ usageError("--source-repo cannot be combined with explicit members; use canonical wh:org/repo/... member wrefs or omit --source-repo.", 'wh collection revise Set/audited --source-repo data/nc-voters --shape Voter -m "refresh audit set"');
37440
+ }
37441
+ if ((add.length > 0 || remove.length > 0) && (members.length > 0 || hasCollectionQuerySource2(querySource))) {
37442
+ usageError("--add/--remove cannot be combined with replacement members or selector flags", 'wh collection revise Set/audited --add Location/c --remove Location/a -m "delta"');
37443
+ }
37444
+ const targetType = collectionTypeFromWref2(wref);
37445
+ if (hasCollectionQuerySource2(querySource) && targetType && targetType !== "set") {
37446
+ usageError("Selector-backed revise requires a Set/<name> target wref", 'wh collection revise Set/audited --match "Location/*" -m "refresh audit set"');
37447
+ }
37448
+ const { org, repo } = parseOrgRepo(getRepoRef(ctx), ctx.config);
37449
+ const queryBacked = hasCollectionQuerySource2(querySource);
37450
+ if (!queryBacked && add.length === 0 && remove.length === 0) {
37451
+ requireMembers(members, 'wh collection revise Set/audited --file members.txt -m "refresh audit set"');
37452
+ }
37453
+ const result = queryBacked ? sourceRepo ? await ctx.client.collection.revise(org, repo, wref, {
37454
+ query: querySource,
37455
+ sourceRepo,
37456
+ message: flags.message,
37457
+ committer: flags.committer
37458
+ }) : await ctx.client.collection.revise(org, repo, wref, {
37459
+ members,
37460
+ query: querySource,
37461
+ message: flags.message,
37462
+ committer: flags.committer
37463
+ }) : await ctx.client.collection.revise(org, repo, wref, {
37464
+ members,
37465
+ ...add.length > 0 ? { add } : {},
37466
+ ...remove.length > 0 ? { remove } : {},
37467
+ message: flags.message,
37468
+ committer: flags.committer
37469
+ });
37470
+ writeOutput(ctx, result, () => renderMutation(ctx, result));
37471
+ };
37472
+ var handleCollectionStats = async (ctx, { flags, args }) => {
37473
+ const wref = args[0];
37474
+ if (!wref) {
37475
+ usageError("Usage: wh collection stats <wref>", "wh collection stats Set/audited");
37476
+ }
37477
+ const { org, repo } = parseCollectionReadRepo(ctx, [wref]);
37478
+ const result = await ctx.client.collection.stats(org, repo, wref, {
37479
+ version: flags.version
37480
+ });
37481
+ writeOutput(ctx, result, () => renderStats(ctx, result));
37482
+ };
37483
+ var COLLECTION_DOMAIN = defineDomain({
37484
+ name: "collection",
37485
+ summary: "Create, inspect, compare, and revise WarmHub collections (Pair, Set, List).",
37486
+ group: "resource",
37487
+ verbs: {
37488
+ create: {
37489
+ summary: "Create a collection thing from explicit members or a set selector",
37490
+ args: "",
37491
+ flags: collectionCreateFlags,
37492
+ examples: [
37493
+ 'wh collection create --type set --name audited --members Location/a,Location/b -m "audit snapshot"',
37494
+ 'wh collection create --type set --name voters --match "Voter/*" -m "voter snapshot"',
37495
+ 'wh collection create --type set --name wake-voters --shape Voter --where state=NC --where county=Wake -m "snapshot Wake County voters"',
37496
+ 'wh collection create --type set --name today --from Set/yesterday --add Location/c --remove Location/a -m "delta"'
37497
+ ],
37498
+ handler: handleCollectionCreate
37499
+ },
37500
+ revise: {
37501
+ summary: "Revise a named collection by replacement, selector, or set delta",
37502
+ args: "<named-wref>",
37503
+ flags: collectionReviseFlags,
37504
+ examples: [
37505
+ 'wh collection revise Set/audited --file members.txt -m "refresh audit set"',
37506
+ 'wh collection revise Set/audited --match "Location/*" -m "refresh audit set"',
37507
+ 'wh collection revise Set/audited --add Location/c --remove Location/a -m "delta"'
37508
+ ],
37509
+ handler: handleCollectionRevise
37510
+ },
37511
+ members: {
37512
+ summary: "List collection members with pagination",
37513
+ args: "<wref>",
37514
+ flags: collectionMembersFlags,
37515
+ examples: ["wh collection members Set/audited --all"],
37516
+ handler: handleCollectionMembers
37517
+ },
37518
+ contains: {
37519
+ summary: "Check collection membership for one or more wrefs",
37520
+ args: "<wref> <member...>",
37521
+ flags: collectionContainsFlags,
37522
+ examples: ["wh collection contains Set/audited Location/a Location/b"],
37523
+ handler: handleCollectionContains
37524
+ },
37525
+ diff: {
37526
+ summary: "Compare two collections by membership or order",
37527
+ args: "<left-wref> <right-wref>",
37528
+ flags: collectionDiffFlags,
37529
+ examples: [
37530
+ "wh collection diff Set/yesterday Set/today --mode membership"
37531
+ ],
37532
+ handler: handleCollectionDiff
37533
+ },
37534
+ stats: {
37535
+ summary: "Summarize collection size and identity",
37536
+ args: "<wref>",
37537
+ flags: collectionStatsFlags,
37538
+ examples: ["wh collection stats Set/audited"],
37539
+ handler: handleCollectionStats
37540
+ }
37541
+ }
37542
+ });
37543
+
36565
37544
  // ../../packages/warmhub-cli/src/domains/commit-submit-flags.ts
36566
37545
  var createFlags3 = {
36567
37546
  ops: flag.string({
@@ -36626,7 +37605,10 @@ var createFlags3 = {
36626
37605
  multiple: true
36627
37606
  }),
36628
37607
  type: flag.string({
36629
- description: "Collection type: pair, triple, set, list"
37608
+ description: "Collection type: pair, set, list"
37609
+ }),
37610
+ name: flag.string({
37611
+ description: "Collection name for --type shorthand"
36630
37612
  }),
36631
37613
  members: flag.string({
36632
37614
  description: "Collection members (comma-separated wrefs)"
@@ -36771,32 +37753,12 @@ function emitTemplateHint(ctx, operationType) {
36771
37753
  const assertionArgs = operationType === "add" ? "--kind assertion --about <Target/FILL_IN>" : "--kind assertion";
36772
37754
  ctx.err(`Template note: shapes define payload fields. To scaffold an assertion, rerun with ${assertionArgs}.`);
36773
37755
  }
36774
- var COLLECTION_ABOUT_PREFIX_RE = /^(pair|triple|set|list):(.*)$/;
37756
+ var COLLECTION_ABOUT_PREFIX_RE = /^(pair|set|list):(.*)$/;
36775
37757
  function parseCollectionAboutFlag(raw) {
36776
37758
  const match = COLLECTION_ABOUT_PREFIX_RE.exec(raw);
36777
37759
  if (!match)
36778
37760
  return raw;
36779
- const tag = match[1];
36780
- const members = match[2].split(",").map((m) => m.trim()).filter(Boolean);
36781
- switch (tag) {
36782
- case "pair":
36783
- if (members.length !== 2) {
36784
- throw new CliError(2 /* UserInput */, "USER_INPUT", `pair requires exactly 2 members, got ${members.length}`, undefined, "Example: --about pair:Location/a,Location/b");
36785
- }
36786
- break;
36787
- case "triple":
36788
- if (members.length !== 3) {
36789
- throw new CliError(2 /* UserInput */, "USER_INPUT", `triple requires exactly 3 members, got ${members.length}`, undefined, "Example: --about triple:Location/a,Location/b,Location/c");
36790
- }
36791
- break;
36792
- case "set":
36793
- case "list":
36794
- if (members.length === 0) {
36795
- throw new CliError(2 /* UserInput */, "USER_INPUT", `${tag} requires at least 1 member`, undefined, `Example: --about ${tag}:Location/a,Location/b`);
36796
- }
36797
- break;
36798
- }
36799
- return { [tag]: members };
37761
+ throw new CliError(2 /* UserInput */, "USER_INPUT", COLLECTION_ABOUT_REMOVED_MESSAGE, undefined, "Use wh commit submit --file with a named collection add followed by an assertion add.");
36800
37762
  }
36801
37763
  var handleTemplate = async (ctx, { flags, args }) => {
36802
37764
  const shapeNames = args;
@@ -37600,7 +38562,7 @@ var handleSubmit = async (ctx, { flags, args }) => {
37600
38562
  throw new CliError(2 /* UserInput */, "USER_INPUT", `Invalid --kind "${k}". Must be one of: ${validKinds.join(", ")}`);
37601
38563
  }
37602
38564
  }
37603
- const validCollectionTypes = ["pair", "triple", "set", "list"];
38565
+ const validCollectionTypes = ["pair", "set", "list"];
37604
38566
  if (flags.type && !validCollectionTypes.includes(flags.type)) {
37605
38567
  throw new CliError(2 /* UserInput */, "USER_INPUT", `Invalid --type "${flags.type}". Must be one of: ${validCollectionTypes.join(", ")}`);
37606
38568
  }
@@ -37632,11 +38594,14 @@ var handleSubmit = async (ctx, { flags, args }) => {
37632
38594
  if (streamInput) {
37633
38595
  operations = [];
37634
38596
  } else if (collectionType) {
38597
+ if (!flags.name) {
38598
+ usageError("Usage: wh commit submit --type <pair|set|list> --name <collection-name> --members <wref1,wref2,...>", "wh commit submit --type pair --name location-distance --members Location/a,Location/b");
38599
+ }
37635
38600
  if (!flags.members) {
37636
- usageError("Usage: wh commit submit --type <pair|triple|set|list> --members <wref1,wref2,...>", "wh commit submit --type pair --members Location/a,Location/b");
38601
+ usageError("Usage: wh commit submit --type <pair|set|list> --name <collection-name> --members <wref1,wref2,...>", "wh commit submit --type pair --name location-distance --members Location/a,Location/b");
37637
38602
  }
37638
38603
  const members = flags.members.split(",").map((m) => m.trim()).filter(Boolean);
37639
- const arityMap = { pair: 2, triple: 3 };
38604
+ const arityMap = { pair: 2 };
37640
38605
  const expected = arityMap[collectionType];
37641
38606
  if (expected && members.length !== expected) {
37642
38607
  throw new CliError(2 /* UserInput */, "USER_INPUT", `${collectionType} requires exactly ${expected} members, got ${members.length}`);
@@ -37649,6 +38614,7 @@ var handleSubmit = async (ctx, { flags, args }) => {
37649
38614
  operation: "add",
37650
38615
  kind: "collection",
37651
38616
  type: collectionType,
38617
+ name: flags.name,
37652
38618
  members
37653
38619
  }
37654
38620
  ];
@@ -37799,8 +38765,8 @@ var COMMIT_DOMAIN = defineDomain({
37799
38765
  `printf '%s\\n' '{"operation":"add","kind":"thing","name":"Player/alice","data":{"score":1}}' | wh commit submit --stream --stream-id bulk-2026-06-04 --skip-existing --repo acme/world -m "stdin stream"`,
37800
38766
  'wh commit submit --file dataset.jsonl --stream-id bulk-2026-06-04 --skip-existing --progress -m "bulk stream"',
37801
38767
  "wh shape template Session HypothesisCandidate -o ops.json",
37802
- "wh commit submit --type pair --members Location/a,Location/b",
37803
- 'wh commit submit --type set --members Location/a,Location/b,Location/c -m "Create location set"'
38768
+ "wh commit submit --type pair --name location-distance --members Location/a,Location/b",
38769
+ 'wh commit submit --type set --name active-locations --members Location/a,Location/b,Location/c -m "Create location set"'
37804
38770
  ],
37805
38771
  notes: [
37806
38772
  "Need to build an ops file? Run `wh shape template <Shape>` to scaffold the JSON from a shape definition, edit the FILL_IN placeholders, then pass it to `--file`.",
@@ -37814,7 +38780,7 @@ var COMMIT_DOMAIN = defineDomain({
37814
38780
  import {
37815
38781
  existsSync as existsSync7,
37816
38782
  mkdirSync as mkdirSync6,
37817
- readFileSync as readFileSync6,
38783
+ readFileSync as readFileSync7,
37818
38784
  renameSync as renameSync3,
37819
38785
  rmSync as rmSync4,
37820
38786
  writeFileSync as writeFileSync6
@@ -37850,7 +38816,7 @@ function loadInstallSnapshotCacheRaw(repoSlug) {
37850
38816
  if (!path2 || !existsSync7(path2))
37851
38817
  return null;
37852
38818
  try {
37853
- const raw = readFileSync6(path2, "utf-8");
38819
+ const raw = readFileSync7(path2, "utf-8");
37854
38820
  return JSON.parse(raw);
37855
38821
  } catch {
37856
38822
  return null;
@@ -38620,7 +39586,7 @@ function formatReservedNameWarning(name) {
38620
39586
  }
38621
39587
 
38622
39588
  // ../../packages/warmhub-cli/src/manifest/parser.ts
38623
- import { existsSync as existsSync8, readFileSync as readFileSync7 } from "node:fs";
39589
+ import { existsSync as existsSync8, readFileSync as readFileSync8 } from "node:fs";
38624
39590
  import { resolve } from "node:path";
38625
39591
  function parseComponentPackage(dirPath) {
38626
39592
  const rootDir = resolve(dirPath);
@@ -38633,7 +39599,7 @@ function parseComponentPackage(dirPath) {
38633
39599
  }
38634
39600
  let componentRaw;
38635
39601
  try {
38636
- componentRaw = JSON.parse(readFileSync7(componentJsonPath, "utf-8"));
39602
+ componentRaw = JSON.parse(readFileSync8(componentJsonPath, "utf-8"));
38637
39603
  } catch (err) {
38638
39604
  errors.push(`Failed to parse warmhub/component.json: ${err instanceof Error ? err.message : String(err)}`);
38639
39605
  return { ok: false, errors, warnings };
@@ -38648,7 +39614,7 @@ function parseComponentPackage(dirPath) {
38648
39614
  }
38649
39615
  let manifestRaw;
38650
39616
  try {
38651
- manifestRaw = JSON.parse(readFileSync7(manifestJsonPath, "utf-8"));
39617
+ manifestRaw = JSON.parse(readFileSync8(manifestJsonPath, "utf-8"));
38652
39618
  } catch (err) {
38653
39619
  errors.push(`Failed to parse warmhub/manifest.json: ${err instanceof Error ? err.message : String(err)}`);
38654
39620
  return { ok: false, errors, warnings };
@@ -38849,7 +39815,7 @@ function formatLifecycleUrl(url, colors) {
38849
39815
  }
38850
39816
 
38851
39817
  // ../../packages/warmhub-cli/src/domains/component-utils.ts
38852
- import { readFileSync as readFileSync8 } from "node:fs";
39818
+ import { readFileSync as readFileSync9 } from "node:fs";
38853
39819
  function isRegisteredComponentSource(source) {
38854
39820
  return /^[a-z0-9-]+\/[a-z0-9-]+$/.test(source);
38855
39821
  }
@@ -38889,7 +39855,7 @@ function resolveMintedTokensFlag(args) {
38889
39855
  function readManifestArg(path2) {
38890
39856
  let raw;
38891
39857
  try {
38892
- raw = readFileSync8(path2, "utf8");
39858
+ raw = readFileSync9(path2, "utf8");
38893
39859
  } catch {
38894
39860
  throw new CliError(2 /* UserInput */, "USER_INPUT", `Cannot read manifest file: ${path2}`, undefined, "Pass --manifest <path to warmhub/manifest.json>");
38895
39861
  }
@@ -41508,7 +42474,7 @@ wh assertion create --shape ShapeName --about Target/name --name my-assertion --
41508
42474
  wh thing list --repo org/repo # all things at HEAD
41509
42475
  wh thing view Shape/name --repo org/repo # inspect a thing
41510
42476
  wh thing query --shape MyShape --repo org/repo # find things by shape
41511
- wh thing about Shape/name --repo org/repo # assertions about a thing
42477
+ wh thing about Shape/name --repo org/repo # assertions about thing/shape
41512
42478
  wh assertion list --repo org/repo # all assertions at HEAD
41513
42479
  wh thing history Shape/name --repo org/repo # version history
41514
42480
 
@@ -41520,17 +42486,16 @@ cat wrefs.txt | wh thing view --format jsonl # one JSON line per *deduped
41520
42486
 
41521
42487
  ## Wref Quick Reference
41522
42488
 
41523
- **Batch tokens** (in write operations):
41524
- - \`$N\` in last segment of ADD name generates an opaque stream-scoped identifier
41525
- - \`#N\` anywhere references value allocated by \`$N\`
41526
- - Example: \`Player/player-$1\` + about \`Player/player-#1\` → \`Player/player-a1b2c3d4e5f6a7b8\`
42489
+ Write operations use explicit names and explicit wrefs. To connect operations
42490
+ inside one commit, create the first thing with a deterministic name and point
42491
+ later operations at that wref.
41527
42492
 
41528
42493
  ## Command Reference
41529
42494
 
41530
42495
  **Global flags**: \`--repo\`, \`--format\`, \`--json\`, \`--live\`
41531
42496
  ### thing — Thing operations
41532
42497
  - \`wh thing list [--shape] [--kind] [--match] [--include-retracted]\` — Current HEAD state
41533
- - \`wh thing view [<wref>...] [--file <path>] [--version] [--depth] [--include-retracted]\` — Thing details. Variadic: singleton routes to \`thing.get\`; multiple wrefs or \`--file\` routes to batch fetch (max 500). Wrefs from positionals, \`--file <path>\` (use \`--file=-\` for stdin), bare \`-\` positional (stdin), or piped stdin when no other source given. Explicit positionals never implicitly consume stdin. \`--version\` implies \`--include-retracted\`. Batch returns \`{ requested, items, missing }\` in \`--json\`; \`--format jsonl\` emits one record per deduped requested wref.
42498
+ - \`wh thing view [<wref>...] [--file <path>] [--version] [--depth] [--include-retracted] [--data-mode auto|full]\` — Thing details. Variadic (max 500). \`--version\` implies \`--include-retracted\`. Batch JSON returns \`{ requested, items, missing }\`; jsonl emits one row per deduped requested wref. Large Set/List bodies summarize by default; use \`--data-mode full\` for canonical collection JSON.
41534
42499
  - \`wh thing history [wref] [--shape] [--about] [--include-retracted]\` — Version history
41535
42500
  - \`wh thing resolve <wref>\` — Resolve wref
41536
42501
  - \`wh thing create <name|Shape/name> --data <json-object> [--shape] [--message] [--committer]\` — Create
@@ -41539,7 +42504,7 @@ cat wrefs.txt | wh thing view --format jsonl # one JSON line per *deduped
41539
42504
  - \`wh thing query [--shape] [--kind] [--about] [--match]\` — Query by filters
41540
42505
  - \`wh thing search <query> [--shape] [--kind] [--about] [--mode]\` — Search text
41541
42506
  - \`wh thing rename <Shape/oldName> <newName>\` — Rename
41542
- - \`wh thing refs <wref> [--inbound] [--outbound] [--field]\` — Show field references; use \`wh thing about\` for assertions about a thing
42507
+ - \`wh thing refs <wref> [--inbound] [--outbound] [--field]\` — Show field references; use \`wh thing about\` for assertions about things/shapes
41543
42508
  - \`wh thing about <wref> [--shape] [--match] [--depth] [--resolve-collections] [--limit] [--include-retracted]\` — Show assertions about the target identity; \`--resolve-collections\` expands collection members for bare/@HEAD/@ALL inputs, not pinned @vN
41544
42509
 
41545
42510
  ### commit — Write operations
@@ -41625,12 +42590,12 @@ cat wrefs.txt | wh thing view --format jsonl # one JSON line per *deduped
41625
42590
  wh thing list --repo org/repo # see all things in HEAD
41626
42591
  wh thing view Shape/name --repo org/repo # inspect a specific thing
41627
42592
  wh thing history Shape/name --repo org/repo # inspect version history
41628
- wh thing about Shape/name # assertions about a thing
42593
+ wh thing about Shape/name # assertions about thing/shape
41629
42594
  \`\`\`
41630
42595
 
41631
42596
  **Create an assertion** (most common write):
41632
42597
  \`\`\`bash
41633
- # --about takes a wref (Shape/name). Use \`wh thing list\` to find valid wrefs.
42598
+ # --about takes a target wref: Shape/name thing, or Shape itself.
41634
42599
  wh assertion create --shape MyShape --about TargetShape/target-name \\
41635
42600
  --name my-assertion --data '{"field_a":1,"field_b":"value"}' --repo org/repo
41636
42601
  # Output includes per-operation status; relay failures when present.
@@ -41659,17 +42624,15 @@ wh commit submit --file ops.jsonl --stream-id "$ID" --chunk-size 5000 \\
41659
42624
  # --chunk-size: ops per append chunk (default 1000, max 10000); chunk≈1 is ~60× slower
41660
42625
  # --skip-existing: skips already-written add ops (drops per-row read-before-write)
41661
42626
  # Add-stream restart: rerun the WHOLE file with the SAME --stream-id.
41662
- # Fixed-name adds are idempotent via --skip-existing; tokenized ($N/#N) names
41663
- # derive from streamId, so rerun rebuilds token state. Omitting --stream-id is
41664
- # rejected because reruns must reuse the same token namespace. Mid-stream resume
41665
- # is not a CLI mode. Mixed revise/retract JSONL streams are not full-rerun safe
41666
- # after an ambiguous append; inspect repo state and reconcile explicitly.
42627
+ # Fixed-name adds are idempotent via --skip-existing. Mid-stream resume is not
42628
+ # a CLI mode. Mixed revise/retract JSONL streams are not full-rerun safe after
42629
+ # an ambiguous append; inspect repo state and reconcile explicitly.
41667
42630
  \`\`\`
41668
42631
 
41669
42632
  **Create collections:**
41670
42633
  \`\`\`bash
41671
- wh commit submit --type pair --members Location/a,Location/b --repo org/repo
41672
- wh assertion create --shape Distance --about pair:Location/a,Location/b --data '{"value":5}' --repo org/repo
42634
+ wh commit submit --type pair --name location-distance --members Location/a,Location/b --repo org/repo
42635
+ wh assertion create --shape Distance --about Pair/location-distance --data '{"value":5}' --repo org/repo
41673
42636
  \`\`\`
41674
42637
 
41675
42638
  **Modify data:**
@@ -41759,8 +42722,7 @@ var wrefSyntax = {
41759
42722
  "GameState/round-1/state"
41760
42723
  ],
41761
42724
  canonicalFormat: "wh:org/repo/Shape/name",
41762
- versionModifiers: ["@HEAD", "@vN", "@ALL"],
41763
- batchTokens: { allocate: "$N", reference: "#N" }
42725
+ versionModifiers: ["@HEAD", "@vN", "@ALL"]
41764
42726
  };
41765
42727
  var handlePrime = async (ctx) => {
41766
42728
  writeOutput(ctx, {
@@ -44084,7 +45046,7 @@ var TOKEN_DOMAIN = defineDomain({
44084
45046
  });
44085
45047
 
44086
45048
  // ../../packages/warmhub-cli/src/update-check-cache.ts
44087
- import { mkdirSync as mkdirSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "node:fs";
45049
+ import { mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync7 } from "node:fs";
44088
45050
  import { homedir as homedir5 } from "node:os";
44089
45051
  import { dirname as dirname7, resolve as resolve3 } from "node:path";
44090
45052
 
@@ -44153,7 +45115,7 @@ var WH_CACHE_PACKAGE_NAME = WH_CLI_PACKAGE_NAME;
44153
45115
  var cachePath = (homePath) => resolve3(homePath, ".warmhub", "cli", "update-check.json");
44154
45116
  var readCache = (homePath) => {
44155
45117
  try {
44156
- return JSON.parse(readFileSync9(cachePath(homePath), "utf8"));
45118
+ return JSON.parse(readFileSync10(cachePath(homePath), "utf8"));
44157
45119
  } catch {
44158
45120
  return;
44159
45121
  }
@@ -44177,7 +45139,7 @@ var markUpdateNoticeShown = ({
44177
45139
 
44178
45140
  // ../../packages/warmhub-cli/src/domains/update-install.ts
44179
45141
  import { spawnSync as spawnSync2 } from "node:child_process";
44180
- import { existsSync as existsSync9, readFileSync as readFileSync10, realpathSync } from "node:fs";
45142
+ import { existsSync as existsSync9, readFileSync as readFileSync11, realpathSync } from "node:fs";
44181
45143
  import { homedir as homedir6 } from "node:os";
44182
45144
  import { dirname as dirname8, resolve as resolve4 } from "node:path";
44183
45145
  var DEV_INSTALL_PACKAGE_SEARCH_DEPTH = 8;
@@ -44327,7 +45289,7 @@ var isDevInstall = (scriptPath) => {
44327
45289
  for (let i = 0;i < DEV_INSTALL_PACKAGE_SEARCH_DEPTH; i += 1) {
44328
45290
  const pkgPath = resolve4(dir, "package.json");
44329
45291
  try {
44330
- const pkg = JSON.parse(readFileSync10(pkgPath, "utf8"));
45292
+ const pkg = JSON.parse(readFileSync11(pkgPath, "utf8"));
44331
45293
  if (pkg.name === "@warmhub/cli") {
44332
45294
  if (pkg.private === true)
44333
45295
  return true;
@@ -44613,6 +45575,7 @@ function registerAllDomains(registry2) {
44613
45575
  registry2.register(INIT_DOMAIN);
44614
45576
  registry2.register(REPO_DOMAIN);
44615
45577
  registry2.register(THING_DOMAIN);
45578
+ registry2.register(COLLECTION_DOMAIN);
44616
45579
  registry2.register(COMMIT_DOMAIN);
44617
45580
  registry2.register(ASSERTION_DOMAIN);
44618
45581
  registry2.register(SHAPE_DOMAIN);
@@ -45735,7 +46698,7 @@ function resolveLogLevel(flags, env) {
45735
46698
  // package.json
45736
46699
  var package_default3 = {
45737
46700
  name: "@warmhub/cli",
45738
- version: "0.66.0",
46701
+ version: "0.68.0",
45739
46702
  private: false,
45740
46703
  type: "module",
45741
46704
  description: "The wh CLI for WarmHub — create repos, commit and query data, and compound knowledge with your AI agents.",
@@ -46390,4 +47353,4 @@ if (!updateCheckSuppressedByArgv && shouldRunUpdateCheck(updateEligibility)) {
46390
47353
  var interceptedExitCode = await maybeHandleComponentShellBoundary(dispatchArgv);
46391
47354
  process.exitCode = interceptedExitCode === undefined ? await runCli(dispatchArgv, { version: package_default3.version }) : interceptedExitCode;
46392
47355
 
46393
- //# debugId=C5B05FE58189780B64756E2164756E21
47356
+ //# debugId=201635543D774B8564756E2164756E21