@warmhub/cli 0.67.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 +1199 -254
  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)
@@ -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.66.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":
@@ -29319,6 +29441,144 @@ class WarmHubClient {
29319
29441
  }
29320
29442
  }
29321
29443
  };
29444
+ collection = {
29445
+ create: async (orgName, repoName, opts) => {
29446
+ try {
29447
+ assertSelectorBackedCollectionType(opts.type, opts);
29448
+ return await this.trpc.collection.create.mutate({
29449
+ orgName,
29450
+ repoName,
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,
29476
+ limit: opts?.limit,
29477
+ cursor: opts?.cursor
29478
+ });
29479
+ } catch (error) {
29480
+ throw toWarmHubError(error);
29481
+ }
29482
+ },
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);
29494
+ },
29495
+ membersAll: async (orgName, repoName, wref, opts) => {
29496
+ const { max, ...pageOpts } = opts ?? {};
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);
29507
+ },
29508
+ contains: async (orgName, repoName, wref, members, opts) => {
29509
+ try {
29510
+ return await this.trpc.collection.contains.query({
29511
+ orgName,
29512
+ repoName,
29513
+ wref,
29514
+ version: opts?.version,
29515
+ members,
29516
+ position: opts?.position
29517
+ });
29518
+ } catch (error) {
29519
+ throw toWarmHubError(error);
29520
+ }
29521
+ },
29522
+ diff: async (orgName, repoName, leftWref, rightWref, opts) => {
29523
+ try {
29524
+ return await this.trpc.collection.diff.query({
29525
+ orgName,
29526
+ repoName,
29527
+ leftWref,
29528
+ rightWref,
29529
+ leftVersion: opts?.leftVersion,
29530
+ rightVersion: opts?.rightVersion,
29531
+ mode: opts?.mode
29532
+ });
29533
+ } catch (error) {
29534
+ throw toWarmHubError(error);
29535
+ }
29536
+ },
29537
+ revise: async (orgName, repoName, wref, opts) => {
29538
+ try {
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({
29553
+ orgName,
29554
+ repoName,
29555
+ wref,
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
29564
+ });
29565
+ } catch (error) {
29566
+ throw toWarmHubError(error);
29567
+ }
29568
+ },
29569
+ stats: async (orgName, repoName, wref, opts) => {
29570
+ try {
29571
+ return await this.trpc.collection.stats.query({
29572
+ orgName,
29573
+ repoName,
29574
+ wref,
29575
+ version: opts?.version
29576
+ });
29577
+ } catch (error) {
29578
+ throw toWarmHubError(error);
29579
+ }
29580
+ }
29581
+ };
29322
29582
  thing = {
29323
29583
  head: async (orgName, repoName, opts) => {
29324
29584
  try {
@@ -29356,7 +29616,8 @@ class WarmHubClient {
29356
29616
  repoName,
29357
29617
  wref,
29358
29618
  version,
29359
- includeRetracted: opts?.includeRetracted
29619
+ includeRetracted: opts?.includeRetracted,
29620
+ dataMode: opts?.dataMode
29360
29621
  });
29361
29622
  } catch (error) {
29362
29623
  throw toWarmHubError(error);
@@ -29417,7 +29678,8 @@ class WarmHubClient {
29417
29678
  repoName,
29418
29679
  wrefs: chunkWrefs,
29419
29680
  version,
29420
- includeRetracted: opts?.includeRetracted
29681
+ includeRetracted: opts?.includeRetracted,
29682
+ dataMode: opts?.dataMode
29421
29683
  });
29422
29684
  };
29423
29685
  if (wrefs.length <= chunkSize) {
@@ -32882,7 +33144,7 @@ function emitPartialPageHint(ctx, count, _nextCursor, _limit) {
32882
33144
  }
32883
33145
 
32884
33146
  // ../../packages/warmhub-cli/src/domains/assertion/shared.ts
32885
- var COLLECTION_TAGS = ["pair", "triple", "set", "list"];
33147
+ var COLLECTION_TAGS = ["pair", "set", "list"];
32886
33148
  function parseAbout(raw) {
32887
33149
  const colonIdx = raw.indexOf(":");
32888
33150
  if (colonIdx === -1) {
@@ -32892,32 +33154,7 @@ function parseAbout(raw) {
32892
33154
  if (!COLLECTION_TAGS.includes(tag)) {
32893
33155
  return raw;
32894
33156
  }
32895
- const membersStr = raw.slice(colonIdx + 1);
32896
- const members = membersStr.split(",").map((m) => m.trim()).filter(Boolean);
32897
- switch (tag) {
32898
- case "pair":
32899
- if (members.length !== 2) {
32900
- throw new CliError(2 /* UserInput */, "USER_INPUT", `pair requires exactly 2 members, got ${members.length}`, undefined, "Example: --about pair:Location/a,Location/b");
32901
- }
32902
- return { pair: members };
32903
- case "triple":
32904
- if (members.length !== 3) {
32905
- 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");
32906
- }
32907
- return { triple: members };
32908
- case "set":
32909
- if (members.length === 0) {
32910
- throw new CliError(2 /* UserInput */, "USER_INPUT", "set requires at least 1 member", undefined, "Example: --about set:Location/a,Location/b");
32911
- }
32912
- return { set: members };
32913
- case "list":
32914
- if (members.length === 0) {
32915
- throw new CliError(2 /* UserInput */, "USER_INPUT", "list requires at least 1 member", undefined, "Example: --about list:Location/a,Location/b");
32916
- }
32917
- return { list: members };
32918
- default:
32919
- return raw;
32920
- }
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.");
32921
33158
  }
32922
33159
  function renderAbout(out, c, result) {
32923
33160
  const target = result.target;
@@ -33015,7 +33252,7 @@ var createFlags = {
33015
33252
  name: flag.string({ description: "Assertion name" }),
33016
33253
  shape: flag.string({ description: "Shape for assertion (required)" }),
33017
33254
  data: flag.string({ description: "Data payload (JSON)" }),
33018
- about: flag.string({ description: "Target wref or collection (pair:a,b)" }),
33255
+ about: flag.string({ description: "Target wref" }),
33019
33256
  message: flag.string({ short: "m", description: "Commit message" }),
33020
33257
  committer: flag.string({
33021
33258
  description: "Committer thing wref (e.g. Agent/bot-1)"
@@ -33374,6 +33611,9 @@ function renderThing(out, c, result) {
33374
33611
  out(` ${c.dim}revisedOn:${c.reset} ${formatTime(meta.revisedOn, now)}`);
33375
33612
  }
33376
33613
  }
33614
+ if (result.collection) {
33615
+ renderCollectionSummary(out, c, result.collection);
33616
+ }
33377
33617
  const fields = shapeName && result.data ? collectionFields(shapeName, result.data) : null;
33378
33618
  if (fields) {
33379
33619
  for (const field of fields) {
@@ -33396,6 +33636,21 @@ function renderThing(out, c, result) {
33396
33636
  }
33397
33637
  }
33398
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
+ }
33399
33654
  function renderDataBlock(out, data, indent) {
33400
33655
  const lines = JSON.stringify(data, null, 2).split(`
33401
33656
  `);
@@ -33587,7 +33842,7 @@ var historyFlags = {
33587
33842
  description: "Allow retracted shape/about targets to resolve"
33588
33843
  }),
33589
33844
  "resolve-collections": flag.boolean({
33590
- 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."
33591
33846
  })
33592
33847
  };
33593
33848
  var handleHistory = async (ctx, { flags, args }) => {
@@ -33978,7 +34233,7 @@ var queryFlags = {
33978
34233
  description: "Include retracted things"
33979
34234
  }),
33980
34235
  "resolve-collections": flag.boolean({
33981
- 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."
33982
34237
  }),
33983
34238
  component: flag.string({
33984
34239
  description: "Filter to things owned by this component (Org/Name ref)"
@@ -34391,7 +34646,7 @@ var searchFlags = {
34391
34646
  description: "Include retracted things"
34392
34647
  }),
34393
34648
  "resolve-collections": flag.boolean({
34394
- 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."
34395
34650
  }),
34396
34651
  limit: flag.number({ description: "Max results (default: 25, max: 500)" }),
34397
34652
  cursor: flag.string({ description: "Opaque pagination cursor (text mode)" }),
@@ -34554,6 +34809,9 @@ var viewFlags = {
34554
34809
  }),
34555
34810
  file: flag.string({
34556
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."
34557
34815
  })
34558
34816
  };
34559
34817
  var MAX_GET_MANY_WREFS = 500;
@@ -34568,6 +34826,13 @@ async function readStreamUtf8(input) {
34568
34826
  function parseLineList(text) {
34569
34827
  return text.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
34570
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
+ }
34571
34836
  async function collectWrefs(opts) {
34572
34837
  const dashPositional = opts.positionals.includes("-");
34573
34838
  const cleanPositionals = opts.positionals.filter((a) => a !== "-");
@@ -34606,12 +34871,16 @@ async function runSingleView(ctx, wref, flags) {
34606
34871
  const depth = flags.depth;
34607
34872
  const { org, repo } = looksLikeDurableId(wref) && depth === undefined ? parseOrgRepoOptional(getRepoRef(ctx), ctx.config) : parseOrgRepo(getRepoRef(ctx), ctx.config);
34608
34873
  const includeRetracted = flags["include-retracted"] || version !== undefined;
34874
+ const dataMode = validateDataMode(flags["data-mode"]);
34609
34875
  if (depth !== undefined && (depth < 1 || depth > 5)) {
34610
34876
  usageError("Usage: wh thing view <wref> --depth <1-5>", "wh thing view Game/base --depth 2");
34611
34877
  }
34612
34878
  if (depth !== undefined && ctx.liveMode) {
34613
34879
  usageError("--depth is not supported with --live", "wh thing view Game/base --depth 2");
34614
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
+ }
34615
34884
  if (depth !== undefined && (flags["include-retracted"] || version !== undefined)) {
34616
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");
34617
34886
  }
@@ -34619,7 +34888,8 @@ async function runSingleView(ctx, wref, flags) {
34619
34888
  await runLive({
34620
34889
  apiUrl: ctx.config.apiUrl,
34621
34890
  poll: (c) => c.thing.get(org, repo, wref, version, {
34622
- includeRetracted
34891
+ includeRetracted,
34892
+ dataMode
34623
34893
  }),
34624
34894
  render: (r) => renderThing(ctx.out, ctx.colors, r),
34625
34895
  out: ctx.out,
@@ -34644,7 +34914,8 @@ async function runSingleView(ctx, wref, flags) {
34644
34914
  return;
34645
34915
  }
34646
34916
  const result = await ctx.client.thing.get(org, repo, wref, version, {
34647
- includeRetracted
34917
+ includeRetracted,
34918
+ dataMode
34648
34919
  });
34649
34920
  writeOutput(ctx, result, () => renderThing(ctx.out, ctx.colors, result));
34650
34921
  }
@@ -34652,7 +34923,8 @@ async function runBatchView(ctx, wrefs, flags) {
34652
34923
  const allDurable = wrefs.length > 0 && wrefs.every(looksLikeDurableId);
34653
34924
  const { org, repo } = allDurable ? parseOrgRepoOptional(getRepoRef(ctx), ctx.config) : parseOrgRepo(getRepoRef(ctx), ctx.config);
34654
34925
  const includeRetracted = flags["include-retracted"] || flags.version !== undefined;
34655
- 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 });
34656
34928
  if (ctx.format === "jsonl") {
34657
34929
  for (const event of walkBatchResult(wrefs, result, flags.version)) {
34658
34930
  if (event.kind === "miss") {
@@ -34995,7 +35267,9 @@ var handleHistory2 = async (ctx, { flags, args }) => {
34995
35267
  writePageOutput(ctx, result.versions, { limit, nextCursor: result.nextCursor ?? null }, () => renderHistory(ctx.out, ctx.colors, result));
34996
35268
  };
34997
35269
  var listFlags = {
34998
- about: flag.string({ description: "Target thing wref (required)" }),
35270
+ about: flag.string({
35271
+ description: "Target wref (thing or shape, required)"
35272
+ }),
34999
35273
  shape: flag.string({ description: "Filter by shape" }),
35000
35274
  depth: flag.number({ description: "Assertion depth" }),
35001
35275
  limit: flag.number({
@@ -35006,7 +35280,7 @@ var listFlags = {
35006
35280
  count: flag.boolean({ description: "Return count of matching assertions" }),
35007
35281
  match: flag.string({ description: "Filter by wref glob pattern" }),
35008
35282
  "resolve-collections": flag.boolean({
35009
- description: "Include assertions about collections (Pair/Triple/Set/List) containing the target."
35283
+ description: "Include assertions about collections (Pair/Set/List) containing the target."
35010
35284
  }),
35011
35285
  "include-retracted": flag.boolean({
35012
35286
  description: "Include retracted assertions"
@@ -35189,7 +35463,7 @@ var ASSERTION_DOMAIN = defineDomain({
35189
35463
  flags: createFlags,
35190
35464
  examples: [
35191
35465
  `wh assertion create --shape belief --about player --data '{"confidence":0.8}'`,
35192
- `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}'`
35193
35467
  ],
35194
35468
  handler: handleCreate
35195
35469
  },
@@ -36580,6 +36854,693 @@ var CHANNEL_DOMAIN = defineDomain({
36580
36854
  handler: handleChannel
36581
36855
  });
36582
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
+
36583
37544
  // ../../packages/warmhub-cli/src/domains/commit-submit-flags.ts
36584
37545
  var createFlags3 = {
36585
37546
  ops: flag.string({
@@ -36644,7 +37605,10 @@ var createFlags3 = {
36644
37605
  multiple: true
36645
37606
  }),
36646
37607
  type: flag.string({
36647
- 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"
36648
37612
  }),
36649
37613
  members: flag.string({
36650
37614
  description: "Collection members (comma-separated wrefs)"
@@ -36789,32 +37753,12 @@ function emitTemplateHint(ctx, operationType) {
36789
37753
  const assertionArgs = operationType === "add" ? "--kind assertion --about <Target/FILL_IN>" : "--kind assertion";
36790
37754
  ctx.err(`Template note: shapes define payload fields. To scaffold an assertion, rerun with ${assertionArgs}.`);
36791
37755
  }
36792
- var COLLECTION_ABOUT_PREFIX_RE = /^(pair|triple|set|list):(.*)$/;
37756
+ var COLLECTION_ABOUT_PREFIX_RE = /^(pair|set|list):(.*)$/;
36793
37757
  function parseCollectionAboutFlag(raw) {
36794
37758
  const match = COLLECTION_ABOUT_PREFIX_RE.exec(raw);
36795
37759
  if (!match)
36796
37760
  return raw;
36797
- const tag = match[1];
36798
- const members = match[2].split(",").map((m) => m.trim()).filter(Boolean);
36799
- switch (tag) {
36800
- case "pair":
36801
- if (members.length !== 2) {
36802
- throw new CliError(2 /* UserInput */, "USER_INPUT", `pair requires exactly 2 members, got ${members.length}`, undefined, "Example: --about pair:Location/a,Location/b");
36803
- }
36804
- break;
36805
- case "triple":
36806
- if (members.length !== 3) {
36807
- 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");
36808
- }
36809
- break;
36810
- case "set":
36811
- case "list":
36812
- if (members.length === 0) {
36813
- throw new CliError(2 /* UserInput */, "USER_INPUT", `${tag} requires at least 1 member`, undefined, `Example: --about ${tag}:Location/a,Location/b`);
36814
- }
36815
- break;
36816
- }
36817
- 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.");
36818
37762
  }
36819
37763
  var handleTemplate = async (ctx, { flags, args }) => {
36820
37764
  const shapeNames = args;
@@ -37618,7 +38562,7 @@ var handleSubmit = async (ctx, { flags, args }) => {
37618
38562
  throw new CliError(2 /* UserInput */, "USER_INPUT", `Invalid --kind "${k}". Must be one of: ${validKinds.join(", ")}`);
37619
38563
  }
37620
38564
  }
37621
- const validCollectionTypes = ["pair", "triple", "set", "list"];
38565
+ const validCollectionTypes = ["pair", "set", "list"];
37622
38566
  if (flags.type && !validCollectionTypes.includes(flags.type)) {
37623
38567
  throw new CliError(2 /* UserInput */, "USER_INPUT", `Invalid --type "${flags.type}". Must be one of: ${validCollectionTypes.join(", ")}`);
37624
38568
  }
@@ -37650,11 +38594,14 @@ var handleSubmit = async (ctx, { flags, args }) => {
37650
38594
  if (streamInput) {
37651
38595
  operations = [];
37652
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
+ }
37653
38600
  if (!flags.members) {
37654
- 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");
37655
38602
  }
37656
38603
  const members = flags.members.split(",").map((m) => m.trim()).filter(Boolean);
37657
- const arityMap = { pair: 2, triple: 3 };
38604
+ const arityMap = { pair: 2 };
37658
38605
  const expected = arityMap[collectionType];
37659
38606
  if (expected && members.length !== expected) {
37660
38607
  throw new CliError(2 /* UserInput */, "USER_INPUT", `${collectionType} requires exactly ${expected} members, got ${members.length}`);
@@ -37667,6 +38614,7 @@ var handleSubmit = async (ctx, { flags, args }) => {
37667
38614
  operation: "add",
37668
38615
  kind: "collection",
37669
38616
  type: collectionType,
38617
+ name: flags.name,
37670
38618
  members
37671
38619
  }
37672
38620
  ];
@@ -37817,8 +38765,8 @@ var COMMIT_DOMAIN = defineDomain({
37817
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"`,
37818
38766
  'wh commit submit --file dataset.jsonl --stream-id bulk-2026-06-04 --skip-existing --progress -m "bulk stream"',
37819
38767
  "wh shape template Session HypothesisCandidate -o ops.json",
37820
- "wh commit submit --type pair --members Location/a,Location/b",
37821
- '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"'
37822
38770
  ],
37823
38771
  notes: [
37824
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`.",
@@ -37832,7 +38780,7 @@ var COMMIT_DOMAIN = defineDomain({
37832
38780
  import {
37833
38781
  existsSync as existsSync7,
37834
38782
  mkdirSync as mkdirSync6,
37835
- readFileSync as readFileSync6,
38783
+ readFileSync as readFileSync7,
37836
38784
  renameSync as renameSync3,
37837
38785
  rmSync as rmSync4,
37838
38786
  writeFileSync as writeFileSync6
@@ -37868,7 +38816,7 @@ function loadInstallSnapshotCacheRaw(repoSlug) {
37868
38816
  if (!path2 || !existsSync7(path2))
37869
38817
  return null;
37870
38818
  try {
37871
- const raw = readFileSync6(path2, "utf-8");
38819
+ const raw = readFileSync7(path2, "utf-8");
37872
38820
  return JSON.parse(raw);
37873
38821
  } catch {
37874
38822
  return null;
@@ -38638,7 +39586,7 @@ function formatReservedNameWarning(name) {
38638
39586
  }
38639
39587
 
38640
39588
  // ../../packages/warmhub-cli/src/manifest/parser.ts
38641
- import { existsSync as existsSync8, readFileSync as readFileSync7 } from "node:fs";
39589
+ import { existsSync as existsSync8, readFileSync as readFileSync8 } from "node:fs";
38642
39590
  import { resolve } from "node:path";
38643
39591
  function parseComponentPackage(dirPath) {
38644
39592
  const rootDir = resolve(dirPath);
@@ -38651,7 +39599,7 @@ function parseComponentPackage(dirPath) {
38651
39599
  }
38652
39600
  let componentRaw;
38653
39601
  try {
38654
- componentRaw = JSON.parse(readFileSync7(componentJsonPath, "utf-8"));
39602
+ componentRaw = JSON.parse(readFileSync8(componentJsonPath, "utf-8"));
38655
39603
  } catch (err) {
38656
39604
  errors.push(`Failed to parse warmhub/component.json: ${err instanceof Error ? err.message : String(err)}`);
38657
39605
  return { ok: false, errors, warnings };
@@ -38666,7 +39614,7 @@ function parseComponentPackage(dirPath) {
38666
39614
  }
38667
39615
  let manifestRaw;
38668
39616
  try {
38669
- manifestRaw = JSON.parse(readFileSync7(manifestJsonPath, "utf-8"));
39617
+ manifestRaw = JSON.parse(readFileSync8(manifestJsonPath, "utf-8"));
38670
39618
  } catch (err) {
38671
39619
  errors.push(`Failed to parse warmhub/manifest.json: ${err instanceof Error ? err.message : String(err)}`);
38672
39620
  return { ok: false, errors, warnings };
@@ -38867,7 +39815,7 @@ function formatLifecycleUrl(url, colors) {
38867
39815
  }
38868
39816
 
38869
39817
  // ../../packages/warmhub-cli/src/domains/component-utils.ts
38870
- import { readFileSync as readFileSync8 } from "node:fs";
39818
+ import { readFileSync as readFileSync9 } from "node:fs";
38871
39819
  function isRegisteredComponentSource(source) {
38872
39820
  return /^[a-z0-9-]+\/[a-z0-9-]+$/.test(source);
38873
39821
  }
@@ -38907,7 +39855,7 @@ function resolveMintedTokensFlag(args) {
38907
39855
  function readManifestArg(path2) {
38908
39856
  let raw;
38909
39857
  try {
38910
- raw = readFileSync8(path2, "utf8");
39858
+ raw = readFileSync9(path2, "utf8");
38911
39859
  } catch {
38912
39860
  throw new CliError(2 /* UserInput */, "USER_INPUT", `Cannot read manifest file: ${path2}`, undefined, "Pass --manifest <path to warmhub/manifest.json>");
38913
39861
  }
@@ -41526,7 +42474,7 @@ wh assertion create --shape ShapeName --about Target/name --name my-assertion --
41526
42474
  wh thing list --repo org/repo # all things at HEAD
41527
42475
  wh thing view Shape/name --repo org/repo # inspect a thing
41528
42476
  wh thing query --shape MyShape --repo org/repo # find things by shape
41529
- 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
41530
42478
  wh assertion list --repo org/repo # all assertions at HEAD
41531
42479
  wh thing history Shape/name --repo org/repo # version history
41532
42480
 
@@ -41538,17 +42486,16 @@ cat wrefs.txt | wh thing view --format jsonl # one JSON line per *deduped
41538
42486
 
41539
42487
  ## Wref Quick Reference
41540
42488
 
41541
- **Batch tokens** (in write operations):
41542
- - \`$N\` in last segment of ADD name generates an opaque stream-scoped identifier
41543
- - \`#N\` anywhere references value allocated by \`$N\`
41544
- - 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.
41545
42492
 
41546
42493
  ## Command Reference
41547
42494
 
41548
42495
  **Global flags**: \`--repo\`, \`--format\`, \`--json\`, \`--live\`
41549
42496
  ### thing — Thing operations
41550
42497
  - \`wh thing list [--shape] [--kind] [--match] [--include-retracted]\` — Current HEAD state
41551
- - \`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.
41552
42499
  - \`wh thing history [wref] [--shape] [--about] [--include-retracted]\` — Version history
41553
42500
  - \`wh thing resolve <wref>\` — Resolve wref
41554
42501
  - \`wh thing create <name|Shape/name> --data <json-object> [--shape] [--message] [--committer]\` — Create
@@ -41557,7 +42504,7 @@ cat wrefs.txt | wh thing view --format jsonl # one JSON line per *deduped
41557
42504
  - \`wh thing query [--shape] [--kind] [--about] [--match]\` — Query by filters
41558
42505
  - \`wh thing search <query> [--shape] [--kind] [--about] [--mode]\` — Search text
41559
42506
  - \`wh thing rename <Shape/oldName> <newName>\` — Rename
41560
- - \`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
41561
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
41562
42509
 
41563
42510
  ### commit — Write operations
@@ -41643,12 +42590,12 @@ cat wrefs.txt | wh thing view --format jsonl # one JSON line per *deduped
41643
42590
  wh thing list --repo org/repo # see all things in HEAD
41644
42591
  wh thing view Shape/name --repo org/repo # inspect a specific thing
41645
42592
  wh thing history Shape/name --repo org/repo # inspect version history
41646
- wh thing about Shape/name # assertions about a thing
42593
+ wh thing about Shape/name # assertions about thing/shape
41647
42594
  \`\`\`
41648
42595
 
41649
42596
  **Create an assertion** (most common write):
41650
42597
  \`\`\`bash
41651
- # --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.
41652
42599
  wh assertion create --shape MyShape --about TargetShape/target-name \\
41653
42600
  --name my-assertion --data '{"field_a":1,"field_b":"value"}' --repo org/repo
41654
42601
  # Output includes per-operation status; relay failures when present.
@@ -41677,17 +42624,15 @@ wh commit submit --file ops.jsonl --stream-id "$ID" --chunk-size 5000 \\
41677
42624
  # --chunk-size: ops per append chunk (default 1000, max 10000); chunk≈1 is ~60× slower
41678
42625
  # --skip-existing: skips already-written add ops (drops per-row read-before-write)
41679
42626
  # Add-stream restart: rerun the WHOLE file with the SAME --stream-id.
41680
- # Fixed-name adds are idempotent via --skip-existing; tokenized ($N/#N) names
41681
- # derive from streamId, so rerun rebuilds token state. Omitting --stream-id is
41682
- # rejected because reruns must reuse the same token namespace. Mid-stream resume
41683
- # is not a CLI mode. Mixed revise/retract JSONL streams are not full-rerun safe
41684
- # 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.
41685
42630
  \`\`\`
41686
42631
 
41687
42632
  **Create collections:**
41688
42633
  \`\`\`bash
41689
- wh commit submit --type pair --members Location/a,Location/b --repo org/repo
41690
- 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
41691
42636
  \`\`\`
41692
42637
 
41693
42638
  **Modify data:**
@@ -41777,8 +42722,7 @@ var wrefSyntax = {
41777
42722
  "GameState/round-1/state"
41778
42723
  ],
41779
42724
  canonicalFormat: "wh:org/repo/Shape/name",
41780
- versionModifiers: ["@HEAD", "@vN", "@ALL"],
41781
- batchTokens: { allocate: "$N", reference: "#N" }
42725
+ versionModifiers: ["@HEAD", "@vN", "@ALL"]
41782
42726
  };
41783
42727
  var handlePrime = async (ctx) => {
41784
42728
  writeOutput(ctx, {
@@ -44102,7 +45046,7 @@ var TOKEN_DOMAIN = defineDomain({
44102
45046
  });
44103
45047
 
44104
45048
  // ../../packages/warmhub-cli/src/update-check-cache.ts
44105
- 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";
44106
45050
  import { homedir as homedir5 } from "node:os";
44107
45051
  import { dirname as dirname7, resolve as resolve3 } from "node:path";
44108
45052
 
@@ -44171,7 +45115,7 @@ var WH_CACHE_PACKAGE_NAME = WH_CLI_PACKAGE_NAME;
44171
45115
  var cachePath = (homePath) => resolve3(homePath, ".warmhub", "cli", "update-check.json");
44172
45116
  var readCache = (homePath) => {
44173
45117
  try {
44174
- return JSON.parse(readFileSync9(cachePath(homePath), "utf8"));
45118
+ return JSON.parse(readFileSync10(cachePath(homePath), "utf8"));
44175
45119
  } catch {
44176
45120
  return;
44177
45121
  }
@@ -44195,7 +45139,7 @@ var markUpdateNoticeShown = ({
44195
45139
 
44196
45140
  // ../../packages/warmhub-cli/src/domains/update-install.ts
44197
45141
  import { spawnSync as spawnSync2 } from "node:child_process";
44198
- import { existsSync as existsSync9, readFileSync as readFileSync10, realpathSync } from "node:fs";
45142
+ import { existsSync as existsSync9, readFileSync as readFileSync11, realpathSync } from "node:fs";
44199
45143
  import { homedir as homedir6 } from "node:os";
44200
45144
  import { dirname as dirname8, resolve as resolve4 } from "node:path";
44201
45145
  var DEV_INSTALL_PACKAGE_SEARCH_DEPTH = 8;
@@ -44345,7 +45289,7 @@ var isDevInstall = (scriptPath) => {
44345
45289
  for (let i = 0;i < DEV_INSTALL_PACKAGE_SEARCH_DEPTH; i += 1) {
44346
45290
  const pkgPath = resolve4(dir, "package.json");
44347
45291
  try {
44348
- const pkg = JSON.parse(readFileSync10(pkgPath, "utf8"));
45292
+ const pkg = JSON.parse(readFileSync11(pkgPath, "utf8"));
44349
45293
  if (pkg.name === "@warmhub/cli") {
44350
45294
  if (pkg.private === true)
44351
45295
  return true;
@@ -44631,6 +45575,7 @@ function registerAllDomains(registry2) {
44631
45575
  registry2.register(INIT_DOMAIN);
44632
45576
  registry2.register(REPO_DOMAIN);
44633
45577
  registry2.register(THING_DOMAIN);
45578
+ registry2.register(COLLECTION_DOMAIN);
44634
45579
  registry2.register(COMMIT_DOMAIN);
44635
45580
  registry2.register(ASSERTION_DOMAIN);
44636
45581
  registry2.register(SHAPE_DOMAIN);
@@ -45753,7 +46698,7 @@ function resolveLogLevel(flags, env) {
45753
46698
  // package.json
45754
46699
  var package_default3 = {
45755
46700
  name: "@warmhub/cli",
45756
- version: "0.67.0",
46701
+ version: "0.68.0",
45757
46702
  private: false,
45758
46703
  type: "module",
45759
46704
  description: "The wh CLI for WarmHub — create repos, commit and query data, and compound knowledge with your AI agents.",
@@ -46408,4 +47353,4 @@ if (!updateCheckSuppressedByArgv && shouldRunUpdateCheck(updateEligibility)) {
46408
47353
  var interceptedExitCode = await maybeHandleComponentShellBoundary(dispatchArgv);
46409
47354
  process.exitCode = interceptedExitCode === undefined ? await runCli(dispatchArgv, { version: package_default3.version }) : interceptedExitCode;
46410
47355
 
46411
- //# debugId=F47337F68EA6DF1364756E2164756E21
47356
+ //# debugId=201635543D774B8564756E2164756E21